一、基础知识
1、设备文件
设备驱动程序为内核和硬件设备之间的接口,其对硬件细节透明,且调和被映射为特殊的文件进行处理。每个设备都对应一个文件名,在内核中也对应一个索引节点,应用程序可以通过设备的文件名来访问硬件设备。
Linux为文件和设备提供了一致性的接口,用户操作设备文件与操作普通文件类似。例如:通过open()函数可打开设备文件,建立起应用程序与目标设备的连接;之后,可以通过read()、write()等常规文件函数对目标设备进行操作。linux将硬件设备分为两大类:块设备和字符设备。相应地,提供了两个标准接口:块设备文件和字符文件。块设备驱动和字符设备不同,字符设备是直接和虚拟文件系统进行交互,而块设备驱动则是通过块缓冲/高度层间接和虚拟文件系统交互;块设备驱动数据访问都是以块为单位;多个块I/O需要组成一个请求队列,这个功能是块缓冲/调度层提供的,它出于硬件特性和读写性能的考虑,将块I/O进行重新排序,并组成 一个请求队列,交给内核 ,内核则调用请求队列处理函数来逐个处理请求队列。
2、 主设备号与次设备号
设备文件作为一个可挂接的文件系统并可被挂接到任何需要的目录下。调和文件的索引节点,是包含硬件设备相关信息的一个表示。通常,主设备号指明唯一的设备类型,即标识设备对应的驱动程序类型,它是块设备表或字符设备表中表项的索引。
在内核中,dev_t类型用来保存设备编号,包括主设备号和次设备号。在使用dev_t时,应该采用系统提供的一组宏对设备号进行访问:
MAJOR(dev_t dev); /*从dev_t类型中取出主设备号*/
MINOR(dev_t dev); /*从dev_t类型中取出次设备号*/
mknod()函数可用来创建设备文件,使用该函数需要4个参数:设备文件名、设备类型、主设备号及次设备号。用户也可通过mknod命令创建设备文件,例如:
mknod/dev/fd1 b 2 1
该命令在/dev目录下创建一个名为fd1的块设备文件,文件的主设备号为2,次设备号1。设备文件的主设备号与次设备号放在索引对象的i_rdev字段,设备文件的类型存放在索引对象的i_mode字段。对于内核而言,设备名是无关紧要的,依靠主设备号和次设备号对设备进行标识。
3、设备文件的VFS处理
在VFS中,每个文件都有一个索引节点与之对应。在内核的inode结构中,有一个名为i_fop成员,其类型file_operations.file_operations定义文件的各种操作,用户对文件的操作是通过调用file_operations来实现的。为了使用户对设备文件的操作转换为设备的驱动操作,VFS必须在设备文件打开时,改变其node结构中i_fop成员的默认值,将该值换成与该设备相关的具体函数操作。
当用户准备对设备文件进行访问时,文件系统读取设备文件在磁盘上相应的索引节点,并存入主存inode结构中。内核将文件的主设备号与次设备号写入inode结构中的i_dev字段,并将i_fop字段设置为def_bkl_fops(块)或def_chr_fops(字符)。
二、字符设备
1、数据结构
3个重要内核数据结构,file_operations、file 和inode。内核通过这3个数据结构的关联,将用户对设备文件的操作转换为驱动程序相关函数的调用,,进而实现对设备的驱动操作。
file_operations 函数指针集合:
图
在内核中,通过包含一个指向file_operations结构的f_op字段,每个打开的设备文件都会与一组文件操作函数相关联。file_operations结构的每个字段都必须指向驱动程序中实现特定操作的具体函数,对于不支持的文件操作,可设为NULL值。
file代表一个打开了的文件。它由内核在使用open()函数时建立,并传递给该文件上进行操作的所有函数,直到最后的close()函数。当文件的所有 操作结束后,内核会释放
内核用inode结构在内部标识文件,它和file结构不同,后者标识打开的文件描述符。对于单个文件,可能会有许多个表示打开的文件描述符的file结构,但它们 都指向同一个inode结构。 inode结构中有两个重要的字段:
struct cdev *i_cdev;
dev_t i_rdev;
i_cdev结构表示字符设备的内核数据结构,当inode指向一个字符设备文件时,该字段包含指向struct cdev结构的指针。i_rdev包含设备编号,内核不推荐开发直接通过访问inode结构的 i_rdev字段来获得设备的主、次设备号,宏:
unsigned int imajor(struct inode *inode);
unsigned int iminor(struct inode *inode);
2、申请与释放设备编号
在创建字符设备之前,需要给设备申请设备编号。
int register_chrdev_region(dev_t from,unsigned count,const char *name);//静态
开发者须明确知道所需要的设备编号。
int alloc_chrdev_region(dev_t from,unsigned count,const char *name);//设备号并没有预先确定,动态
缺点:每次分配的主设备号不能够保持一致,因而用户不能够预先在/dev目录下用mknod命令创建设备文件。用户需要在驱动程序注册后,根据/proc/device读取到设备所用到的设备编号,然后创建相应的设备文件。
int unregister_chrdev_region(dev_t from,unsigned count);
驱动程序一般通过模块加载,可在模块的初始化函数进行设备号申请,并在模块的清理函数中,对设备号进行释放。
3、设备注册与注销
内核用cdev结构 来表示字符设备,在内核调用设备的驱动操作之前,必须分配并注册一个上述结构。分配 和初始化cdev结构有两种方式,获得一个独立的cdev结构:
struct cdev *my_cdev=cdev_alloc();
my_cdev->ops=&my_ops;
也可以通过内核函数cdev_init()来进行cdev结构的初始化。通过cdev_add()内核函数将cdev结构添加到内核中,
int cdev_add(struct cdev *p,dev_t dev,unsigned count);
若cdev_add成功返回,驱动程序就可以有时被内核调用。
void cdev_del(struct cdev *dev);