scull, 即“Simple Character Utility for Loading Localities,区域装载的简单字符工具“的缩写。
1. scull的设计
编写驱动程序的第一步就是定义驱动程序为用户程序提供的能力(机制)。scull的源代码实现了下列设备,我们将由模块实现的每种设备称作一种”类型“:
scull0~scull3
这四个设备分别有一个全局且持久的内存区域组成。
scullpipe0~scullpipe3
这四个FIFO设备与管道类似。Scullpipe的内部实现将说明在不借助于中断的情况下如何实现阻塞式和非阻塞式读/写操作。
Scullsingle
一次只允许一个进程使用该驱动程序。
sullpriv
对每个虚拟控制台(或X终端会话)是私有的,这是因为每个控制台/终端上的进程将获取不同的内存区。
sculluid
scullwuid
上面两个可被多次打开,但每次只能有一个用户打开如果另一个用户锁定了设备,sculluid将返回“Device Busy”的错误,而scullwuid则实现了阻塞式open。
2. 主设备号和次设备号
对字符设备的访问是通过文件系统内的设备名称进行的。那些名称被称作特殊文件、设备文件件、或者简单称为文件系统树的节点,它们通常位于/dev目录。通常而言,主设备号标识设备对应的驱动程序,现代的Linux内核允许多个驱动程序共享主设备号,但我们看到的大多数设备仍然按照“一个主设备号对应一个驱动程序”的原则组织。
次设备号由内核使用,用于正确确定设备文件所指的设备。我们可以通过次设备号获得一个指向内核设备的直接指针,也可将次设备号当作设备本地数组的索引。
设备编号的内部表达
dev_t类型(在<linux/types.h>中定义)用来保存设备编号——包括主设备号和次设备号。在内 核的2.6.0版本中,dev_t是一个32位的数,其中的12位用来表示主设备号,而其余20位用来 表示次设备号。要获得dev_t对应的主设备号或次设备号,应使用:
MAJOR(dev_t dev);
MINOR (dev_t dev);
相反,如果需要将主设备号和次设备号转换成dev_t类型,则使用:
MKDEV(int major, int minor);
分配和释放设备编号
int register_chrdev_region(dev_t first, unsigned int count, char *name);int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
void unregister_chrdev_region(dev_t first, unsigned int count);
在用户程序可以访问上述设备编号之前,驱动程序需要将设备编号和内部函数连接起来,这些内部函数用来实现设备的操作。
动态分配主设备号
作为驱动程序的作者,我们可以将简单选定一个尚未被使用的编号,或者通过动态方式分配主设备号。动态分配的缺点是:由于分配的主设备号不能保持一致,所以无法预先创建设备节点。
可以利用awk这类工具从/proc/devices中获取信息,并在/dev目录中创建设备文件。
3. 一些重要的数据结构
大部分基本的驱动程序操作涉及到三个重要的内核数据结构,分别是file_operations、file和inode。文件操作
file_operations结构用来将设备编号和驱动程序连接起来。我们可以认为文件是一个“对象”,而操作它的函数是“方法”,如果采用面向对象编程的术语来表达就是,对象声明的动作将作用于其本身。
file结构
file结构代表一个打开的文件(它并不仅仅限定于设备驱动程序,系统中每个打开的文件在内核空间都有一个对应的file结构)。它由内核在open时创建,并传递给在该文件上进行操作的所有函数,直到最后的close函数。inode结构
内核用inode结构在内部表示文件,因此它和file结构不同,后者表示打开的文件描述符。对于单个文件,可能有多个表示打开的文件描述符的file结构,但它们都指向inode结构。
dev_t i_rdev
对表示设备文件的inode结构,该字段包含了真正的设备编号。
Struct cdev *i_cdev
structcdev是表示字符设备的内核的内部结构。当inode指向一个字符设备文件时,该字段包含了指向structcdev结构的指针。
4. 字符设备的注册
内核内部使用structcdev结构来表示字符设备。
分配和初始化structcdev结构的方式有两种。
如果读者打算在运行时获取一个独立的cdev结构,则应该如下编写代码:
struct cdev *my_cdev =cdev_alloc();
my_cdev->ops = &my_fops;
可以将cdev结构嵌入到自己的设备特定结构中,scull就是这样做:
void cdev_init(struct cdev*cdev, struct file_operations *fops);
另外,还有一个structcdev的字段需要初始化。和file_operations结构类似,structcdev也有一个所有者字段,应被设置为THIS_MODULE.
在cdev结构设置好之后,最后的步骤是通过下面的调用告诉内核该结构的消息:
int cdev_add(struct cdev*dev, dev_t num, unsigned int count);
要从系统中移除一个字符设备,做如下调用:
viod cdev_del(struct cdev*dev);