第 6 章 字符设备驱动程序
6.1 字符设备驱动介绍
6.1.2 主、次设备号
可以看到 inode 结构包含了大量有关文件的信息,但通常情况下对设备驱动程序开发有 用的成员有下面两个:
n dev_t i_rdev;
该成员表示设备文件的 inode 结构,它包含了真正的设备编号(将在下一节介绍设备编号)。
n struct cdev *i_cdev;
该成员表示字符设备的内核的内部结构,当 inode 指向一个字符设备文件时,该成员包含 了指向 struct cdev 结构的指针,其中 cdev 结构是字符设备结构体。
其实关于 Linux 字符设备驱动开发还有一些其它的数据结构,这里只介绍了最经常使用 的三个结构体,在后面的章节中还会介绍其他的数据结构。
6.1.2 主、次设备号
对字符设备的访问是通过文件系统内的设备名称进行的,那些文件被称为设备文件,他 们通常位于/dev 目录下。在/dev 目录下,通过 ls –l 命令可以查看系统中的字符设备和块设 备。例如在系统中利用 ls –l 命令查看设备显示如下:
brw-rw---- 1 root disk 15, 0 Jan 30 2003 cdu31a brw-rw---- 1 root disk 24, 0 Jan 30 2003 cdu535 crw-rw---- 1 root disk 67, 0 Jan 30 2003 cfs0 brw-rw---- 1 root disk 30, 0 Jan 30 2003 cm205cd brw-rw---- 1 root disk 32, 0 Jan 30 2003 cm206cd drwxr-xr-x 2 root root 4096 Oct 24 17:43 compaq crw--- 1 root root 5, 1 Feb 11 15:15 console crw--- 1 root root 1, 6 Jan 30 2003 core drwxr-xr-x 18 root root 4096 Oct 24 17:43 cpu crw-rw---- 1 root uucp 5, 64 Jan 30 2003 cua0 crw-rw---- 1 root uucp 5, 65 Jan 30 2003 cua1 crw-rw---- 1 root uucp 5, 66 Jan 30 2003 cua2 crw-rw---- 1 root uucp 5, 67 Jan 30 2003 cua3
crw-rw---- 1 root uucp 205, 16 Jan 30 2003 cuam0 crw-rw---- 1 root uucp 205, 17 Jan 30 2003 cuam1 crw-rw---- 1 root uucp 205, 18 Jan 30 2003 cuam2 crw-rw---- 1 root uucp 205, 19 Jan 30 2003 cuam3 crw-rw---- 1 root uucp 44, 0 Jan 30 2003 cui0
上面只列举了部分的设备,其中第一列中第一个字符为 c(character)的行代表的是字 符设备,为 b(block)的行代表为块设备。在日期的前面有两个数字,并且用逗号分开,这 两个数字就是相应设备的主设备号和次设备号。读者会问,用主设备号和次设备号干什么?
通常主设备号用来标识该设备对应的驱动程序,而次设备号是由内核使用,用于正确确定设 备文件所指的设备。上述设备中/dev/ console,/dev/ cua0,/dev/ cua1,/dev/ cua2,/dev/ cua3 的主设备号都是 5,所以这些设备都由驱动程序 5 管理。现代的 Linux 内核允许多个驱动程 序共享主设备号,但是大多数设备仍然按照“一个主设备号对应一个驱动程序”的原则安排。
6.1.2.1 主、次设备号的内部表示
在内核中,用 dev_t 类型来保存设备编号(包括主设备号和次设备号)。在 Linux 2.6.10 版本中,dev_t 是一个 32 位的数,其中用 12 位表示主设备号,而其余 20 位用来表示次设备 号。要获得 dev_t 的主设备号和次设备号,应使用以下宏:
n MAJOR(dev) n MINOR(dev)
这两个宏定义在<include/linux/kdev_t.h>文件中,具体定义如下:
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
通过定义可以看出,获得主、次设备号的方法就是通过左移和位与获得,相反,如果需 要将主设备号和次设备号转换成 dev_t 类型,则使用以下宏:
n MKDEV(int major, int minor)
这个宏的定义也在<include/linux/kdev_t.h>文件中,具体定义如下:
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
这个宏的实现通过对主设备号移位后然后与次设备号或而产生。关于主设备号的定义在
<include/linux/major.h>文件中,关于设备号的分配可参考内核中<Documentation/devices.txt>
文件。
在内核 2.6 以前,限定 255 个主设备号和 255 个次设备号,在计算机硬件飞速发展的时 代,这些设备号已经不能满足大量设备的需求,所以在内核 2.6 版本中,它可以容量大于 255 个的设备。
6.1.2.2 静态分配和释放主设备号
在建立一个字符设备之前,首先要获得一个设备编号,在 Linux 2.6.10 内核中,该工作 通过 register_chrdev_region 函数完成。该函数在 include/linux/fs.h 文件中声明,该函数的原 型如下:
int register_chrdev_region(dev_t from, unsigned count, const char *name)
其中,参数 from 是要分配的设备编号范围的起始值;参数 count 是所请求的连续设备 编号的个数;如果 count 值非常大,则所请求的范围可能和下一个主设备号重叠,但是只要
所申请的编号范围是可用的,则不会带来任何问题。参数 name 是和该编号范围关联的设备 名称。该函数在分配字符设备号成功时返回 0,在错误情况下,将返回一个负的错误码,并 且不能使用所请求的编号区域。
无论采用 register_chrdev_region 静态分配设备号还是下一节介绍的 alloc_chrdev_region 函数动态分配设备编号,都必须在不再使用这些设备编号时释放它们,释放设备编号使用 unregister_chrdev 函数,该函数的原型如下:
int unregister_chrdev(unsigned int major, const char *name)
其中,参数 major 为要释放设备的主设备号,参数 name 为相关的设备名称。
6.1.2.3 动态分配主设备号
如果我们提前知道所需要的设备编号,那么使用 register_chrdev_region 函数非常合适,
但是经常我们不知道设备要使用哪些主设备号,在运行过程中通常使用 alloc_chrdev_region 函数,内核将会恰当地动态分配所需主设备号,该函数原型如下:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
其中,参数 dev 是仅用于输出的参数,在成功完成调用后将保存已分配范围的第一个编 号;参数 baseminor 是要使用的被请求的第一个次设备号,它通常是 0;参数 count 为所请 求的连续设备编号的个数;参数 name 是和该编号范围关联的设备名称。
对于一个新的驱动程序,笔者强烈建议不要随意选择一个当前没有被使用的设备号作为 主设备号,而应该 使用动态分 配机制获得主 设备 号。也就是说,建 议读者经常 使用 alloc_chrdev_region 函数而取代 register_chrdev_region 函数。动态分配相对方便且安全,但 是它也有缺点,由于分配的主设备号不能保证始终一致,所以无法预先创建设备节点,对于 设备号变量 major,初始化该变量的值为 MY_MAJOR,MY_MAJOR 的默认值取 0,也就是 选择动态分配。用户可以使用这个默认值或选择某个特定的主设备号,而且既可以在编译前
error = alloc_chrdev_region(&dev, minor_start, num, name);
if (!error) {
major = MAJOR(dev);
driver->minor_start = MINOR(dev);
} } else {
dev = MKDEV(major, minor_start);
error = register_chrdev_region(dev, num, name);
}
if (error < 0) {
kfree(p);
return error;
}
……
到此为止,对字符设备的一些基本知识有了一个大体的介绍,下面让我们进入最为期待 的字符驱动程序的实例开发。