【转载】Linux驱动程序框架

2023-11-17

http://blog.csdn.net/lemon_fantasy/archive/2009/02/17/3901030.aspx

Linux 将所有外部设备看成是一类特殊文件,称之为“设备文件”,如果说系统调用是Linux内核和应用程序之间的接口,那么设备驱动程序则可以看成是Linux 内核与外部设备之间的接口。设备驱动程序向应用程序屏蔽了硬件在实现上的细节,使得应用程序可以像操作普通文件一样来操作外部设备。

1. 字符设备和块设备

Linux 抽象了对硬件的处理,所有的硬件设备都可以像普通文件一样来看待:它们可以使用和操作文件相同的、标准的系统调用接口来完成打开、关闭、读写和I/O控制操作,而驱动程序的主要任务也就是要实现这些系统调用函数。Linux系统中的所有硬件设备都使用一个特殊的设备文件来表示,例如,系统中的第一个IDE 硬盘使用/dev/hda表示。每个设备文件对应有两个设备号:一个是主设备号,标识该设备的种类,也标识了该设备所使用的驱动程序;另一个是次设备号,标识使用同一设备驱动程序的不同硬件设备。设备文件的主设备号必须与设备驱动程序在登录该设备时申请的主设备号一致,否则用户进程将无法访问到设备驱动程序。

在Linux操作系统下有两类主要的设备文件:一类是字符设备,另一类则是块设备。字符设备是以字节为单位逐个进行 I/O操作的设备,在对字符设备发出读写请求时,实际的硬件I/O紧接着就发生了,一般来说字符设备中的缓存是可有可无的,而且也不支持随机访问。块设备则是利用一块系统内存作为缓冲区,当用户进程对设备进行读写请求时,驱动程序先查看缓冲区中的内容,如果缓冲区中的数据能满足用户的要求就返回相应的数据,否则就调用相应的请求函数来进行实际的I/O操作。块设备主要是针对磁盘等慢速设备设计的,其目的是避免耗费过多的CPU时间来等待操作的完成。一般说来,PCI卡通常都属于字符设备。

所有已经注册(即已经加载了驱动程序)的硬件设备的主设备号可以从/proc/devices文件中得到。使用mknod命令可以创建指定类型的设备文件,同时为其分配相应的主设备号和次设备号。例如,下面的命令:

[root@gary root]# mknod  /dev/lp0  c  6  0

将建立一个主设备号为6,次设备号为0的字符设备文件/dev/lp0。当应用程序对某个设备文件进行系统调用时,Linux内核会根据该设备文件的设备类型和主设备号调用相应的驱动程序,并从用户态进入到核心态,再由驱动程序判断该设备的次设备号,最终完成对相应硬件的操作。

2. 设备驱动程序接口

    Linux中的I/O子系统向内核中的其他部分提供了一个统一的标准设备接口,这是通过include/linux/fs.h中的数据结构file_operations来完成的:    struct file_operations {
        struct module *owner;
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char *, size_t, loff_t *);
        ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
        int (*readdir) (struct file *, void *, filldir_t);
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
        int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, struct dentry *, int datasync);
        int (*fasync) (int, struct file *, int);
        int (*lock) (struct file *, int, struct file_lock *);
        ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
        ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
};

    当应用程序对设备文件进行诸如 open、close、read、write等操作时,Linux内核将通过file_operations结构访问驱动程序提供的函数。例如,当应用程序对设备文件执行读操作时,内核将调用file_operations结构中的read函数。

