In continuation of the previous text:第三章:字符设备驱动-1:Major and Minor Numbers, let's GO ahead.
The Internal Representation of Device Numbers
Within the kernel, the dev_t type (defined in <linux/types.h>) is used to hold device numbers—both the major and minor parts. As of Version 2.6.0 of the kernel, dev_t is a 32-bit quantity with 12 bits set aside for the major number and 20 for the minor number. Your code should, of course, never make any assumptions about the internal organization of device numbers; it should, instead, make use of a set of macros found in <linux/kdev_t.h>. To obtain the major or minor parts of a dev_t, use:
在内核中,dev_t 类型(定义于 <linux/types.h>)用于存储设备号 —— 包括主设备号和次设备号两部分。从内核 2.6.0 版本开始,dev_t 是一个 32 位的数值,其中 12 位用于主设备号,20 位用于次设备号。
当然,你的代码绝对不应假设设备号的内部结构,而应使用 <linux/kdev_t.h> 中定义的一组宏。要从 dev_t 中提取主设备号或次设备号,可使用:
MAJOR(dev_t dev);
MINOR(dev_t dev);
If, instead, you have the major and minor numbers and need to turn them into a dev_t, use:
反之,若你已有主设备号和次设备号,需要将它们组合为 dev_t 类型,可使用:
MKDEV(int major, int minor);
Note that the 2.6 kernel can accommodate a vast number of devices, while previous kernel versions were limited to 255 major and 255 minor numbers. One assumes that the wider range will be sufficient for quite some time, but the computing field is littered with erroneous assumptions of that nature. So you should expect that the format of dev_t could change again in the future; if you write your drivers carefully, however, these changes will not be a problem.
需要注意的是,2.6 内核可支持数量极多的设备,而早期内核版本仅能支持 255 个主设备号和 255 个次设备号。人们曾认为这种更宽的范围在相当长的时间内足够使用,但计算机领域中此类 “想当然” 的假设往往会被打破。因此,你应预期 dev_t 的格式未来可能再次改变;不过,只要你的驱动编写得当,这些变化就不会造成问题。
补充说明:
-
dev_t类型的跨版本兼容性-
早期内核(2.4 及之前):
dev_t为 16 位,主设备号占 8 位(最大 255),次设备号占 8 位(最大 255); -
现代内核(2.6 及之后):
dev_t扩展为 32 位,主设备号 12 位(最大 4095),次设备号 20 位(最大 1,048,575),支持更多设备实例; -
驱动适配关键:始终使用
MAJOR()、MINOR()、MKDEV()宏操作设备号,而非直接通过位运算(如dev >> 8)提取,可自动适配dev_t格式变化。
-
-
设备号范围的实际意义
-
主设备号 4095 个:足够分配给所有类型的设备驱动(如字符设备、块设备各占一部分);
-
次设备号 100 多万个:单个驱动可管理大量设备实例(如 USB 驱动可支持数千个设备,每个设备对应一个次设备号)。
-
-
宏的实现原理(简化版)这些宏的核心是通过位运算组合或拆分
dev_t,以 32 位dev_t为例:#define MAJOR(dev) ((dev) >> 20) // 右移 20 位提取主设备号(高 12 位) #define MINOR(dev) ((dev) & 0xfffff) // 与运算提取次设备号(低 20 位) #define MKDEV(m, i) (((m) << 20) | ((i) & 0xfffff)) // 主设备号左移 20 位,与次设备号组合内核通过宏封装屏蔽了位运算细节,确保驱动代码与
dev_t内部格式解耦。
Allocating and Freeing Device Numbers
One of the first things your driver will need to do when setting up a char device is to obtain one or more device numbers to work with. The necessary function for this task is register_chrdev_region, which is declared in <linux/fs.h>:
字符设备驱动在初始化阶段的首要任务之一,就是获取一个或多个用于操作的设备号。完成该任务的核心函数是 register_chrdev_region,它声明于 <linux/fs.h> 中,函数原型如下:
int register_chrdev_region(dev_t first, unsigned int count, char *name);
Here, first is the beginning device number of the range you would like to allocate. The minor number portion of first is often 0, but there is no requirement to that effect. count is the total number of contiguous device numbers you are requesting. Note that, if count is large, the range you request could spill over to the next major number; but everything will still work properly as long as the number range you request is available. Finally, name is the name of the device that should be associated with this number range; it will appear in /proc/devices and sysfs.
As with most kernel functions, the return value from register_chrdev_region will be 0 if the allocation was successfully performed. In case of error, a negative error code will be returned, and you will not have access to the requested region.
其中:
-
first:待分配设备号范围的起始值,其包含的次设备号通常从 0 开始,但并非强制要求。 -
count:请求分配的连续设备号总数。需注意,若count数值较大,请求的范围可能会跨到下一个主设备号 —— 但只要该范围未被占用,分配仍能正常完成。 -
name:与该设备号范围关联的设备名称,会显示在/proc/devices和sysfs中(用于标识设备类型)。
和大多数内核函数一样,register_chrdev_region 成功分配时返回 0;若失败,则返回负的错误码,此时你无法使用请求的设备号范围。
register_chrdev_region works well if you know ahead of time exactly which device numbers you want. Often, however, you will not know which major numbers your device will use; there is a constant effort within the Linux kernel development community to move over to the use of dynamicly-allocated device numbers. The kernel will happily allocate a major number for you on the fly, but you must request this allocation by using a different function:
register_chrdev_region 适用于提前明确知道所需设备号的场景。但在实际开发中,你往往无法确定设备会使用哪个主设备号 ——Linux 内核开发社区一直在推动使用动态分配设备号的方式。内核会自动为你分配一个未占用的主设备号,不过你需要通过另一个函数发起请求,函数原型如下:
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,
unsigned int count, char *name);
With this function, dev is an output-only parameter that will, on successful completion, hold the first number in your allocated range. firstminor should be the requested first minor number to use; it is usually 0. The count and name parameters work like those given to request_chrdev_region.
Regardless of how you allocate your device numbers, you should free them when they are no longer in use. Device numbers are freed with:
该函数的参数:
-
dev:仅用于输出的参数,分配成功后,会存储分配到的设备号范围的起始值。 -
firstminor:请求使用的起始次设备号,通常设为 0。 -
count和name:作用与register_chrdev_region中的同名参数一致。
无论采用哪种方式分配设备号,当不再使用时,都必须将其释放。释放设备号的函数如下:
void unregister_chrdev_region(dev_t first, unsigned int count);
The usual place to call unregister_chrdev_region would be in your module’s cleanup function.
The above functions allocate device numbers for your driver’s use, but they do not tell the kernel anything about what you will actually do with those numbers. Before a user-space program can access one of those device numbers, your driver needs to connect them to its internal functions that implement the device’s operations. We will describe how this connection is accomplished shortly, but there are a couple of necessary digressions to take care of first.
调用 unregister_chrdev_region 的常规位置是模块的清理函数,确保模块卸载时能归还设备号,避免资源泄漏。
需要注意的是,上述函数仅为驱动分配设备号,并不会告知内核 “这些设备号将用于实现什么功能”。在用户态程序能访问这些设备号之前,驱动还需将设备号与 “实现设备操作的内部函数” 关联起来。我们很快会介绍这种关联的实现方式,但在此之前,需要先补充几个必要的知识点。
技术交流,欢迎加入社区:GPUers。
9686

被折叠的 条评论
为什么被折叠?



