Linux驱动设备号分配与自动创建设备节点

Linux 驱动设备号

对于 Linux 系统,为了识别和管理设备,每个设备便使用一个唯一的编号来标记设备,每个注册到内核的设备都需要一个编号,这个编号就是设备号,为了细分设备号分为主设备号和次设备号。

由于 Linux 的设备管理是和文件系统紧密结合的,各种设备都以文件的形式存放在 /dev 目录下,所以我们查看文件的详细信息就可以看到设备的设备号。

crw-rw---- 1 root uucp 4, 70 04-14 18:16 ttyS6
crw-rw---- 1 root uucp 4, 71 04-14 18:16 ttyS7
crw-rw---- 1 root tty 7, 0 08-08 18:58 vcs
crw-rw---- 1 root tty 7, 1 08-08 18:58 vcs1

可以看到设备文件权限不再像普通文件那样为 rwx 了,而是变成了 crw 第一个字符为 c 的表示字符设备。同时多了两个数字并且使用逗号隔开,这两个数字对应的就是设备的主设备号和次设备号,如上 4,7 分别是主设备号,70,71,0,1 都是次设备号。

主设备号用来区分不同种类的设备,而次设备号用来区分同一类型的多个设备(主设备号用来标记设备的类型,次设备号用来区分在这类设备中具体的个体设备)。

1. 设备号表示

主设备号用来区分不同种类的设备,而次设备号用来区分同一类型的多个设备,设备号在 Linux 内核内部表示被定义为 u32 类型的一个数值,最终使用的就是 dev_t 这个类型,如下(在内核源码 include/linux/types.h 中)。

typedef u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;

而 u32 在 Linux 内核源码中被定义为 unsigned int,如下。

typedef unsigned int __u32;
typedef __u32 u32;

所以 dev_t 本质属于 unsigned int 类型(即 32 位的数据类型),dev_t 类型为了可以同时表达主设备号和次设备号,用 32 位的数据高 12 位表示主设备号,低 20 位为次设备号。所以主设备号最多可以有 2^12=4096 个(0-4095),次设备号最多可以有 2^20=1048576 个(0-1048575)。

                          dev_t 32 bit
-------------------------------------------------------------------------
| 31 .. MAJOR ... 20 | 19 ................. MINOR ................... 0 |
-------------------------------------------------------------------------

主设备号较少使用时不能超过 4095 而次设备号一般可以随意使用,次设备号一般足够使用,虽然这样但还是要做好驱动设备号的分配,不要随意浪费使用。

2. 设备号操作宏

主设备号和次设备号共同保存在一个 32 位变量中,为了方便提取或设置主/次设备号相应的位就提供了一些宏定义,这些宏定义在编写设备驱动时会用到,如下。

#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))

MINORBITS 表示 20 位次设备号,MINORMASK 用于分离次设备号的掩码,MAJOR()用于从 dev_t 中获取主设备号,MINOR() 用于从 dev_t 中获取次设备号,MKDEV() 用于将主设备号和次设备号组合成 dev_t 类型的设备号。

3. 分配设备号

设备号分配,类似 IP 地址可以静态分配也可以动态分配,静态分配 IP 就是由我们自己指定一个 IP 地址,但是不能用已经被其他设备使用的 IP,动态 IP 分配就由路由器给我们分配一个未被使用的 IP,驱动设备号分配也是这样的规则,动态分配就是向 Linux 内核申请一个设备号。

静态分配设备号要注意不能用已经被其他设备使用的设备号,所以实际上我们一般使用动态设备号分配。

4. 静态分配

(1) 要静态分配设备号很简单只需要在编写驱动时指定一个主设备号以及一个次设备号,然后使用设备号操作宏 MKDEV() 构造出一个设备号,最后调用内核提供的注册接口 register_chrdev_region() 即可将构造出的设备号注册给驱动,如下。

dev_t dev_id;
dev_id = MKDEV(200, 0);
register_chrdev_region(dev_id, 1, "DRIVER_NAME");

注意静态分配时不能用已经被其他设备使用的主设备号和次设备号,因为这样会构造出和其他设备相同的设备号,这是不允许的。

(2) 还有一种静态分配设备设备号的方法是只提供一个主设备号即可,例如使用字符设备注册函数 register_chrdev 注册设备时,如下。

file_operations dev_fops;
register_chrdev(200, "DRIVER_NAME", &dev_fops);

但是这种有个大问题,会将一个主设备号下的所有次设备号都使用掉,比如设置 LED 这个主设备号为 200,那么 0-1048575 这个区间的次设备号会全部都被 LED 一个设备占用(这样太浪费次设备号!一个 LED 设备肯定只能有一个主设备号,一个次设备号),为什么呢?看代码就知道了。