3. 设备驱动程序模块 

    Linux下的设备驱动程序可以按照两种方式进行编译,一种是直接静态编译成内核的一部分,另一种则是编译成可以动态加载的模块。如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能动态地卸载,不利于调试,所有推荐使用模块方式。

    从本质上来讲,模块也是内核的一部分,它不同于普通的应用程序,不能调用位于用户态下的C或者C++库函数,而只能调用Linux内核提供的函数,在/proc/ksyms中可以查看到内核提供的所有函数。

    在以模块方式编写驱动程序时,要实现两个必不可少的函数init_module( )和cleanup_module( ),而且至少要包含<linux/krernel.h>和<linux/module.h>两个头文件。在用gcc编译内核模块时,需要加上-DMODULE -D__KERNEL__ -DLINUX这几个参数,编译生成的模块(一般为.o文件)可以使用命令insmod载入Linux内核,从而成为内核的一个组成部分,此时内核会调用模块中的函数init_module( )。当不需要该模块时,可以使用rmmod命令进行卸载,此进内核会调用模块中的函数cleanup_module( )。任何时候都可以使用命令来lsmod查看目前已经加载的模块以及正在使用该模块的用户数。

    3. 设备驱动程序结构

    了解设备驱动程序的基本结构(或者称为框架),对开发人员而言是非常重要的,Linux的设备驱动程序大致可以分为如下几个部分:驱动程序的注册与注销、设备的打开与释放、设备的读写操作、设备的控制操作、设备的中断和轮询处理。
驱动程序的注册与注销

    向系统增加一个驱动程序意味着要赋予它一个主设备号,这可以通过在驱动程序的初始化过程中调用register_chrdev( )或者register_blkdev( )来完成。而在关闭字符设备或者块设备时,则需要通过调用unregister_chrdev( )或unregister_blkdev( )从内核中注销设备,同时释放占用的主设备号。
  设备的打开与释放

    打开设备是通过调用file_operations结构中的函数open( )来完成的,它是驱动程序用来为今后的操作完成初始化准备工作的。在大部分驱动程序中,open( )通常需要完成下列工作:

1.    检查设备相关错误,如设备尚未准备好等。

2.    如果是第一次打开,则初始化硬件设备。

3.    识别次设备号,如果有必要则更新读写操作的当前位置指针f_ops。

4.    分配和填写要放在file->private_data里的数据结构。

5.    使用计数增1。

    释放设备是通过调用file_operations结构中的函数release( )来完成的,这个设备方法有时也被称为close( ),它的作用正好与open( )相反,通常要完成下列工作:

6.    使用计数减1。

7.    释放在file->private_data中分配的内存。

8.    如果使用计算为0,则关闭设备。

设备的读写操作

    字符设备的读写操作相对比较简单,直接使用函数read( )和write( )就可以了。但如果是块设备的话,则需要调用函数block_read( )和block_write( )来进行数据读写,这两个函数将向设备请求表中增加读写请求,以便Linux内核可以对请求顺序进行优化。由于是对内存缓冲区而不是直接对设备进行操作的,因此能很大程度上加快读写速度。如果内存缓冲区中没有所要读入的数据,或者需要执行写操作将数据写入设备,那么就要执行真正的数据传输,这是通过调用 数据结构blk_dev_struct中的函数request_fn( )来完成的。

·   设备的控制操作

    除了读写操作外,应用程序有时还需要对设备进行控制,这可以通过设备驱动程序中的函数ioctl( )来完成。ioctl( )的用法与具体设备密切关联,因此需要根据设备的实际情况进行具体分析。

·   设备的中断和轮询处理

    对于不支持中断的硬件设备,读写时需要轮流查询设备状态,以便决定是否继续进行数据传输。如果设备支持中断,则可以按中断方式进行操作。

4.PCI驱动程序实现

·   关键数据结构

    PCI设备上有三种地址空间:PCI的I/O空间、PCI的存储空间和PCI的配置 空间。CPU可以访问PCI设备上的所有地址空间,其中I/O空间和存储空间提供给设备驱动程序使用,而配置空间则由Linux内核中的PCI初始化代码使用。内核在启动时负责对所有PCI设备进行初始化,配置好所有的PCI设备,包括中断号以及I/O基址,并在文件/proc/pci中列出所有找到的 PCI设备,以及这些设备的参数和属性。

    Linux驱动程序通常使用结构(struct)来表示一种设备,而结构体中的变量则代表某一具体设备,该变量存放了与该设备相关的所有信息。好的驱动程序都应该能驱动多个同种设备,每个设备之间用次设备号进行区分,如果采用结构数据来代表所有能由该驱动程序驱动的设备,那么就可以简单地使用数组下标来表示次设备号。

