一、引言
很久前接触linux驱动就知道主设备号找驱动,次设备号找设备。这句到底怎么理解呢,如何在驱动中实现呢,在介绍该实现之前先看下内核中主次设备号的管理:
二、Linux内核主次设备号的管理
Linux的设备管理是和文件系统紧密结合的,各种设备都以文件的形式存放在/dev目录下,称为设备文件。应用程序可以打开、关闭和读写这些设备文件,完成对设备的操作,就像操作普通的数据文件一样。为了管理这些设备,系统为设备编了号,每个设备号又分为主设备号和次设备号。主设备号用来区分不同种类的设备,而次设备号用来区分同一类型的多个设备。对于常用设备,Linux有约定俗成的编号,如终端类设备的主设备号是4。
设备号的内部表示
在内核中,dev_t 类型( 在 <linux/types.h>头文件有定义 ) 用来表示设备号,包括主设备号和次设备号两部分。对于 2.6.x内核,dev_t是个32位量,其中高12位用来表示主设备号,低20位用来表示次设备号。
在 linux/types.h 头文件里定义有
typedef __kernel_dev_t dev_t;
typedef __u32 __kernel_dev_t;
主设备号和次设备号的获取
为了写出可移植的驱动程序,不能假定主设备号和次设备号的位数。不同的机型中,主设备号和次设备号的位数可能是不同的。应该使用MAJOR宏得到主设备号,使用MINOR宏来得到次设备号。下面是两个宏的定义:(linux/kdev_t.h)
#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)相或,得到设备号
三、主设备号找驱动、次设备号找设备的内核实现
Linux内核允许多个驱动共享一个主设备号,但更多的设备都遵循一个驱动对一个主设备号的原则。
内核维护着一个以主设备号为key的全局哈希表,而哈希表中数据部分则为与该主设备号设备对应的驱动程序(只有一个次设备)的指针或者多个同类设备驱动程序组成的数组的指针(设备共享主设备号)。根据所编写的驱动程序,可以从内核那里得到一个直接指向设备驱动的指针,或者使用次设备号作为索引的数组来找到设备驱动程序。但无论哪种方式,内核自身几乎不知道次设备号的什么事情。如下图所示:
//***************************************************************************************************************//
原文地址:http://blog.chinaunix.net/uid-24219701-id-3284809.html
字符设备通过文件系统中的名子来存取. 那些名子称为文件系统的特殊文件, 或者设备文件,惯例上它们位于 /dev 目录. 字符驱动的特殊文件由使用 ls -l 的输出的第一列的"c"标识.
ls -l 命令,你会看到在设备文件项中有 2 个数(由一个逗号分隔)在最后修改日期前面,这里通常是文件长度出现的地方. 这些数字是给特殊设备的主次设备编号
传统上, 主编号标识设备相连的驱动. 例如, /dev/null 和 /dev/zero 都由驱动 1 来管理, 而虚拟控制台和串口终端都由驱动 4 管理; 同样, vcs1 和 vcsa1 设备都由驱动 7 管理.现代 Linux 内核允许多个驱动共享主编号, 但是你看到的大部分设备仍然按照一个主编号一个驱动的原则来组织.
次编号被内核用来决定引用哪个设备. 依据你的驱动是如何编写的(如同我们下面见到的), 你可以从内核得到一个你的设备的直接指针, 或者可以自己使用次编号作为本地设备数组的索引. 不论哪个方法, 内核自己几乎不知道次编号的任何事情, 除了它们指向你的驱动实现的设备.
设备编号的内部表示
在内核中, dev_t 类型(在 <linux/types.h>中定义)用来持有设备编号 -- 主次部分都包括. 对于 2.6.0 内核, dev_t 是 32 位的量, 12 位用作主编号, 20 位用作次编号.
为获得一个 dev_t 的主或者次编号, 使用:
相反, 如果你有主次编号, 需要将其转换为一个 dev_t, 使用:
分配和释放设备编号
在建立一个字符驱动时你的驱动需要做的第一件事是获取一个或多个设备编号来使用. 为此目的的必要的函数是 register_chrdev_region, 在 <linux/fs.h>中声明:
int register_chrdev_region(dev_t first, unsigned int count, char *name);
这里, first 是你要分配的起始设备编号. first 的次编号部分常常是 0, 但是没有要求是那个效果. count 是你请求的连续设备编号的总数. 最后, name 是应当连接到这个编号范围的设备的名子; 它会出现在 /proc/devices 和 sysfs 中.
如同大部分内核函数, 如果分配成功进行, register_chrdev_region 的返回值是 0. 出错的情况下, 返回一个负的错误码, 你不能存取请求的区域.
在 Linux 内核开发社团中一直努力使用动态分配设备编号. 内核会乐于动态为你分配一个主编号, 但是你必须使用一个不同的函数来请求这个分配.
使用这个函数, dev 是一个只输出的参数, 它在函数成功完成时持有你的分配范围的第一个数. fisetminor 应当是请求的第一个要用的次编号; 它常常是 0. count 和 name 参数如同给 request_chrdev_region 的一样.
不管你任何分配你的设备编号, 你应当在不再使用它们时释放它. 设备编号的释放使用:
调用 unregister_chrdev_region 的地方常常是你的模块的 cleanup 函数.
主编号的动态分配
对于新驱动, 我们强烈建议你使用动态分配来获取你的主设备编号, 而不是随机选取一个当前空闲的编号. 换句话说, 你的驱动应当几乎肯定地使用alloc_chrdev_region, 不是 register_chrdev_region.
动态分配的缺点是你无法提前创建设备节点, 因为分配给你的模块的主编号会变化. 对于驱动的正常使用, 这不是问题, 因为一旦编号分配了,你可从 /proc/devices 中读取它.[6]
安排主编号最好的方式, 是缺省使用动态分配, 而留给自己在加载时指定主编号的选项权, 或者甚至在编译时. scull 实现以这种方式工作; 它使用一个全局变量, scull_major, 来持有选定的编号(还有一个 scull_minor 给次编号). 这个变量初始化为 SCULL_MAJOR, 定义在 scull.h. 发布的源码中的 SCULL_MAJOR 的缺省值是 0, 意思是"使用动态分配". 用户可以接受缺省值或者选择一个特殊主编号, 或者在编译前修改宏定义或者在 insmod 命令行指定一个值给 scull_major.
这是我们用在 scull 的源码中获取主编号的代码:
点击(此处)折叠或打开
- if (scull_major) {
- dev = MKDEV(scull_major, scull_minor);
- result = register_chrdev_region(dev, scull_nr_devs,"scull");
- } else {
- result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,"scull");
- scull_major = MAJOR(dev);
- }
- if (result< 0) {
- printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
- return result;
- }
所有例子驱动使用类似的代码来分配它们的主编号.
点击(此处)折叠或打开
- /**
- * 注册设备号
- * Lzy 2012\7\24
- */
- #include <linux/init.h>
- #include <linux/module.h>
- #include <linux/kernel.h>
- #include <linux/fs.h>
- #include "hello.h"
- int mem_major = MEMDEV_MAJOR; /* 定义主设备号*/
- module_param(mem_major,int, S_IRUGO);/* 主设备号设置为模块参数*/
- int mem_minor = MEMDEV_MINOR; /* 定义次设备号*/
- module_param(mem_minor,int, S_IRUGO);/* 次设备号设置为模块参数*/
- static int mem_init(void)
- {
- int ret;
- dev_t mem_dev; /* 声明设备号*/
-
- if(mem_major)
- {
- mem_dev = MKDEV(mem_major,mem_minor);/* 获得设备号 */
- ret = register_chrdev_region(mem_dev, MEMDEV_NR_DEVS, MEMDEV_NAME); /* 静态注册设备号 */
- }
- else
- {
- ret = alloc_chrdev_region(&mem_dev, mem_minor, MEMDEV_NR_DEVS, MEMDEV_NAME);/* 动态注册设备号 */
- mem_major = MAJOR(mem_dev);/* 获得主设备号 */
- }
- if(ret< 0) /* 判断设备号注册是否成功*/
- {
- printk(KERN_WARNING "scull: can't get major %d\n", mem_major);
- return ret;
- }
- printk("succeed mem major %d\n",mem_major);
-
- return ret;
-
- }
- static void mem_exit(void)
- {
- unregister_chrdev_region(MKDEV(mem_major,mem_minor), MEMDEV_NR_DEVS); /* 注销设备号 */
- }
- module_init(mem_init);
- module_exit(mem_exit);
- MODULE_LICENSE("GPL");/* 模块许可证 */
- MODULE_AUTHOR("Lzy"); /* 作者声明*/
- MODULE_DESCRIPTION("memdev module");/* 模块描述 */
- MODULE_VERSION("V1.0");/* 模块版本声明 */