Linux内核驱动之主次编号

字符设备通过文件系统中的名子来存取. 那些名子称为文件系统的特殊文件, 或者设备文件,惯例上它们位于 /dev 目录. 字符驱动的特殊文件由使用 ls -l 的输出的第一列的"c"标识.

ls -l 命令, 你会看到在设备文件项中有 2 个数(由一个逗号分隔)在最后修改日期前面, 这里通常是文件长度出现的地方. 这些数字是给特殊设备的主次设备编号

crw-rw-rw- 1 root  root  1,  3 Apr 11  2002 null 
crw------- 1 root  root  10, 1 Apr 11  2002 psaux   
crw------- 1 root  root  4,  1 Oct 28 03:04 tty1   
crw-rw-rw- 1 root  tty   4, 64 Apr 11  2002 ttys0   
crw-rw---- 1 root  uucp  4, 65 Apr 11  2002 ttyS1   
crw--w---- 1 vcsa  tty   7,  1 Apr 11  2002 vcs1   
crw--w---- 1 vcsa  tty   7,129 Apr 11  2002 vcsa1   
crw-rw-rw- 1 root  root  1,  5 Apr 11  2002 zero 

传统上, 主编号标识设备相连的驱动. 例如, /dev/null 和 /dev/zero 都由驱动 1 来管理, 而虚拟控制台和串口终端都由驱动 4 管理; 同样, vcs1 和 vcsa1 设备都由驱动 7 管理. 现代 Linux 内核允许多个驱动共享主编号, 但是你看到的大部分设备仍然按照一个主编号一个驱动的原则来组织.

次编号被内核用来决定引用哪个设备. 依据你的驱动是如何编写的(如同我们下面见到的), 你可以从内核得到一个你的设备的直接指针, 或者可以自己使用次编号作为本地设备数组的索引. 不论哪个方法, 内核自己几乎不知道次编号的任何事情, 除了它们指向你的驱动实现的设备.

设备编号的内部表示

在内核中, dev_t 类型(在 中定义)用来持有设备编号 -- 主次部分都包括. 对于 2.6.0 内核, dev_t 是 32 位的量, 12 位用作主编号, 20 位用作次编号.

为获得一个 dev_t 的主或者次编号, 使用:

MAJOR(dev_t dev);  MINOR(dev_t dev);

相反, 如果你有主次编号, 需要将其转换为一个 dev_t, 使用:

MKDEV(int major, int minor);

分配和释放设备编号

在建立一个字符驱动时你的驱动需要做的第一件事是获取一个或多个设备编号来使用. 为此目的的必要的函数是 register_chrdev_region, 在 中声明:

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 内核开发社团中一直努力使用动态分配设备编号. 内核会乐于动态为你分配一个主编号, 但是你必须使用一个不同的函数来请求这个分配.

int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);

使用这个函数, dev 是一个只输出的参数, 它在函数成功完成时持有你的分配范围的第一个数. fisetminor 应当是请求的第一个要用的次编号; 它常常是 0. count 和 name 参数如同给 request_chrdev_region 的一样.

不管你任何分配你的设备编号, 你应当在不再使用它们时释放它. 设备编号的释放使用:

void unregister_chrdev_region(dev_t first, unsigned int count);

调用 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 的源码中获取主编号的代码:


点击(此处)折叠或打开

  1. if (scull_major) {
  2.  dev = MKDEV(scull_major, scull_minor);
  3.  result = register_chrdev_region(dev, scull_nr_devs, "scull");
  4. } else {
  5.  result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs, "scull");
  6.  scull_major = MAJOR(dev);
  7. }
  8. if (result < 0) {
  9.  printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
  10.  return result;
  11. }

所有例子驱动使用类似的代码来分配它们的主编号.

点击(此处)折叠或打开

  1. /**
  2.  * 注册设备号
  3.  * Lzy     2012\7\24
  4.  */

  5. #include <linux/init.h>
  6. #include <linux/module.h>
  7. #include <linux/kernel.h>
  8. #include <linux/fs.h>

  9. #include "hello.h"

  10. int mem_major = MEMDEV_MAJOR;    /* 定义主设备号 */
  11. module_param(mem_major, int, S_IRUGO); /* 主设备号设置为模块参数 */
  12. int mem_minor = MEMDEV_MINOR;    /* 定义次设备号 */
  13. module_param(mem_minor, int, S_IRUGO); /* 次设备号设置为模块参数 */


  14. static int mem_init(void)
  15. {
  16.     int ret;
  17.     dev_t mem_dev;    /* 声明设备号 */
  18.     
  19.     if(mem_major)
  20.     {
  21.         mem_dev = MKDEV(mem_major,mem_minor); /* 获得设备号 */
  22.         ret = register_chrdev_region(mem_dev, MEMDEV_NR_DEVS, MEMDEV_NAME);    /* 静态注册设备号 */
  23.     }
  24.     else
  25.     {
  26.         ret = alloc_chrdev_region(&mem_dev, mem_minor, MEMDEV_NR_DEVS, MEMDEV_NAME); /* 动态注册设备号 */
  27.         mem_major = MAJOR(mem_dev); /* 获得主设备号 */
  28.     }    

  29.     if(ret < 0)    /* 判断设备号注册是否成功 */
  30.     {
  31.         printk(KERN_WARNING "scull: can't get major %d\n", mem_major);
  32.         return ret;
  33.     }

  34.     printk("succeed mem major %d\n",mem_major);
  35.     
  36.     return ret;
  37.     
  38. }

  39. static void mem_exit(void)
  40. {
  41.     unregister_chrdev_region(MKDEV(mem_major,mem_minor), MEMDEV_NR_DEVS);    /* 注销设备号 */
  42. }


  43. module_init(mem_init);
  44. module_exit(mem_exit);

  45. MODULE_LICENSE("GPL"); /* 模块许可证 */
  46. MODULE_AUTHOR("Lzy");    /* 作者声明 */
  47. MODULE_DESCRIPTION("memdev module"); /* 模块描述 */
  48. MODULE_VERSION("V1.0"); /* 模块版本声明 */
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值