当前位置: 首页 > news >正文

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 系统&#xff0c;为了识别和管理设备&#xff0c;每个设备便使用一个唯一的编号来标记设备&#xff0c;每个注册到内核的设备都需要一个编号&#xff0c;这个编号就是设备号&#xff0c;为了细分设备号分为主设备号和次设备号。 由于 Linux 的设…...

基于MFC和OpenCV实现人脸识别

基于MFC和OpenCV实现人脸识别 文章目录 基于MFC和OpenCV实现人脸识别1. 项目说明1. 创建项目2. 启动窗口3. 登录窗口-添加窗口、从启动窗口跳转4. 启动窗口-美化按钮5. 登录窗口-美化按钮、雪花视频6. 注册窗口-美化按钮、雪花视频、从启动窗口跳转7. 注册窗口-开启摄像头8. 注…...

力扣 -- 377. 组合总和 Ⅳ

解题步骤&#xff1a; 参考代码&#xff1a; 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(…...

阿里云新账户什么意思?老用户、产品首购详细说明

阿里云新账户、老账号、产品首购和同人账号什么意思&#xff1f;阿里云账号分为云新账户、老账户、产品首购、同人账号和同一用户&#xff0c;阿里云官方推出的活动很多是限制账号类型的&#xff0c;常见的如阿里云新用户&#xff0c;什么是阿里云新用户&#xff1f;是指从未在…...

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&#xff1a; {% load static %} <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><link rel"stylesheet" href"{% static plugins…...

wzsc_文件上传(条件竞争)

打开题目链接&#xff0c;很常见的文件上传框 经过尝试&#xff0c;发现上传东西后会调用upload.php&#xff0c;猜测文件被传到upload目录下 随便传了几个类型的文件&#xff0c;访问upload目录 发现.php文件以及.htaccess、.user.ini这种配置文件都没有传上去 但是通过抓包…...

unplugin-vue-components和unplugin-auto-import插件

unplugin-auto-import&#xff1a;自动按需引入 vue\vue-router\pinia 等的 api unplugin-vue-components&#xff1a;自动按需引入 第三方的组件库组件 和 我们自定义的组件 使用此类插件&#xff0c;不需要手动编写import {xxx} from vue这样的代码了&#xff0c;提升开发效…...

docker系列文章目录

docker系列专栏笔记总算完成了&#xff0c;平时下班比较晚&#xff0c;利用晚上的一些时间整理了这一系列的学习笔记。 docker系列教程包含以下几个方面&#xff1a; docker环境篇 介绍docker环境的搭建&#xff0c;已经管理平台工具(portainer)的简单使用。 docker常用命令篇…...

第80步 时间序列建模实战:GRNN回归建模

基于WIN10的64位系统演示 一、写在前面 这一期&#xff0c;我们使用Matlab进行GRNN模型的构建。 使用的数据如下&#xff1a; 采用《PLoS One》2015年一篇题目为《Comparison of Two Hybrid Models for Forecasting the Incidence of Hemorrhagic Fever with Renal Syndrom…...

《C和指针》笔记33:指针数组

除了创建整型数组一样&#xff0c;也可以声明指针数组。 int *api[10];为了弄清这个复杂的声明&#xff0c;我们假定它是一个表达式&#xff0c;并对它进行求值。下标引用的优先级高于间接访问&#xff0c;所以在这个表达式中&#xff0c;首先执行下标引用。因此&#xff0c;a…...

C/C++字符函数和字符串函数详解————内存函数详解与模拟

个人主页&#xff1a;点我进入主页 专栏分类&#xff1a;C语言初阶 C语言程序设计————KTV C语言小游戏 C语言进阶 C语言刷题 欢迎大家点赞&#xff0c;评论&#xff0c;收藏。 一起努力&#xff0c;一起奔赴大厂。 目录 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中的问题汇总

需求&#xff1a;内网通过Excel文件将数据同步到外网的CDH服务器中&#xff0c;将CDH中的文件数据写入hive中。 CDH版本为&#xff1a;6.3.2 spark版本为&#xff1a;2.4 python版本&#xff1a;2.7.5 操作系统&#xff1a;CentOS Linux 7 集群方式&#xff1a;yarn-cluster …...

2120 -- 预警系统题解

Description OiersOiers 国的预警系统是一棵树&#xff0c;树中有 &#xfffd;n 个结点&#xff0c;编号 1∼&#xfffd;1∼n&#xff0c;树中每条边的长度均为 11。预警系统中只有一个预警信号发射站&#xff0c;就是树的根结点 11 号结点&#xff0c;其它 &#xfffd;−1…...

C++入门-day01

一、认识C C融合了三种不同的编程方式 C代表的过程性语言在C基础上添加的类、结构体puls代表的面向对象语言C模板支持泛型编程 C完全兼容C的特性 Tips&#xff1a;侯捷老师提倡的Modren C是指C11、C14、C17和C20这些新标准所引入的一系列新特性和改进。在我们练习的时候也应当去…...

Android开源 Skeleton 骨架屏 V1.3.0