在PCI驱动程序中,下面几个关键数据结构起着非常核心的作用:

·   pci_driver

       这个数据结构在文件include/linux/pci.h里,这是Linux内核版本2.4之后为新型的PCI设备驱动程序所添加的,其中最主要的是用于识别设备的id_table结构,以及用于检测设备的函数probe( )和卸载设备的函数remove( ):

struct pci_driver {

    struct list_head node;

    char *name;

    const struct pci_device_id *id_table;

    int (*probe) (struct pci_dev *dev, const struct pci_device_id *id);

    void (*remove) (struct pci_dev *dev);

    int (*save_state) (struct pci_dev *dev, u32 state);

    int (*suspend)(struct pci_dev *dev, u32 state);

    int (*resume) (struct pci_dev *dev);

    int (*enable_wake) (struct pci_dev *dev, u32 state, int enable);

};

·   pci_dev

这个数据结构也在文件include/linux/pci.h里,它详细描述了一个PCI设备几乎所有的硬件信息,包括厂商ID、设备ID、各种资源等:

struct pci_dev {

    struct list_head global_list;

    struct list_head bus_list;

    struct pci_bus *bus;

    struct pci_bus *subordinate;

    void        *sysdata;

    struct proc_dir_entry *procent;

    unsigned int    devfn;

    unsigned short vendor;

    unsigned short device;

    unsigned short subsystem_vendor;

    unsigned short subsystem_device;

    unsigned int    class;

    u8        hdr_type;

    u8        rom_base_reg;

    struct    pci_driver *driver;

    void      *driver_data;

    u64       dma_mask;

    u32        current_state;

    unsigned short vendor_compatible[DEVICE_COUNT_COMPATIBLE];

    unsigned short device_compatible[DEVICE_COUNT_COMPATIBLE];

    unsigned int    irq;

    struct resource resource[DEVICE_COUNT_RESOURCE];

    struct resource dma_resource[DEVICE_COUNT_DMA];

    struct resource irq_resource[DEVICE_COUNT_IRQ];

    char       name[80];

    char       slot_name[8];

    int        active;

    int        ro;

    unsigned short regs;

    int (*prepare)(struct pci_dev *dev);

    int (*activate)(struct pci_dev *dev);

    int (*deactivate)(struct pci_dev *dev);

};

·   基本框架

    在用模块方式实现PCI设备驱动程序时,通常至少要实现以下几个部分:初始化设备模块、设备打开模块、数据读写和控制模块、中断处理模块、设备释放模块、设备卸载模块。下面给出一个典型的PCI设备驱动程序的基本框架,从中不难体会到这几个关键模块是如何组织起来的。

/* 指明该驱动程序适用于哪一些PCI设备 */

static struct pci_device_id demo_pci_tbl [] __initdata = {

    {PCI_VENDOR_ID_DEMO, PCI_DEVICE_ID_DEMO,

     PCI_ANY_ID, PCI_ANY_ID, 0, 0, DEMO},

    {0,}

};

/* 对特定PCI设备进行描述的数据结构 */

struct demo_card {

    unsigned int magic;

    /* 使用链表保存所有同类的PCI设备 */

    struct demo_card *next;

    /* ... */

}

/* 中断处理模块 */

static void demo_interrupt(int irq, void *dev_id, struct pt_regs *regs)

{

    /* ... */

}

/* 设备文件操作接口 */

static struct file_operations demo_fops = {

    owner:      THIS_MODULE,   /* demo_fops所属的设备模块 */