找到 register_chrdev() 函数的定义,发现调用了 __register_chrdev() 函数,并且形参传递了我们静态指定的主设备号,强制指定次设备号为 0,以及设备号注册数量为 256 个。

static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
{return __register_chrdev(major, 0, 256, name, fops);
}

再看 __register_chrdev() 函数的定义,调用了 __register_chrdev_region() 函数去根据我们设置的静态主设备号,根据强制指定的次设备号 0 和注册数量 256,来强制注册 256 个设备号,如下。

int __register_chrdev(unsigned int major, unsigned int baseminor,unsigned int count, const char *name,const struct file_operations *fops)
{struct char_device_struct *cd;struct cdev *cdev;int err = -ENOMEM;cd = __register_chrdev_region(major, baseminor, count, name);if (IS_ERR(cd))return PTR_ERR(cd);...err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);...return err;
}

通过阅读上方代码就可以知道为什么这种静态分配方式会将一个主设备号下的所有次设备号都使用掉了。

5. 动态分配

静态分配设备号需要我们事先去 Linux 根文件系统中查看设备文件的属性确定好哪些设备号没有使用,再选择一个设备号注册给我们的驱动。

解决这个问题最好的方法就是在使用设备号的时候向 Linux 内核申请,需要几个就申请几个,由 Linux 内核给你的驱动分配可以使用的设备号。

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

dev 用于接收申请到的设备号,baseminor 指定次设备号,count 指定要申请的设备号个数。

dev_t dev_id;
alloc_chrdev_region(&dev_id, 0, 1, "DRIVER_NAME");

申请到设备号之后使用设备号操作宏 MAJOR() 和 MINOR() 从设备号中分离出主设备号和次设备号用于其他用途。

int major = MAJOR(dev_id);
int minor = MINOR(dev_id);

6. 设备节点文件

在 Linux 系统中应用程序通过访问设备节点文件来访问设备(驱动)的,访问设备节点文件的操作如何映射到具体的驱动呢?答案就是我们给设备分配的设备号。

由于我们给驱动分配了设备号,所以只要将设备节点文件映射到设备号就相当于映射到了驱动,这就是我们给驱动分配设备号的作用。

7. 手动创建设备节点

如何将设备节点文件映射到对应的设备号呢?第一种办法是在使用 mknod 命令手动创建设备节点文件时把设备号作为参数传递给 mknod 命令,如下。

mknod /dev/leddrv c 200 0

这里设备节点文件名为 “/dev/leddrv”,主设备号为 200,次设备号为 0,这样设备节点文件就映射到设备号相关的驱动了,最终应用程序就可以通过设备节点文件访问到相应驱动了。

fd = open("/dev/leddrv", O_RDWR);

8. 自动创建设备节点

手动创建设备节点有一个条件是要求我们知道具体的设备号,并且在创建设备节点时将设备号作为参数传递给设备节点文件。

设备号采用静态分配时这没有问题,但是设备号采用动态分配(向内核申请)时我们无法事先知道具体的设备号。此时处于不确定的设备映射状态,特别是那些动态设备,比如 USB 设备,设备节点文件到实际设备(驱动)的映射并不确定。

此时就需要设备(驱动)在初始化时利用分配到的设备号自行创建设备节点文件。

8.1 认识 udev 和 mdev

udev 是一个用户程序,在 Linux 通过 udev 可实现设备文件的创建与删除,udev 可以检测系统中硬件设备状态,并根据硬件设备状态来创建或者删除设备文件。

比如使用 modprobe 命令成功加载驱动模块后 udev 就自动在 rootFS 的 /dev 目录下创建对应的设备节点文件,使用 rmmod 命令卸载驱动模块后就自动删除 /dev 目录下对应的设备节点文件,而 mdev 是 udev 的简化版本,用于少资源的嵌入式平台。

如果需要使用自动创建设备节点这个功能需要在内核 menuconfig 中把 mdev 打开。

8.2 创建和删除类

自动创建设备节点的工作是在驱动程序的入口函数中完成的,一般在 cdev_add 函数后添加自动创建设备节点相关代码。

第一步,使用 class_create 函数创建一个 class 类。

struct class * _class =  class_create(THIS_MODULE, "DRIVER_NAME");

在文件 include/linux/device.h 中可以看到 class_create 的定义,可以看到是一个宏定义函数,如下。

extern struct class * __must_check __class_create(struct module*owner, const char *name, struct lock_class_key *key);#define class_create(owner, name) \
({ \static struct lock_class_key __key; \__class_create(owner, name, &__key); \
})

形参 owner 一般为 THIS_MODULE,name 是类名字,返回值是个指向结构体 class 的指针,也就是创建的类。

