9.主设备号和次设备号
更多内容请参考Linux设备驱动程序学习----目录
本章的目标是编写一个模块化的字符设备驱动程序。在本书中,取设备驱动程序名称为:scull,即 Simple Character Utility for Loading Localities,区域装载的简单字符工具。scull是一个操作内存区域的字符设备驱动程序,这片内存区域相当于一个设备。
scull的优点在于它不和硬件相关,而只是操作从内核中分配的一些内存。scull可以随便编译和运行,scull可以被移植到Linux支持的计算机平台上,scull还可以展示内核和字符设备驱动程序之间的接口并让用户运行某些测试例程。但是scull设备在实际上并不会操作外部设备。
scull的设计
编写设备驱动程序的第一步是定义驱动程序为用户程序提供的机制。由于scull是计算机内存的一部分,所以它可以随意被操作,可以是顺序或随机存取设备,也可以是一个或多个设备等。
主设备号和次设备号
对字符设备的访问是通过文件系统内的设备名称进行的,即特殊文件、设备文件,或者被称为文件系统树的节点,通常位于/dev目录。
字符设备驱动程序的设备文件可通过ls -l命令输出的第一列中的字符'c'来识别。块设备也在/dev下,由字符'b'标识。
执行 ls -l 命令,在设备文件的最后修改日期前有两个数(用逗号分隔),对于普通文件代表文件的长度,而对于设备文件,就是相应设备的主设备号和次设备号。
通常,主设备号标识设备对应的驱动程序。现代的Linux内核允许多个驱动程序共享主设备号,但大多数设备按照“一个主设备号对应一个驱动程序”的原则。
次设备号由内核使用,用于确定设备文件所指的设备。
设备编号的内部表达
在内核中,dev_t类型用来保存设备编号--包括主设备号和次设备号,包含在头文件<linux/types.h>中。在内核Linux2.6.0版本中,dev_t是一个32位的数,其中的12位用来表示主设备号,其余20位用来表示次设备号,设备编号应该使用<linux/kdev_t.h>中定义的宏。获取dev_t的主设备号或次设备号,应使用:
MAJOR(dev_t dev);
MINOR(dev_t dev);
如果需要将主设备号和次设备号转换成dev_t类型,则使用:
MKDEV(int major, int minor);
分配和释放设备编号
在建立一个字符设备之前,驱动程序需要获得一个或多个设备编号。
静态申请设备编号
如果提前确定所需要的设备编号,可以通过register_chrdev_region()函数注册设备编号:
#include <linux/fs.h>
int register_chrdev_region(dev_t first, unsigned int count, char *name);
参数说明:
first: 要分配的设备编号范围的起始值;
count: 所请求的连续设备编号的个数;
name: 是和该编号范围关联的设备名称,将出现在/proc/devices和sysfs中;
返回值:
分配成功时返回0;
错误时返回一个负的错误码,并且不能使用所请求的编号区域;
动态申请设备编号
如果不知道设备将要使用的设备编号,可以通过alloc_chrdev_region()函数注册设备编号:
#include <linux/fs.h>
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
参数说明:
dev: 用于输出参数,函数执行成功时,将保存已分配范围的第一个设备编号;
firstminor: 要使用的被请求的第一个次设备号,通常是0;
count: 所请求的连续设备编号的个数;
name: 和该编号范围关联的设备名称,将出现在/proc/devices和sysfs中;
释放设备编号
在不使用申请的设备编号时,要释放这些设备编号,通常在模块的清除函数中释放申请的设备编号;设备编号的释放需要使用以下函数实现:
#include <linux/fs.h>
void unregister_chrdev_region(dev_t first, unsigned int count);
通过以上方法,位驱动程序的使用分配设备编号,但是在用户空间程序访问设备编号,驱动程序需要将设备编号和内部函数连接起来,而这些内部函数用来实现设备的操作。
动态分配主设备号
部分主设备号已经静态地分配给了常用设备,在内核的Documentation/devices.txt文件中可以查到设备清单,驱动程序申请设备编号可以由以下两个选择:
- 简单选定一个尚未被使用的主设备号;此方法适用于只有我们自己使用驱动程序的情况;
- 通过动态方式分配主设备号;此方法适用于驱动程序被广泛使用时的情况;此时选定的主设备号可能造成冲突和麻烦;
对于一个新的驱动程序,建议不要随便选择一个当前未使用的设备号作为主设备号,应该使用动态分配机制获取主设备号。驱动程序应始终使用alloc_chrdev_region()函数,而不是register_chrdev_region()函数。
动态分配主设备号的缺点:由于分配的主设备号不能保证始终一致,无法预先创建对应的设备节点。一旦分配了设备号,就可以从/proc/devices中读取到。
为了能够加载使用动态主设备号的设备驱动程序,可以使用脚本代替insmod命令加载程序,该脚本在调用insmod之后,读取到/proc/devices以获取新分配的主设备号,然后创建对应的设备文件。
。。。。。。
反复创建和删除/dev节点有些不必要,如果只是装载和卸载单个驱动程序,则可在第一次创建设备文件之后仅使用rmmod和insmod两个命令;因为动态设备号并不是随机生成的,如果不受其他动态模块影响,可以获取到相同的动态主设备号;但是这个技巧不适用于同时有多个驱动程序存在的场合。
分配主设备号的最佳方式是:默认采用动态分配,同时保留在加载甚至是编译时指定主设备号的余地。scull驱动程序的实现方式:使用一个全局变量scull_major保存所选择的设备号,一个全局变量scull_minor保存次设备号。scull_major默认值为SCULL_MAJOR,该宏定义在scull.h中,SCULL_MAJOR默认值为0,即采用动态分配设备号。这样既可以在编译前修改SCULL_MAJOR宏定义,也可以通过insmod命令行指定scull_major的值。
在scull驱动中获取主设备号的代码,如下所示:
if (scull_major) {
dev = MKDEV(scull_major, scull_minor);
result = register_chrdev_region(dev, scull_nr, "scull");
} else {
result = alloc_chrdev_region(&dev, scull_minor, scull_nr, "scull");
scull_major = MAJOR(dev);
}
if (result < 0) {
printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
return result;
}
更多内容请参考Linux设备驱动程序学习----目录