字符设备通过文件系统中的名子来存取. 那些名子称为文件系统的特殊文件, 或者设备文件,惯例上它们位于 /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"); /* 模块版本声明 */

Linux内核驱动之主次编号相关推荐

  1. Linux内核驱动 --ioctl函数解析

    1.前言 当我们在讨论linux内核驱动开发时,就不得不提到ioctl这个及其重要的函数.它是字符类设备驱动程序中实现对设备控制的接口之一. ioctl是设备驱动程序中对设备的I/O通道进行管理的函数 ...

  2. vmlinux 反汇编_ARM Linux内核驱动异常定位方法分析--反汇编方式

    通常认为,产生异常的地址是lr寄存器的值,从上面的异常信息可以看到[lr]的值是c01a4e30. 接下来,我们可以通过内核镜像文件反汇编来找到这个地址.内核编译完成后,会在内核代码根目录下生成vml ...

  3. linux内核创建字符节点,Tiny6410学习ing—(四)、嵌入式Linux内核驱动进阶—(7)、高级字符设备驱动(自动创建节点)—#931...

    按照国嵌的视频教程上来说的,最后就是-自动创建设备文件! 其实我感觉以前完全可以直接是手动创建了设备文件,然后就可以直接讲述自动创建设备文件,为啥非要拖到最后来讲述,我也就不清楚了!! 不管了,写完收 ...

  4. linux内核下网络驱动流程,基于Linux内核驱动的网络带宽测速方法与流程

    本发明涉及一种测速方法,尤其是一种网络带宽测速方法. 背景技术: :电信运营商为客户提供一定带宽的Internet接入:为了检验带宽是否达标,一般均由客户使用个人电脑在网页上直接测速.但是随着智能网关 ...

  5. linux内核添加spi驱动,Linux内核驱动之spi子系统spi协议.docx

    Linux内核驱动之spi子系统spi协议 概况 SPI接口是摩托罗拉首先提出的全双工三线同步串行外围接口SCK,MOSI,MISO,采用主从模式(Master Slave)架构:支持多slave模式 ...

  6. 树莓派基于Linux内核驱动开发详解

    一.驱动认知 首先理解Linux内核框图 文件系统认知,Linux内核框图 1.什么是驱动 linux内核驱动.软件层面上的驱动 广义上是指:这一段代码操作了硬件去动,所以这一段代码就叫硬件的驱动程序 ...

  7. Linux内核驱动之efi-rtc

    Linux内核驱动之efi-rtc 1. UEFI与BIOS概述 1.1. BIOS 概述 1.1.1. BIOS缺点: 1.1.2. BIOS的启动流程 1.2 UEFI 概述 1.2.1 Boot ...

  8. 从入门到精通ARM(4412)-Linux内核驱动编程【下】-李志勇-专题视频课程

    从入门到精通ARM(4412)-Linux内核驱动编程[下]-247人已学习 课程介绍         嵌入式绝对是当前IT领域最炙手可热的话题了.其主要应用领域涵盖与人类相关的各行各业: * 消费电 ...

  9. 嵌入式 Linux 内核驱动开发【The first day: 36093万字】

    嵌入式 Linux 内核驱动开发[1] 嵌入式 Linux 内核驱动开发前言 第1章 Linux 内核裁剪和定制 [1]Linux 内核开发简介 [2] Linux 源码阅读工具 [1.2.1]Sou ...

  10. 树莓派Linux内核编译、文件系统、Linux内核驱动基础框架、驱动测试步骤、总线地址

    树莓派高阶开发课程 1. ubuntu18.04版本安装          让程序猿搭建环境太搞笑了,轻松easy! ========================================= ...

最新文章

  1. IBM Tivoli Netview在企业网络管理中的实践(附视频)
  2. [HNOI2015]接水果
  3. 企业所得税汇算清缴系统服务器,【图解】汇算清缴风险控制本周五发布,操作流程这里全有!...
  4. [TCP/IP] ping traceroute和TTL
  5. android界面布局错位,IOS 浏览器页面布局错位(如:点不到)的分析与解决
  6. 腾讯视频客户端导出MP4格式
  7. PCA和线性回归之间的关系如何?
  8. [转]javascript 判断某页面上的表单数据是否改变过
  9. cmd windows 命令sleep_最实在的9个黑客命令!确定不学习下?
  10. Expert 诊断优化系列------------------你的CPU高么?
  11. 本地环境测试二级域名
  12. 在VIM中使用GDB调试 – 使用pyclewn
  13. windows安装和使用git工具
  14. Flutter 自定义CheckBox (用于兴趣爱好、风格选择)
  15. 基于multisim14的函数信号发生器仿真
  16. LaTeX2021 公式编写、图文安装、详细教程、一文读懂
  17. [一起来做动图吧]Animate制作简单动图,包教包会,不会举报
  18. 目前网络环境介绍目前网络环境
  19. 下列python语句的输出结果是_下列Python语句的输出结果是
  20. php后台跨域token,JSON Web Token(JWT)目前最流行的跨域身份验证解决方案(PHP)类...

热门文章

  1. C#中的常量、类型推断和作用域
  2. 性能测试基本功 - Centos5.5下安装LAMP
  3. 各种数组元素复制方式的性能比较
  4. 分析器错误 分析器错误信息: 类型“Websystem.Global”不明确: 它可能来自程序集...的解决...
  5. Oracle Database Documentary Library
  6. Netty4.0学习笔记系列之四:混合使用coder和handler
  7. SAP 生产订单变更管理 OCM Order Changement Management
  8. PHP排序算法之快速排序
  9. 电脑管理器地址栏 按右键会有的功能
  10. remove Host from VMware cluster