目录 一、简介 二、效果图 三、引用 Skeleton 添加jitpack 仓库 添加依赖: 四、新增 “块”骨架屏 1、bind方法更改和变化&#xff1a; 2、load方法更改和变化&#xff1a; 五、关于上一个版本 一、简介 骨架屏的作用是在网络请求较慢时&#xff0c;提供基础占位&…...

网络资料搬运(2)

(1) Ubuntu 22.04&#xff1a; 为 Ubuntu22.04 系统添加中文输入法 linux解压gz文件的命令 Ubuntu20.04出现Unit ssh.service could not be found 详解使用SSH远程连接Ubuntu服务器系统 Configuring networks&#xff08;配置网络&#xff09; (2) Python && OpenCV: …...

SEO搜索引擎

利用搜索引擎的规则提高网站在有关搜索引擎内的自然排名&#xff0c;吸引更多的用户访问网站&#xff0c;提高网站的访问量&#xff0c;提高网站的销售能力和宣传能力&#xff0c;从而提升网站的品牌效应 搜索引擎优化的技术手段 黑帽SEO 通过欺骗技术和滥用搜索算法来推销毫不…...

动态规划-状态机(188. 买卖股票的最佳时机 IV)

状态分类&#xff1a; f[i,j,0]考虑前i只股票&#xff0c;进行了j笔交易&#xff0c;目前未持有股票 所能获得最大利润 f[i,j,1]考虑前i只股票&#xff0c;进行了j笔交易&#xff0c;目前持有股票 所能获得最大利润 状态转移&#xff1a; f[i][j][0] Math.max(f[i-1][j][0],f[…...

React Native 导航系统实战(React Navigation)

导航系统实战&#xff08;React Navigation&#xff09; React Navigation 是 React Native 应用中最常用的导航库之一&#xff0c;它提供了多种导航模式&#xff0c;如堆栈导航&#xff08;Stack Navigator&#xff09;、标签导航&#xff08;Tab Navigator&#xff09;和抽屉…...

基于Uniapp开发HarmonyOS 5.0旅游应用技术实践

一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架&#xff0c;支持"一次开发&#xff0c;多端部署"&#xff0c;可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务&#xff0c;为旅游应用带来&#xf…...

spring:实例工厂方法获取bean

spring处理使用静态工厂方法获取bean实例&#xff0c;也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下&#xff1a; 定义实例工厂类&#xff08;Java代码&#xff09;&#xff0c;定义实例工厂&#xff08;xml&#xff09;&#xff0c;定义调用实例工厂&#xff…...

大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计

随着大语言模型&#xff08;LLM&#xff09;参数规模的增长&#xff0c;推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长&#xff0c;而KV缓存的内存消耗可能高达数十GB&#xff08;例如Llama2-7B处理100K token时需50GB内存&a…...

虚拟电厂发展三大趋势:市场化、技术主导、车网互联

市场化&#xff1a;从政策驱动到多元盈利 政策全面赋能 2025年4月&#xff0c;国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》&#xff0c;首次明确虚拟电厂为“独立市场主体”&#xff0c;提出硬性目标&#xff1a;2027年全国调节能力≥2000万千瓦&#xff0…...

JavaScript 数据类型详解

JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型&#xff08;Primitive&#xff09; 和 对象类型&#xff08;Object&#xff09; 两大类&#xff0c;共 8 种&#xff08;ES11&#xff09;&#xff1a; 一、原始类型&#xff08;7种&#xff09; 1. undefined 定…...

解决:Android studio 编译后报错\app\src\main\cpp\CMakeLists.txt‘ to exist

现象&#xff1a; android studio报错&#xff1a; [CXX1409] D:\GitLab\xxxxx\app.cxx\Debug\3f3w4y1i\arm64-v8a\android_gradle_build.json : expected buildFiles file ‘D:\GitLab\xxxxx\app\src\main\cpp\CMakeLists.txt’ to exist 解决&#xff1a; 不要动CMakeLists.…...

Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析

Java求职者面试指南&#xff1a;Spring、Spring Boot、Spring MVC与MyBatis技术解析 一、第一轮基础概念问题 1. Spring框架的核心容器是什么&#xff1f;它的作用是什么&#xff1f; Spring框架的核心容器是IoC&#xff08;控制反转&#xff09;容器。它的主要作用是管理对…...

ubuntu22.04有线网络无法连接,图标也没了

今天突然无法有线网络无法连接任何设备&#xff0c;并且图标都没了 错误案例 往上一顿搜索&#xff0c;试了很多博客都不行&#xff0c;比如 Ubuntu22.04右上角网络图标消失 最后解决的办法 下载网卡驱动&#xff0c;重新安装 操作步骤 查看自己网卡的型号 lspci | gre…...

32单片机——基本定时器

STM32F103有众多的定时器&#xff0c;其中包括2个基本定时器&#xff08;TIM6和TIM7&#xff09;、4个通用定时器&#xff08;TIM2~TIM5&#xff09;、2个高级控制定时器&#xff08;TIM1和TIM8&#xff09;&#xff0c;这些定时器彼此完全独立&#xff0c;不共享任何资源 1、定…...