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);
相关文章:
Linux驱动设备号分配与自动创建设备节点
Linux 驱动设备号 对于 Linux 系统,为了识别和管理设备,每个设备便使用一个唯一的编号来标记设备,每个注册到内核的设备都需要一个编号,这个编号就是设备号,为了细分设备号分为主设备号和次设备号。 由于 Linux 的设…...
基于MFC和OpenCV实现人脸识别
基于MFC和OpenCV实现人脸识别 文章目录 基于MFC和OpenCV实现人脸识别1. 项目说明1. 创建项目2. 启动窗口3. 登录窗口-添加窗口、从启动窗口跳转4. 启动窗口-美化按钮5. 登录窗口-美化按钮、雪花视频6. 注册窗口-美化按钮、雪花视频、从启动窗口跳转7. 注册窗口-开启摄像头8. 注…...
力扣 -- 377. 组合总和 Ⅳ
解题步骤: 参考代码: class Solution { public:int combinationSum4(vector<int>& nums, int target) {int nnums.size();vector<double> dp(target1);//初始化dp[0]1;//填表for(int i1;i<target;i){for(int j0;j<n;j){//填表if(…...
阿里云新账户什么意思?老用户、产品首购详细说明
阿里云新账户、老账号、产品首购和同人账号什么意思?阿里云账号分为云新账户、老账户、产品首购、同人账号和同一用户,阿里云官方推出的活动很多是限制账号类型的,常见的如阿里云新用户,什么是阿里云新用户?是指从未在…...
C++ YAML使用
C++工程如何使用YAML-cpp 一、前期准备工作 1、已安装minGW、cmake、make等本地工具。 2、下载YAML-cpp第三方开源代码(一定要下载最新的release版本,不然坑很多)。 3、生成YAML-cpp静态库 (1)在yaml-cpp-master下建立build文件夹; (2)在该文件夹下生成MakaFile文…...
十二、Django之模板的继承+用户列表
模板的继承 新建layout.html: {% load static %} <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><link rel"stylesheet" href"{% static plugins…...
wzsc_文件上传(条件竞争)
打开题目链接,很常见的文件上传框 经过尝试,发现上传东西后会调用upload.php,猜测文件被传到upload目录下 随便传了几个类型的文件,访问upload目录 发现.php文件以及.htaccess、.user.ini这种配置文件都没有传上去 但是通过抓包…...
unplugin-vue-components和unplugin-auto-import插件
unplugin-auto-import:自动按需引入 vue\vue-router\pinia 等的 api unplugin-vue-components:自动按需引入 第三方的组件库组件 和 我们自定义的组件 使用此类插件,不需要手动编写import {xxx} from vue这样的代码了,提升开发效…...
docker系列文章目录
docker系列专栏笔记总算完成了,平时下班比较晚,利用晚上的一些时间整理了这一系列的学习笔记。 docker系列教程包含以下几个方面: docker环境篇 介绍docker环境的搭建,已经管理平台工具(portainer)的简单使用。 docker常用命令篇…...
第80步 时间序列建模实战:GRNN回归建模
基于WIN10的64位系统演示 一、写在前面 这一期,我们使用Matlab进行GRNN模型的构建。 使用的数据如下: 采用《PLoS One》2015年一篇题目为《Comparison of Two Hybrid Models for Forecasting the Incidence of Hemorrhagic Fever with Renal Syndrom…...
《C和指针》笔记33:指针数组
除了创建整型数组一样,也可以声明指针数组。 int *api[10];为了弄清这个复杂的声明,我们假定它是一个表达式,并对它进行求值。下标引用的优先级高于间接访问,所以在这个表达式中,首先执行下标引用。因此,a…...
C/C++字符函数和字符串函数详解————内存函数详解与模拟
个人主页:点我进入主页 专栏分类:C语言初阶 C语言程序设计————KTV C语言小游戏 C语言进阶 C语言刷题 欢迎大家点赞,评论,收藏。 一起努力,一起奔赴大厂。 目录 1.前言 2 .memcpy函数 3.memmove函…...
CAcUiDockControlBar初始位置 2023/8/19 下午3:51:18
2023/8/19 下午3:51:18 CAcUiDockControlBar初始位置 2023/8/19 下午3:52:00 CAcUiDockControlBar的初始位置是根据其在程序代码中的设置而确定的。通常情况下,它的初始位置可以通过以下几种方式进行设置: 使用Create函数:在创建CAcUiDockControlBar对象时,可以调用Cre…...
CDH6.3.2 的pyspark读取excel表格数据写入hive中的问题汇总
需求:内网通过Excel文件将数据同步到外网的CDH服务器中,将CDH中的文件数据写入hive中。 CDH版本为:6.3.2 spark版本为:2.4 python版本:2.7.5 操作系统:CentOS Linux 7 集群方式:yarn-cluster …...
2120 -- 预警系统题解
Description OiersOiers 国的预警系统是一棵树,树中有 �n 个结点,编号 1∼�1∼n,树中每条边的长度均为 11。预警系统中只有一个预警信号发射站,就是树的根结点 11 号结点,其它 �−1…...
C++入门-day01
一、认识C C融合了三种不同的编程方式 C代表的过程性语言在C基础上添加的类、结构体puls代表的面向对象语言C模板支持泛型编程 C完全兼容C的特性 Tips:侯捷老师提倡的Modren C是指C11、C14、C17和C20这些新标准所引入的一系列新特性和改进。在我们练习的时候也应当去…...
Android开源 Skeleton 骨架屏 V1.3.0
目录 一、简介 二、效果图 三、引用 Skeleton 添加jitpack 仓库 添加依赖: 四、新增 “块”骨架屏 1、bind方法更改和变化: 2、load方法更改和变化: 五、关于上一个版本 一、简介 骨架屏的作用是在网络请求较慢时,提供基础占位&…...
网络资料搬运(2)
(1) Ubuntu 22.04: 为 Ubuntu22.04 系统添加中文输入法 linux解压gz文件的命令 Ubuntu20.04出现Unit ssh.service could not be found 详解使用SSH远程连接Ubuntu服务器系统 Configuring networks(配置网络) (2) Python && OpenCV: …...
SEO搜索引擎
利用搜索引擎的规则提高网站在有关搜索引擎内的自然排名,吸引更多的用户访问网站,提高网站的访问量,提高网站的销售能力和宣传能力,从而提升网站的品牌效应 搜索引擎优化的技术手段 黑帽SEO 通过欺骗技术和滥用搜索算法来推销毫不…...
动态规划-状态机(188. 买卖股票的最佳时机 IV)
状态分类: f[i,j,0]考虑前i只股票,进行了j笔交易,目前未持有股票 所能获得最大利润 f[i,j,1]考虑前i只股票,进行了j笔交易,目前持有股票 所能获得最大利润 状态转移: f[i][j][0] Math.max(f[i-1][j][0],f[…...
超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
visual studio 2022更改主题为深色
visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中,选择 环境 -> 常规 ,将其中的颜色主题改成深色 点击确定,更改完成...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
Ascend NPU上适配Step-Audio模型
1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统,支持多语言对话(如 中文,英文,日语),语音情感(如 开心,悲伤)&#x…...
springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
华为OD机考-机房布局
import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseSystem.out.println(solve(in.nextLine()));}}priv…...
08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险
C#入门系列【类的基本概念】:开启编程世界的奇妙冒险 嘿,各位编程小白探险家!欢迎来到 C# 的奇幻大陆!今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类!别害怕,跟着我,保准让你轻松搞…...
C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...