    read:       demo_read,    /* 读设备操作*/

    write:      demo_write,    /* 写设备操作*/

    ioctl:      demo_ioctl,    /* 控制设备操作*/

    mmap:       demo_mmap,    /* 内存重映射操作*/

    open:       demo_open,    /* 打开设备操作*/

    release:    demo_release    /* 释放设备操作*/

    /* ... */

};

/* 设备模块信息 */

static struct pci_driver demo_pci_driver = {

    name:       demo_MODULE_NAME,    /* 设备模块名称 */

    id_table:   demo_pci_tbl,    /* 能够驱动的设备列表 */

    probe:      demo_probe,    /* 查找并初始化设备 */

    remove:     demo_remove    /* 卸载设备模块 */

    /* ... */

};

static int __init demo_init_module (void)

{

    /* ... */

}

static void __exit demo_cleanup_module (void)

{

    pci_unregister_driver(&demo_pci_driver);

}

/* 加载驱动程序模块入口 */

module_init(demo_init_module);

/* 卸载驱动程序模块入口 */

module_exit(demo_cleanup_module);

上面这段代码给出了一个典型的PCI设备驱动程序的框架,是一种相对固定的模式。需 要注意的是,同加载和卸载模块相关的函数或数据结构都要在前面加上__init、__exit等标志符,以使同普通函数区分开来。构造出这样一个框架之后,接下去的工作就是如何完成框架内的各个功能模块了。

转载于:https://www.cnblogs.com/ZJoy/archive/2011/01/09/1931185.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【转载】Linux驱动程序框架 的相关文章

  • 凸优化学习(三)——凸函数

    注意 本文内容来自于吴恩达老师cs229课堂笔记的中文翻译项目 https github com Kivy CN Stanford CS 229 CN 中的凸优化部分的内容进行翻译学习 3 凸函数 凸优化的一个核心要素是凸函数的概念 定义

