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[…...
银行业务队列简单模拟(队列应用)
设某银行有A、B两个业务窗口,且处理业务的速度不一样,其中A窗口处理速度是B窗口的2倍 —— 即当A窗口每处理完2个顾客时,B窗口处理完1个顾客。给定到达银行的顾客序列,请按业务完成的顺序输出顾客序列。假定不考虑顾客先后到达的时…...
2023/8/8 下午10:42:04 objectarx
2023/8/8 下午10:42:04 objectarx 2023/8/8 下午10:42:16 ObjectARX(AutoCAD Runtime Extension)是用于开发和自定义AutoCAD软件的编程接口。ObjectARX允许开发者使用C++、.NET等编程语言来创建插件、扩展功能和定制化AutoCAD的行为。 通过ObjectARX,开发者可以访问Auto…...
Day-06 基于 Docker安装 Nginx 镜像
1.去官方公有仓库查询nginx镜像 docker search nginx 2.拉取该镜像 docker pull nginx 3. 启动镜像,使用nginx服务,代理本机8080端口(测试是不是好使) docker run -d -p 8080:80 --name nginx-8080 nginx docker ps curl 127.0.0.1:8080...
linux入门---信号的保存和捕捉
目录标题 信号的一些概念信号的保存pending表block表handler表 信号的捕捉内核态和用户态信号的捕捉 信号的一些概念 1.进程会收到各种各样的信号,那么程序对该信号进行实际处理的动作叫做信号的递达。 2.我们之前说过当进程收到信号的时候可能并不会立即处理这个信…...
5.外部中断
中断初始化配置步骤: IO口初始化配置 开启中断总允许EA 打开某个IO口的中断允许 打开IO口的某一位的中断允许 配置该位的中断触发方式 中断函数: #pragma vector PxINT_VECTOR __interrupt void 函数名(void){}#pragma vector PxINT_VECTOR __int…...
Mydb数据库问题
1、请简要介绍一下这个基于 Java 的简易数据库管理系统。它的主要功能是什么? TM(Transaction Manager):事务管理器,用于维护事务的状态,并提供接口供其他模块查询某个事务的状态。DM(Data Man…...
部署并应用ByteTrack实现目标跟踪
尽管YOLOv8已经集成了ByteTrack算法,但在这里我还是想利用ByteTrack官网的代码,自己实现目标跟踪。 要想应用ByteTrack算法,首先就要从ByteTrack官网上下载并安装。虽然官网上介绍得很简单,只需要区区6行代码,但对于国…...
MacOS怎么配置JDK环境变量
1 输入命令看是否配置了JDk 的环境变量:echo $JAVA_HOME 要是什么也没输出 证明是没配置 2 输入命令编辑 sudo vim ~/.bash_profile 然后按 i ,进入编辑模式,粘贴下面的代码,注意:JAVA_HOME后面路径需要改成自己的版…...
Spring Boot 开发16个实用的技巧
当涉及到使用Spring Boot开发应用程序时,以下是16个实用的技巧: 1. **使用Spring Initializr**:Spring Initializr是一个快速创建Spring Boot项目的工具,可以帮助您选择项目依赖和生成项目骨架。 2. **自动配置**:Sp…...
《机器学习实战》学习记录-ch2
PS: 个人笔记,建议不看 原书资料:https://github.com/ageron/handson-ml2 2.1数据获取 import pandas as pd data pd.read_csv(r"C:\Users\cyan\Desktop\AI\ML\handson-ml2\datasets\housing\housing.csv")data.head() data.info()<clas…...
中牟网站建设/网址域名注册
ESG使用指南:1.ESG操作文档网站:ESG有个网站,是专门的操作文档网站,因为ESG三个环境,流程各不一样。地址:http://10.20.12.90:20567/esg-help-doc/2.ESG管理平台网站,分别管理开发,测…...
免费软件下载网站排行/济南做网站比较好的公司
加上引号就好了👏 def foo(value: str|int|float|list|tuple):print(value)foo(None)...
如何在自己建设的网站上发表文章/长尾关键词有哪些
vueconf(2018hangzhou)大会刚刚过去,vue作者尤大大向我们展示了vue3.0的进展,并介绍vue3.0的一些改动,其中最令我期待的就是重写数据监听机制。 回顾vue2.x的双向数据绑定 谈起vue的双向数据绑定,我们首先能想到的就是ES5中Obje…...
品牌理念设计企业网站建设/客户引流的最快方法是什么
Python版本: 3.6.0 一、添加Media Types Name:自定义的媒介名称 Type:Scipt 脚本 Scipt name : 脚本名称 Scipt parameters : 传递给脚本的参数。(该参数可以在后面的Actions部分自定义)。这里我们就使…...
最好看的免费网站源码/网站排名软件
2019独角兽企业重金招聘Python工程师标准>>> 本文前戏较多,务实的同学可以直接跳到文末的结论。 由「钢的琴」网友脑洞大开延伸出了吉的他二的胡琵的琶,以及后来许嵩的「苏格拉没有底」,是否可以再拓展一下,得到哥本不…...
哪个网站是做包装材料珍珠棉包管/免费直链平台
问题描述:本人的项目是用Maven管理,而且用到了servlet3.0的技术,但是项目中用到servlet3.0的地方,总提示找不到类中的方法。很奇怪,在网上找到好多解决办法,综合一下终于解决了。现将经验分享给大家。 前提…...