《{设备管理}设备驱动程序24》由会员分享,可在线阅读,更多相关《{设备管理}设备驱动程序24(62页珍藏版)》请在金锄头文库上搜索。
1、Linux设备驱动,广州嵌入式软件公共技术支持中心 梁老师 2007年07月,设备驱动概述,操作系统是通过各种驱动程序来驾驭硬件设备,它为用户屏蔽了各种各样的设备,硬件设备的抽象。 设备驱动程序:处理和管理硬件控制器的软件。 设备驱动程序是操作系统内核和机器硬件之间的接口。,设备驱动概述,设备由两部分组成,一个是被称为控制器的电器部分,另一个是机械部分。 一组寄存器组被赋予到各个控制器。I/O端口包含4组寄存器,即状态寄存器,控制寄存器,数据输入寄存器,数据输出寄存器。 状态寄存器拥有可以被CPU读取的(状态)位,用来 指示当前命令是否执行完毕,或者字节是否可以被读出或写入,以及任何错误提示。
2、 控制寄存器则用于启动一条命令(指令)或者改变设备的(工作)模式。 数据输入寄存器用于获取输入的数据。 数据输出寄存器则向CPU发送结果。 处理器和设备之间的基本界面是控制和状态寄存器。,设备驱动概述,寄存器拥有在I/O空间明确定义的地址范围。 通常这些地址在启动时被分配。 如果设备是静态加载的,各个设备的地址范围可能被预分配。这意味内核包含了已存在设备的驱动 程序。通过运行“cat /proc/ioports” 命令检查其所使用的地址范围。第一列输出显示了端口的范围而第二列则是拥用这些端口的设备。,设备驱动概述,设备驱动的概念是非常抽象的并且处于一台计算上所运行软件的最低层。 由于直接到设备
3、的硬件特性的限 制。每个设备驱动都只管理一种单一类型的设备。 如果一个应用 程序向设备提出(操作)要求。内核会联系到对应的设备驱动,设备驱动接着向特定的设备发出命令。 设备驱 动是一个函数集合:包含了许多调用入口,类似于open,close,read,write,ioctl,llseek 等。,设备驱动概述,Linux操作系统把设备纳入文件系统的范畴来管理。 文件操作是对设备操作的组织和抽象。设备操作则是对文件操作的最终实现。 每个设备都对应一个文件名,在内核中也就对应一个索引节点。 对文件操作的系统调用大都适用于设备文件。 从应用程序的角度看,设备文件逻辑上的空间是一个线性空间(起始地址为0
4、,每读取一个字节加1)。从这个逻辑空间到具体设备物理空间(如磁盘的磁道、扇区)的映射则是由内核提供,并被划分为文件操作和设备驱动两个层次。,设备驱动概述,Linux将设备分成两大类。 一类像键盘那样以字符(字节)为单位,逐个字符进行输入输出的设备,称为字符设备。 一类是像磁盘那样以块或扇区为单位,成块进行输入输出的设备,称为块设备。 文件系统通常都建立在块设备上。,设备驱动概述,文件操作和设备驱动是对一个具体的设备操作的不同层次。从这种观点出发,从概念上可以把一个系统划分为应用、文件系统和设备驱动三个层次。,设备驱动概述,设备驱动概述,要使一项设备可以被应用程序访问,首先要在系统中建立一个代表
5、此设备的设备文件,这是通过系统调用mknode()实现的。此外,更重要的是在设备驱动层要有这种设备的驱动程序。,设备驱动概述,设备文件: 任何设备都被当作路径/dev 的设备文件处理,并通过这些设备文件提供访问硬件的方法。 每个设备文件除了设备名外,还有类型、主设备号、次设备号这三个属性。 设备文件是通过mknod系统调用创建的。其原型为:mknod(const char * filename, int mode, dev_t dev) mknod /dev/led0 c 253 0,设备驱动概述,主设备号和次设备号: 主设备号标识设备对应的驱动程序。一般“一个主设备号对应一个驱动程序” 次设
6、备号用于确定设备文件所指的设备。 可通过ls l “设备文件名”命令查看设备的主次设备号,以及设备的类型。,设备驱动概述,主设备号和次设备号的内部表达: Dev_t类型用于保存设备号,称为设备编号。/linux/types.h文件中定义。 目前设备编号dev_t是一个32位的整数,其中12位表示主设备号,20位表示次设备号。 通过设备编号获取主次设备号: MAJOR(dev_t dev); MINOR(dev_t dev); 通过主次设备号合成设备编号: MKDEV(int major, int minor); Dev_t格式以后可能会发生变化,但只要使用这些宏,就可保证设备驱动程序的正确性。
7、,一些重要的数据结构,大部分驱动程序涉及三个重要的内核数据结构: 文件操作file_operations结构体 文件对象file结构体 索引节点inode结构体,一些重要的数据结构,文件操作结构体file_operations 结构体file_operations在头文件 linux/fs.h中定义,用来存储驱动内核模块提供的对设备进行各种操作的函数的指针。 结构体的每个域都对应着驱动模块用来处理某个被请求的事务的函数的地址。 struct file_operations struct module *owner; loff_t(*llseek) (struct file *, loff_t,
8、 int); ssize_t(*read) (struct file *, char _user *, size_t, loff_t *); ssize_t(*write) (struct file *, const char _user *, size_t, loff_t *); 。 ,一些重要的数据结构,file_operations重要的成员 Struct module *owner ,指向拥有该结构体的模块的指针。 方法llseek用来修改文件的当前读写位置,把新位置作为返回值返回。 方法read用来从设备中读取数据。非负返回值表示成功读取的直接数。 方法write向设备发送数据。 方
9、法ioctl提供一种执行设备特定命令的方法。,一些重要的数据结构,file_operations重要的成员 驱动内核模块是不需要实现每个函数的。相对应的file_operations的项就为 NULL。 Gcc的语法扩展,使得可以定义该结构体: struct file_operations fops = read: device_read, write: device_write, open: device_open, release: device_release ; 这种语法清晰,没有显示声明的结构体成员都被gcc初始化为NULL。,一些重要的数据结构,file_operations重要的
10、成员 标准C的标记化结构体的初始化方法: struct file_operations fops = .read = device_read, .write = device_write, .open = device_open, .release = device_release ; 推荐使用该方法,提高移植性,方法允许对结构体成员进行重新排列。没有显示声明的结构体成员同样都被gcc初始化为NULL。 指向结构体file_operations的指针通常命名为fops。,一些重要的数据结构,文件对象file结构体 文件对象file代表着一个打开的文件。进程通过文件描述符fd与已打开文件的fil
11、e结构相联系。进程通过它对文件的线性逻辑空间进行操作。例如:file-f_op-read(); Struct file 在中定义。 指向结构体struct file的指针通常命名为filp,或者file。建议使用文件指针filp。,一些重要的数据结构,文件对象file结构体的成员 Struct file_operations *f_op; 与文件相关的操作结构体指针。与文件相关的操作是在打开文件的时候确定下来的,也就是确定该指针的值。可在需要的时候,改变指针所指向的文件操作结构体。用C语言实现面向对象编程的方法重载。 其他成员可先忽略,后面具体实例分析。因为设备驱动模块并不自己直接填充结构体
12、file,只是使用file中的数据。,一些重要的数据结构,索引节点inode结构 文件打开,在内存建立副本后,由唯一的索引节点inode描述。 与file结构不同。 file结构是进程使用的结构,进程每打开一个文件,就建立一个file结构。不同的进程打开同一个文件,建立不同的file结构。 Inode结构是内核使用的结构,文件在内存建立副本,就建立一个inode结构来描述。一个文件在内存里面只有一个inode结构对应。,一些重要的数据结构,索引节点inode结构 Inode结构包含大量描述文件信息的成员变量。 但是对于描述设备文件的inode,跟设备驱动有关的成员只有两个。 Dev_t i_r
13、dev; 包含真正的设备编号。 Struct cdev *i_cdev; 指向cdev结构体的指针。cdev是表示字符设备的内核数据结构。 从inode中获得主设备号和次设备号的宏: Unsigned int iminor(struct inode *inode); Unsigned int imajor(struct inode *inode);,驱动程序中的内存分配,在Linux内核模式下,不能使用用户态的malloc()和free()函数申请和释放内存。 内核编程最常用的内存申请和释放函数为kmalloc()和kfree(),其原型为: include/linux/kernel.h vo
14、id *kmalloc(unsigned int len, int priority); void kfree(void *_ptr); priority参数: 通常设置为GFP_KERNEL,可能会引起睡眠. 如果在中断服务程序里申请内存则要用GFP_ATOMIC参数,在中断中是不允许睡眠的。,初始化和卸载函数,驱动程序是内核的一部分,因此我们需要给其添加模块初始化函数,该函数用来完成对所控设备的初始化工作,并调用register_chrdev() 函数注册字符设备. int register_chrdev(unsigned int major, const char *name, stru
15、ct file_operations *fops); major 是给定的主设备号。为0代表什么? name 是驱动的名字(将出现在 /proc/devices), fops 是设备驱动的file_operations 结构。 register_chrdev 将给设备分配 0 - 255 的次设备号, 并且为每一个建立一个缺省的 cdev 结构。 与模块初始化函数对应的就是模块卸载函数,需要调用register_chrdev()的反函数,设备操作函数集的定义,file_operations结构体,驱动程序只是利用其中的一部分。 对于字符设备来说,要提供的主要入口有:open ()、releas
16、e ()、read ()、write ()、ioctl ()等。,设备操作函数集的定义,open()函数 对设备特殊文件进行open()系统调用时,将调用驱动程序的open () 函数: int (*open)(struct inode * ,struct file *); 参数inode为设备特殊文件的inode (索引结点) 结构的指针, 参数file是指向这一设备的文件结构的指针。 open()的主要任务是确定硬件处在就绪状态、验证次设备号的合法性(次设备号可以用MINOR(inode- i - rdev) 取得)、控制使用设备的进程数、根据执行情况返回状态码(0表示成功,负数表示存在错误) 等;,设备操作函数集的定义,release()函数 当最后一个打开设备的用户进程执行close ()系统调用时,内核将调用驱动程序的release () 函数: void (*release) (struct inode * ,struct file *) ; release 函数的主要任务是清理未结束的输入/输出操作、释放资源、用户自定义排他标志的复位等.,设备操作函数集的定义,