卸载驱动程序时需要同时使用 class_destroy() 函数删除掉类 ,参数 cls 指定要删除的类,函数定义如下。

void class_destroy(struct class *cls);

8.3 创建设备

第二步,类创建完成后还需要在这个类下创建一个设备,创建设备使用 device_create 函数。

struct device * _device = device_create(_class, NULL, dev_id, NULL, "DRIVER_NAME"");

在文件 include/linux/device.h 中可以看到 device_create 的定义,可以看到是一个可变参数函数,如下。

struct device *device_create(const struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...);

形参 class 指定在哪个类创建设备,parent 是父设备,一般为 NULL(即没有父设备)。devt 是设备号(动态分配的设备号),drvdata 指定设备可能会使用的私有数据,一般为 NULL。fmt 指定设备节点文件名称(设置 fmt=xxx 的话就会生成 /dev/xxx 这个设备节点文件)。返回值就是创建好的设备。

卸载驱动程序时需要同时使用 device_destroy() 函数删除设备 ,函数定义如下。参数 class 是要删除的设备所处的类,参数 devt 指定要删除的设备号。

void device_destroy(const struct class *class, dev_t devt);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/94498.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Java-LangChain:使用 ChatGPT API 搭建系统-6】处理输入-链式 Prompt Chaining Prompts

第六章&#xff0c;处理输入-链式 Prompt Chaining Prompts 在本章中&#xff0c;我们将学习如何通过将复杂任务拆分为一系列简单的子任务来链接多个 Prompt。 您可能会想&#xff0c;为什么要将任务拆分为多个 Prompt&#xff0c;而不是像我们在上一个视频中学习的那样&…

嵌入式数据库sqlite3基本命令操作基础(05)

前言 数据在实际工作中应用非常广泛&#xff0c;数据库的产品也比较多&#xff0c;oracle、DB2、SQL2000、mySQL&#xff1b;基于嵌入式linux的数据库主要有SQLite, Firebird, Berkeley DB, eXtremeDB。 本文主要讲解数据库SQLite&#xff0c;通过这个开源的小型的嵌入式数据…

基于阶梯碳交易的含P2G-CCS耦合和燃气掺氢的虚拟电厂优化调度(matlab代码)

目录 1 主要内容 系统结构图 P2G-CCS 耦合模型 其他算例对比 2 部分代码 3 下载链接 1 主要内容 该程序复现《基于阶梯碳交易的含P2G-CCS耦合和燃气掺氢的虚拟电厂优化调度》模型&#xff0c;以碳交易和碳封存成本、燃煤机组启停和煤耗成本、弃风成本、购气成本之和为目标…

CANoe.Diva生成测试用例

Diva目录 一、CANoe.Diva打开CDD文件二、导入CDD文件三、ECU Information四、时间参数设置五、选择是否测试功能寻址六、勾选需要测试服务项七、生成测试用例 一、CANoe.Diva打开CDD文件 CANoe.Diva可以通过导入cdd或odx文件&#xff0c;自动生成全面的测试用例。再在CANoe中导…

简单查找重复文本文件

声明这是最初 我的提问给个文本分类清单input查找文件夹下 .py .txt .excel .word 一模一样的文本不是找文件名 找相同格式下的文件文本是否一样 文件单独复制到文件夹下两个文件全部复制到文件夹下 print 打印相同文本文件的名字 比如查找到了3.py与4.5.是.py文件中的文本文件…

二分查找模版

对于一个递增序列我们要找大于等于target的数&#xff0c;返回结果的下标时 比如 序列 5 7 7 8 8 10 初始化左右指针l0 rn-1 猜测区间 [l,r] 闭区间&#xff0c;mid(lr)/2 防溢出就写成 midl(r-l)/2 如果有nums[mid]<target 那么[l,mid]这个区间的数就都小于target 更新 lmi…

5.Vectors Transformation Rules

在上节&#xff0c;有个问题&#xff1a;向量分量的转换方式 与 新旧基底的转换方式相反 用例子来感受一下&#xff0c; 空间中一向量V&#xff0c;即该空间的一个基底&#xff1a;e1、e2 v e1 e2 现把基底 e1 、 e2 放大两倍。变成 基向量放大了两倍&#xff0c; 但对于…

Javascript 事件的动态绑定

动态绑定事件&#xff0c;是指在代码执行过程中&#xff0c;通过Javascript代码来绑定事件。这种技术可以大大增强网页的交互性和用户体验。上一期介绍的是通过事件监听器 EventListener 去实现元素颜色的变化。这一期将通过动态绑定方法去实现&#xff0c;对象.事件 匿名函数…

【广州华锐互动】鱼类授精繁殖VR虚拟仿真实训系统

随着科技的不断发展&#xff0c;虚拟现实技术在各个领域的应用越来越广泛。在养殖业中&#xff0c;VR技术可以帮助养殖户进行家鱼授精实操演练&#xff0c;提高养殖效率和繁殖成功率。本文将介绍利用VR开展家鱼授精实操演练的方法和应用。 首先&#xff0c;我们需要了解家鱼授精…

mysql双主+双从集群连接模式

架构图&#xff1a; 详细内容参考&#xff1a; 结果展示&#xff1a; 178.119.30.14(主) 178.119.30.15(主) 178.119.30.16(从) 178.119.30.17(从)

Java中的instanceof

Java中的instanceof运算符是一种非常有用的工具&#xff0c;它可以帮助我们在运行时动态地确定对象的类型。通过使用instanceof&#xff0c;我们可以轻松地检查一个对象是否是指定类型&#xff08;或其子类&#xff09;的实例。本文将详细介绍instanceof运算符的使用方法、特点…

【C++】基础入门

万字复习C基础入门语法&#xff0c;适合学过C的朋友用来复习查阅&#xff0c;可能不太适合0基础的朋友。 一.c初识 (1) 第一个c程序 最简单的格式&#xff1a; // 导入头文件 #include<iostream> // 简化对命名空间std下函数和对象的使用 using namespace std; // …

STM32驱动步进电机

前言 &#xff08;1&#xff09;本章介绍用stm32驱动42步进电机&#xff0c;将介绍需要准备的硬件器材、所需芯片资源以及怎么编程及源代码等等。 &#xff08;2&#xff09;实验效果&#xff1a;按下按键&#xff0c;步进电机顺时针或逆时针旋转90度。 &#xff08;3&#xff…

Multisim14.0仿真(二十七)基于UC3842的反激式开关电源的设计及仿真

一、UC3842简介&#xff1a; UC3842为固定频率电流模式PWM控制器。它们是专门为OFF−线和直流到直流转换器应用与最小的外部组件。内部实现的电路包括用于精确占空比控制的修剪振荡器、温度补偿参考、高增益误差放大器、电流传感比较器和理想适合于驱动功率MOSFET的高电流温度极…

openGauss学习笔记-89 openGauss 数据库管理-内存优化表MOT管理-内存表特性-使用MOT-MOT使用查询原生编译

文章目录 openGauss学习笔记-89 openGauss 数据库管理-内存优化表MOT管理-内存表特性-使用MOT-MOT使用查询原生编译89.1 查询编译&#xff1a;PREPARE语句89.2 运行命令89.3 轻量执行支持的查询89.4 轻量执行不支持的查询89.5 JIT存储过程89.6 MOT JIT诊断89.6.1 mot_jit_detai…

调度程序以及调度算法的评价指标

1.调度器/调度程序 调度程序决定调度算法&#xff0c;时间片大小 ②&#xff0c;③由调度程序引起&#xff0c;调度程序决定: 1.调度时机 创建新进程进程退出运行进程阻塞I/O中断发生&#xff08;可能唤醒某些阻塞进程)非抢占式调度策略&#xff0c;只有运行进程阻塞或退出…

强化学习环境 - robogym - 学习 - 1

强化学习环境 - robogym - 学习 - 1 项目地址 https://github.com/openai/robogym 为什么选择 robogym 自己的项目需要做一些机械臂 table-top 级的多任务操作 robogym 基于 mujoco 搭建&#xff0c;构建了一个仿真机械臂桌面物体操作&#xff08;pick-place、stack、rearr…

视频讲解|基于DistFlow潮流的配电网故障重构代码

目录 1 主要内容 2 视频链接 1 主要内容 该视频为基于DistFlow潮流的配电网故障重构代码讲解内容&#xff0c;对应的资源下载链接为基于DistFlow潮流的配电网故障重构(输入任意线路)&#xff0c;对该程序进行了详尽的讲解&#xff0c;基本做到句句分析和讲解&#xff08;讲解…

【题解 动态规划】 Colored Rectangles

题目描述&#xff1a; 分析&#xff1a; 乍一看我还以为是贪心&#xff01; 猫 想想感觉没问题 但是局部最优并不能保证全局最优 比如这组数据 19 19 19 19 20 20 20 20如果按照贪心的做法&#xff0c;答案是20*20*2 但是其实答案是19*20*4 因此这道题用贪心是不对的 于是我…

MATLAB 函数签名器

文章目录 MATLAB 函数签名器注释规范模板参数类型 kind数据格式 type选项的支持 使用可执行程序封装为m函数程序输出 编译待办事项推荐阅读附录 MATLAB 函数签名器 MATLAB 函数签名器 (FUNCSIGN) &#xff0c;在规范注释格式的基础上为函数文件或类文件自动生成函数签名&#…