什么是设备号?
linux中设备号是用来标记一类设备以及区分这类设备中具体个体的一组号码,由主设备号和次设备号组成,主设备号用来标记设备的类型,次设备号用来区分在这类设备中具体的个体设备。
为什么用设备号?
我们知道"linux下一切皆文件",linux系统将设备抽象成设备文件节点,操作设备就像操作文件一样,而为了唯一识别和管理设备,便使用设备号来标记设备,这样设备文件关联主、次设备号便可关联设备的操作方法(驱动)。
这些设备中,有些设备是对实际存在的物理硬件的抽象,而有些设备则是内核自身提供的功能(不依赖于特定的物理硬件,又称为"虚拟设备")。查看主设备号使用cat /proc/devices命令,查看主、次设备号使用ll /dev/ 。
设备号的组成
Linux系统中,使用dev_t类型来标识一个设备号,他是一个32位的无符号整数:
<include linux/types.h>
typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;
其中,高12位是主设备号,低20位是次设备号,如下图:
内核为了保证在主次设备号位宽发生变化时,现在的程序依然可以工作,内核提供了如下的几个宏:
<include /linux/kdev_t.h>
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
MAJOR宏是用来从一个dev_t 类型的设备号中提取出主设备号,MINOR用来提取次设备号。MKDEV则是将主设备号ma和次设备号mi合成一个dev_t类型的设备号。实现原理都是通过位操作。
字符设备号管理
chrdevs来管理字符设备号(cdev_map管理字符设备),它的定义如下:
<fs/char_dev.c>
static struct char_device_struct {
struct char_device_struct *next;
unsigned int major;//主设备号
unsigned int baseminor;//第一个次设备号
int minorct;//次设备的个数
char name[64];//驱动名
struct cdev *cdev; /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
//#define CHRDEV_MAJOR_HASH_SIZE 255
从上面可以知道chrdevs数组只有255项,这是不是意味着只能有255个主设备号?当然不是,内核是通过哈希表的形式来索引 chrdevs 的,简单的来说就是major %255,这意味着设备号257与设备号2处于同一表项位置,但是它们并不冲突,如下图:
它们的次设备号可以重叠,但如果主设备号相同,那次设备号就不能重叠。
字符设备号分配
字符设备号分配有两个函数,register_chrdev_region,alloc_chrdev_region。
(1)register_chrdev_region(dev_t from, unsigned count, const char *name)
该函数第一个参数from表示是一个设备号,第二个参数count是连续设备编号的个数,代表当前驱动所管理的同类设备的个数,第三个参数name表示设备或者驱动的名称。
此函数用于静态注册设备号,优点是可以在注册的时候就知道其设备号,缺点是可能会与系统中已经注册的设备号冲突导致注册失败。
(2)alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
该函数的第一个参数*dev是返回的设备号,第二个参数baseminor是次设备号起始号,第三个参数count是连续设备编号的个数,第四个参数name表示设备或者驱动的名称。
该函数是由系统协助动态分配设备号,分配的主设备号的范围在1-254之间,系统会从chrdevs数组尾部(也就是第254项)依次往前找未使用的主设备号。
字符设备号释放
设备号作为一种系统资源,当所对应的设备驱动程序被卸载时,就应该把设备号归还给系统,以便分配给其他内核模块使用。无论是静态分配还是动态分配,都是通过调用unregister_chrdev_region函数释放设备号。
unregister_chrdev_region(dev_t from, unsigned count)
函数在chrdevs数组中查找参数from和count所对应的struct char_device_struct 对象节点,找到以后将其从链表中删除并释放该节点所占用的内存,从而将对应的设备号释放以供其他设备驱动模块使用。