随机推荐

  • vtm配置以及编码示例

    vtm配置以及编码示例 下载VTM源码 网址 VTM源码 选择对应的版本下载 版本从2 0到最新的版本 1 0版本的VTM需要使用HM的工具svn下载 VTM 1 0源码地址 https jvet hhi fraunhofer de svn
  • STM32F4 IAP 跳转 APP问题

    1 概念 IAP 的作用 网上其他资料已经有很多介绍了 这里放一个链接 不进行深入的介绍 本文的关注重点是Bootloader在跳转APP程序中出现的问题 IAP的实现原理讲解以及中断向量表的偏移 2 程序 本人主要做应用层的开发 所有Bo
  • python--判断奇数偶数

    num int input 输入一个数 if num 2 0 print 0 是偶数 format num else print 0 是奇数 format num 优化 while True try num int input 输入一个整数
  • TS中类型推论、类型别名和never类型

    一 类型推论 TypeScript会在没有明确的指定类型的时候推测出一个类型 这就是类型推论 如果没声明变量 没定义类型 也没赋值 这时候TS会推断成any类型可以进行任何操作 let str str 456 str null 二 类型别名
  • 5分钟 教你搭建个人博客

    链接 https www jianshu com p 4eaddcbe4d12 五分钟倒数已经可以计时了 三步完成免费个人博客搭建 这是一篇小白也能看懂的文章 本文主要针对mac OS Windows 除了软件安装方式和命令有些区别 装了g
  • C++11之初始化列表

    系列文章 C 11之正则表达式 regex match regex search regex replace C 11之线程库 Thread Mutex atomic lock guard 同步 C 11之智能指针 unique ptr s
  • redis部署锦集,redis部署都在这了。

    大数据之Redis Redis各种部署方案和实现 Redis在大数据技术的发展中主要是用来作为中间值存储 快速计算 管道等工具使用 今天先给大家介绍一下关于Redis的部署方案和实现 其原理和应用将会在下一期和大家分享 直接上干货 一 单机
  • 从零起步:学习数据结构的完整路径

    文章目录 1 基础概念和前置知识 2 线性数据结构 3 栈和队列 4 树结构 5 图结构 6 散列表和哈希表 7 高级数据结构 8 复杂性分析和算法设计 9 实践和项目 10 继续学习和深入 11 学习资源 12 练习和实践 欢迎来到数据结
  • 在MDK5中新建STM32F4XX工程模板(基于固件库)

    0 库函数和寄存器的区别 本质上是一样的 可以在库函数模板里面 直接操作寄存器 因为官方库相关头文件有寄存器定义 但是不能在寄存器模板调用库函数 因为没有引入库函数相关定义 了解寄存器基本原理的目的是为了让我们对STM32相关知识有比较深入
  • linux显示指定目录下的所有文件的文件类型和文件名

    linux显示指定目录下的所有文件的文件类型和文件名 并打印普通文件个数 用到的函数 DIR opendir const char name struct dirent readdir DIR dirp int closedir DIR d
  • python 离群值_如何从Numpy数组中删除离群值

    我写了一个代码 取多个图像的平均值来检索背景 这基本上删除了图像中的移动对象 我试着在取平均值之前去掉离群值 这样我就可以得到背景而不是褪色的对象 我尝试了一些技巧 最近的一个是 usr bin env python3 import num
  • lock-linux

    sem unlink sem open pthread getspecific
  • 初识Java——指针

    指针 Pointer 还记得第一次接触指针是在大一的c语言学习中 当时学完之后只知道 就是代表的指针 但是至于其真实含义及用法还没有真正学会 这一次从零开始学习Java 又一次学习到了指针 因此对指针有了更多的认识 下面就是通过最近的学习我
  • python 制作菜单栏的详细教程

    创建一个下拉式菜单 from tkinter import import tkinter messagebox 创建主窗口 win Tk win config bg 87CEEB win title matinal的分析系统 win geo
  • XSS、CSRF、SSRF、暴力破解

    目录 1 XSS 1 1 XSS概述 1 1 1 什么是XSS 1 1 2 XSS攻击流程 1 1 3 XSS触发条件 1 标签法 2 伪协议 3 事件 1 2 反射型XSS Low Medium High 1 3 存储型XSS Low M
  • 51单片机实训(一)————Keil 基本操作

    文章目录 前言 一 Keil是什么 二 Keil基本操作 1 新建Keil工程 2 编写代码 3 输出 hex 文件 并编译 4 关联仿真程序 总结 前言 大家好 我是三 这是我的第二篇文章 更新有点慢 抱歉 上一篇文章 咱们了解学习了Pr
  • PTA 1075 链表元素分类 (c++)

    1075 链表元素分类 25 分 思路 首先建立一个结构体包含数据和下个地址 还有大小为3得vector数组 然后建立一个结构体数组 下标即为当前结点得地址 这样其实就可以用结构体数组来模拟链表进行一系列操作 然后定义一个变量并赋给它首地址
  • XML乱码问题和encoding的理解

    文件编码 文件编码也称为字符编码 用于指定在处理文本时如何表示字符 一种编码可能优于另一种编码主要取决于它能处理或不能处理哪些语言字符 不过通常首选的是 Unicode 读取或写入文件时 未正确匹配文件编码的情况可能会导致发生异常或产生不正
  • maven打包jar包到本地仓库(命令和插件两种方式)

    maven打包代码到本地仓库 命令行 打包准备 下载好maven 配置了maven的全局变量 测试 进入命令行输入 mvn version 检查maven是否安装好 joi xuyideMacBook Pro mvn version Apa
  • 【转载】Linux驱动程序框架

    http blog csdn net lemon fantasy archive 2009 02 17 3901030 aspx Linux 将所有外部设备看成是一类特殊文件 称之为 设备文件 如果说系统调用是Linux内核和应用程序之间的