一个字符设备或者块设备都有一个主设备号和次设备号。主设备号和次设备号统称为设
备号。主设备号用来表示一个特定的驱动程序。次设备号用来表示使用该驱动程序的各
设备。例如一个嵌入式系统,有两个LED指示灯,LED灯需要独立的打开或者关闭。那么
,可以写一个LED灯的字符设备驱动程序,可以将其主设备号注册成5号设备,次设备号
分别为1和2。这里,次设备号就分别表示两个LED灯。
1.主设备号和次设备号的表示
在Linux内核中,dev_t类型用来表示设备号。在Linux 2.6.29.4中,dev_t定义为一个
无符号长整型变量,如下:typedef u_long dev_t;
u_long在32位机中是4个字节,在64位机中是8字节。以32位机为例,其中高12表示主设备号,低20为表示次设备号,如图6.1所示。
2.主设备号和次设备号的获取
为了写出可移植的驱动程序,不能假定主设备号和次设备号的位数。不同的机型中,主
设备号和次设备号的位数可能是不同的。应该使用MAJOR宏得到主设备号,使用MINOR宏来得到次设备号。下面是两个宏的定义:
#define MINORBITS 20 /*次设备号位数*/
#define MINORMASK ((1U << MINORBITS) - 1) /*次设备号掩码*/
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
/*dev右移20位得到主设备号*/
#define MINOR(dev)((unsigned int) ((dev) & MINORMASK))
/*与次设备掩码与,得到次设备号*/
MAJOR宏将dev_t向右移动20位,得到主设备号;MINOR宏将dev_t的高12位清零,得到次
设备号。相反,可以将主设备号和次设备号转换为设备号类型(dev_t),使用宏
MKDEV可以完成这个功能。#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
MKDEV宏将主设备号(ma)左移20位,然后与次设备号(mi)相与,得到设备号。
3.静态分配设备号
静态分配设备号,就是驱动程序开发者,静态地指定一个设备号。对于一部分常用的设
备,内核开发者已经为其分配了设备号。这些设备号可以在内核源码documentation/
devices.txt文件中找到。如果只有开发者自己使用这些设备驱动程序,那么其可以选
择一个尚未使用的设备号。在不添加新硬件的时候,这种方式不会产生设备号冲突。但
是当添加新硬件时,则很可能造成设备号冲突,影响设备的使用。
4.动态分配设备号
由于静态分配设备号存在冲突的问题,所以内核社区建议开发者使用动态分配设备号的
方法。动态分配设备号的函数是alloc_chrdev_region()。
5.查看设备号
当静态分配设备号时,需要查看系统中已经存在的设备号,从而决定使用哪个新设备号
。可以读取/proc/devices文件获得设备的设备号。/proc/devices文件包含字符设备和
块设备的设备号,如下所示。[root@tom /]# cat /proc/devices /*cat命令查看
/proc/devices文件的内容*/ Character devices: /*字符设备*/
1 mem 4 /dev/vc/0 7 vcs 13 input 14 sound 21 sg Block
devices: /*块设备*/ 1 ramdisk 2 fd 8 sd 253 device-mapper 254 mdp
3. 6.1.3 申请和释放设备号
内核维护着一个特殊的数据结构,用来存放设备号与设备的关系。在安装设备时,应该
给设备申请一个设备号,使系统可以明确设备对应的设备号。设备驱动程序中的很多功
能,是通过设备号来操作设备的。下面,首先对申请设备号进行简述。
1.申请设备号
在构建字符设备之前,首先要向系统申请一个或者多个设备号。完成该工作的函数是
register_chrdev_region(),该函数在<fs/char_dev.c>中定义:
int register_chrdev_region(dev_t from, unsigned count, const char *name);
其中,from是要分配的设备号范围的起始值。一般只提供from的主设备号,from的次设
备号通常被设置成0。count是需要申请的连续设备号的个数。最后name是和该范围编号
关联的设备名称,该名称不能超过64字节。
和大多数内核函数一样,register_chrdev_region()函数成功时返回0。错误时,返回
一个负的错误码,并且不能为字符设备分配设备号。下面是一个例子代码,其申请了
CS5535_GPIO_COUNT个设备号。retval = register_chrdev_region(dev_id,
CS5535_GPIO_COUNT,NAME);
在Linux中有非常多的字符设备,在人为的为字符设备分配设备号时,很可能发生冲突
。Linux内核开发者一直在努力将设备号变为动态的。可以使用
alloc_chrdev_region()函数达到这个目的。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor,unsigned count, const char *name)
在上面的函数中,dev作为输出参数,在函数成功返回后将保存已经分配的设备号。函
数有可能申请一段连续的设备号,这是dev返回第一个设备号。baseminor表示要申请的
第一个次设备号,其通常设为0。count和name与register_chrdev_region()函数的对应
参数一样。count表示要申请的连续设备号个数,name表示设备的名字。下面是一个例
子代码,其申请了CS5535_GPIO_COUNT个设备号。retval =
alloc_chrdev_region(&dev_id, 0, CS5535_GPIO_COUNT, NAME);
2.释放设备号
使用上面两种方式申请的设备号,都应该在不使用设备时,释放设备号。设备号的释放
统一使用下面的函数:void unregister_chrdev_region(dev_t from, unsignedcount);
在上面这个函数中,from表示要释放的设备号,count表示从from开始要释放的设备号
个数。通常,在模块的卸载函数中调用unregister_chrdev_region()函数。
分配并注册主次设备号 设备号是在驱动module中分配并注册的,也就是说,驱动module拥有这个设备号(我的理解),而/dev目录下的设备文件是根据这个设备号创建的,因此,当访问/dev目录下的设备文件时,驱动module就知道,自己该出场服务了(当然是由内核通知)。 在Linux内核看来,主设备号标识设备对应的驱动程序,告诉Linux内核使用哪一个驱动程序为该设备(也就是/dev下的设备文件)服务;而次设备号则用来标识具体且唯一的某个设备。 在内核中,用dev_t类型(其实就是一个32位的无符号整数)的变量来保存设备的主次设备号,其中高12位表示主设备号,低20位表示次设备号。 设备获得主次设备号有两种方式:一种是手动给定一个32位数,并将它与设备联系起来(即用某个函数注册);另一种是调用系统函数给设备动态分配一个主次设备号。
对于手动给定一个主次设备号,使用以下函数:int register_chrdev_region(dev_t first, unsigned int -count, char *name) 其中first是我们手动给定的设备号,count是所请求的连续设备号的个数,而name是和该设备号范围关联的设备名称,它将出现在/proc/devices和sysfs中。 比如,若first为0x3FFFF0,count为0x5,那么该函数就会为5个设备注册设备号,分别是0x3FFFF0、 0x3FFFF1、 0x3FFFF2、 0x3FFFF3、 0x3FFFF4,其中0x3(高12位)为这5个设备所共有的主设备号(也就是说这5个设备都使用同一个驱动程序)。而0xFFFF0、 0xFFFF1、 0xFFFF2、 0xFFFF3、 0xFFFF4就分别是这5个设备的次设备号了。需要注意的是,若count的值太大了,那么所请求的设备号范围可能会和下一个主设备号重叠。比如若 first还是为0x3FFFF0,而count为0x11,那么first+count=0x400001,也就是说为最后两个设备分配的主设备号已经不是0x3,而是0x4了!用这种方法注册设备号有一个缺点,那就是若该驱动module被其他人广泛使用,那么无法保证注册的设备号是其他人的 Linux系统中未分配使用的设备号。
对于动态分配设备号,使用以下函数:int alloc_chrdev_region(dev_t *dev, unsigned int -firstminor, unsigned int -count, char *name) 该函数需要传递给它指定的第一个次设备号firstminor(一般为0)和要分配的设备数count,以及设备名,调用该函数后自动分配得到的设备号保存在dev中。动态分配设备号可以避免手动指定设备号时带来的缺点,但是它却也有自己的缺点,那就是无法预先在/dev下创建设备节点,因为动态分配设备号不能保证在每次加载驱动module时始终一致(其实若在两次加载同一个驱动module之间并没有加载其他的module,那么自动分配的设备号还是一致的,因为内核分配设备号并不是随机的,但是书上说某些内核开发人员预示不久的将来会用随机方式进行处理),不过,这个缺点可以避免,因为在加载驱动module后,我们可以读取/proc/devices文件以获得Linux内核分配给该设备的主设备号。
与主次设备号相关的3个宏:MAJOR(dev_t dev):根据设备号dev获得主设备号;MINOR(dev_t dev):根据设备号dev获得次设备号;MKDEV(int major, int minor):根据主设备号major和次设备号minor构建设备号。另解: Linux的设备管理是和文件系统紧密结合的,把设备和文件关联起来,这样系统调用可以直接用操作文件一样的方法来操作设备。各种设备都以文件的形式存放在/dev目录下,称为设备文件。应用程序可以打开、关闭和读写这些设备文件,完成对设备的操作,就像操作普通的数据文件一样。为了管理这些设备,系统为设备编了号,每个设备号又分为主设备号和次设备号。主设备号用来区分不同种类的设备,而次设备号用来区分同一类型的多个设备。对于常用设备,Linux有约定俗成的编号,如硬盘的主设备号是3。 |