4、Linux驱动开发:设备-设备号设备号注册
目录
🍅点击这里查看所有博文
随着自己工作的进行,接触到的技术栈也越来越多。给我一个很直观的感受就是,某一项技术/经验在刚开始接触的时候都记得很清楚。往往过了几个月都会忘记的差不多了,只有经常会用到的东西才有可能真正记下来。存在很多在特殊情况下有一点用处的技巧,用的不多的技巧可能一个星期就忘了。
想了很久想通过一些手段把这些事情记录下来。也尝试过在书上记笔记,这也只是一时的,书不在手边的时候那些笔记就和没记一样,不是很方便。
很多时候我们遇到了问题,一般情况下都是选择在搜索引擎检索相关内容,这样来的也更快一点,除非真的找不到才会去选择翻书。后来就想到了写博客,博客作为自己的一个笔记平台倒是挺合适的。随时可以查阅,不用随身携带。
同时由于写博客是对外的,既然是对外的就不能随便写,任何人都可以看到。经验对于我来说那就只是经验而已,公布出来说不一定我的一些经验可以帮助到其他的人。遇到和我相同问题时可以少走一些弯路。
既然决定了要写博客,那就只能认真去写。不管写的好不好,尽力就行。千里之行始于足下,一步一个脚印,慢慢来
,写的多了慢慢也会变好的。权当是记录自己的成长的一个过程,等到以后再往回看时,就会发现自己以前原来这么菜😂。
本系列博客所述资料均来自互联网资料
,并不是本人原创(只有博客是自己写的)。出于热心,本人将自己的所学笔记整理并推出相对应的使用教程,方面其他人学习。为国内的物联网事业发展尽自己的一份绵薄之力,没有为自己谋取私利的想法
。若出现侵权现象,请告知本人,本人会立即停止更新,并删除相应的文章和代码。
设备分类
Linux系统中的设备可以分为字符设备、块设备和网络设备这三类。
字符设备
字符设备是能够像字节流一样被访问的设备,通常不支持随机存取。当对字符设备发出读写请求,相应的IO操作立即发生。
Linux系统中很多设备都是字符设备,我们常用的键盘、串口、I2C、SPI、音频都是字符设备。
在嵌入式Linux开发中,接触最多的就是字符设备以及驱动。
块设备
块设备是Linux系统中进行IO操作时必须以块为单位进行访问的设备,应用程序可以寻址磁盘上的任何位置,并由此读取数据(随机读取)。
块设备驱动会利用一块系统内存作为缓冲区,因此对块设备发出读写访问,并不一定立即产生硬件I/O操作。
块设备能够安装文件系统,Linux系统中常见的块设备有硬盘、EMMC、NAND、SD 卡、闪存等
网络设备
网络设备既可以是网卡这样的硬件设备,也可以是一个纯软件设备如回环设备。
网络设备由Linux的网络子系统驱动,负责数据包的发送和接收,而不是面向流设备,因此在Linux系统文件系统中网络设备没有节点。
对网络设备的访问是通过socket调用产生,而不是普通的文件操作如open/closc和read/write等。
其他
一个设备可以 属于多种设备驱动类型,比如 USB WIFI,其使用 USB 接口,所以属于字符设备。但是其又能上网,所以也属于网络设备驱动。
这些设备中,有些设备是对实际存在的物理硬件的抽象,而有些设备则是内核自身提供的功能,不依赖于特定的物理硬件,又称为虚拟设备。
- /dev/loop[x]
- /dev/random
- /dev/null
- /dev/zero
- /dev/full
内核结构
linux内核结构如下图所示,Linux的设备经由内核统一管理。
其中字符设备和块设备最终都挂载与文件子系统之下。两者都可通过虚拟文件系统找到对应的节设备点,并支持使用文件接口进行访问。
设备号
linux中设备号是用来标记一类设备以及区分这类设备中具体个体的一组号码。
Linux提供了一个名为dev_t
的数据类型表示设备号,dev_t
定义在文件include/linux/types.h
里面,定义如下:
typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;
//include/uapi/asm-generic/int-ll64.h
typedef unsigned int __u32;
dev_t
其实就是unsigned int
类型,是一个32位的数据类型。主设备号(高12位)用来标记设备的类型,次设备号(低20位)用来区分在这类设备中具体的个体设备。不同的主设备号之间,次设备号编码可以出现重复现象。
因此Linux系统中主设备号范围为0~4095,所以大家在选择主设备号的时候一定不要超过这个范围。
内核为了保证在主次设备号位宽发生变化时,现在的程序依然可以工作,内核提供了如下的几个宏,编写设备驱动代码是,最好使用宏来计算dev_t参数,而不要使用常数去计算。
<include /linux/kdev_t.h>
#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))
设备管理
前面说到主设备号(高12位)的范围为0~4095。不代表Linux内核可以同时管理这么多主设备。
linux内核为了方便管理设备,在fs/char_dev.c
中维护了一个char_device_struct
结构的结构体。
/*fs/char_dev.c*/
static struct char_device_struct {struct char_device_struct *next;unsigned int major;unsigned int baseminor;int minorct;char name[64];struct cdev *cdev; /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];/* fs.h */
#define CHRDEV_MAJOR_HASH_SIZE 255
内核代码中chrdevs结构体的每一个成员,都是一个以char_device_struct结构体指针为元素的单项链的头指针。
每条链表中的所有元素拥有相同的主设备号,及该链表所在chrdevs数组的下标。
从上面可以得知结构体的长度为255。内核通过对255取模(major%255)来实现将0~4095个主设备号,分配到255个哈希表项上。
需要注意的是,只有255个哈希表不代表说内核最多只有255个主设备。很多人都会陷入误区,认为最多只有255个主设备,这是错误的。
举个栗子,设备号256与设备号1处于同一表项位置,但是它们并不冲突。也就是说设备号1和设备号256是可以同时存在的。
root@ubuntu:# cat /proc/devices
Character devices:1 mem4 /dev/vc/0
.......
254 gpiochip
256 hello_register_chrdev
字符设备注册
Linux内核中字符设备号的分配有两个函数,分别是手动分配register_chrdev_region还有自动分配alloc_chrdev_region。
register_chrdev_region
此函数用于静态注册设备号,该函数第一个参数from表示是一个设备号,第二个参数count是连续设备编号的个数,代表当前驱动所管理的同类设备的个数,第三个参数name表示设备或者驱动的名称。
/*** register_chrdev_region() - register a range of device numbers* @from: the first in the desired range of device numbers; must include* the major number.* @count: the number of consecutive device numbers required* @name: the name of the device or driver.** Return value is zero on success, a negative error code on failure.*/
int register_chrdev_region(dev_t from, unsigned count, const char *name)
优点是可以在注册的时候就知道其设备号,缺点是可能会与系统中已经注册的设备号冲突导致注册失败。
示例
下方示例中,注册了主设备为256的字符设备。
static int major = 256;
static int minor = 0;
static dev_t register_devno;
static int hello_register_chrdev(void)
{int result;printk("hello_register_chrdev \n");register_devno = MKDEV(major, minor);result = register_chrdev_region(register_devno, 1, "hello_register_chrdev");if (result < 0){printk("register_chrdev_region fail \n");return result;}return result;
}
结果
驱动模块挂载后可看到256号主设备被成功注册,且设备名与预期一致。
root@ubuntu:# cat /proc/devices
Character devices:1 mem4 /dev/vc/0
.......
254 gpiochip
256 hello_register_chrdev
alloc_chrdev_region
该函数的第一个参数*dev是返回的设备号,第二个参数baseminor是次设备号起始号,第三个参数count是连续设备编号的个数,第四个参数name表示设备或者驱动的名称。
/*** alloc_chrdev_region() - register a range of char device numbers* @dev: output parameter for first assigned number* @baseminor: first of the requested range of minor numbers* @count: the number of minor numbers required* @name: the name of the associated device or driver** Allocates a range of char device numbers. The major number will be* chosen dynamically, and returned (along with the first minor number)* in @dev. Returns zero or a negative error code.*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);
该函数是由系统协助动态分配设备号,分配的主设备号的范围在1-254之间,系统会从chrdevs数组尾部(也就是第254项)依次往前找未使用的主设备号。
/* temporary */if (major == 0) {for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {if (chrdevs[i] == NULL)break;}if (i == 0) {ret = -EBUSY;goto out;}major = i;}
示例
使用自动分配注册一个字符设备。
static dev_t alloc_devno;
static int hello_alloc_chrdev(void)
{int result;printk("hello_alloc_chrdev \n");result = alloc_chrdev_region(&alloc_devno, 0, 1, "hello_alloc_chrdev");if (result < 0){printk("alloc_chrdev_region fail \n");return result;}return result;
}
结果
驱动模块挂载后,可看到名字为hello_alloc_chrdev的字符设备被成功注册,设备号为243为自动分配。
root@ubuntu:# cat /proc/devices
226 drm
243 hello_alloc_chrdev
244 hidraw
245 aux
246 bsg
字符设备释放
设备号作为一种系统资源,当所对应的设备驱动程序被卸载时,就应该把设备号归还给系统,以便分配给其他内核模块使用。无论是静态分配还是动态分配,都是通过调用unregister_chrdev_region函数释放设备号。
/*** unregister_chrdev_region() - return a range of device numbers* @from: the first in the range of numbers to unregister* @count: the number of device numbers to unregister** This function will unregister a range of @count device numbers,* starting with @from. The caller should normally be the one who* allocated those numbers in the first place...*/
void unregister_chrdev_region(dev_t from, unsigned count)
函数在chrdevs数组中查找参数from和count所对应的struct char_device_struct 对象节点,找到以后将其从链表中删除并释放该节点所占用的内存,从而将对应的设备号释放以供其他设备驱动模块使用。
示例
销毁前方示例创建的两个字符设备。
static void hello_exit(void)
{printk("hello_exit \n");unregister_chrdev_region(register_devno, 1);unregister_chrdev_region(alloc_devno, 1);
}
结果
可以看到主设备编号为256和243的设备全部被释放。
root@ubuntu:# cat /proc/devices
226 drm
244 hidraw
245 aux
246 bsg
247 hmm_device
248 watchdog
249 rtc
250 dax
251 dimmctl
252 ndctl
253 tpm
254 gpiochip
那么本篇博客就到此结束了,这里只是记录了一些我个人的学习笔记,其中存在大量我自己的理解。文中所述不一定是完全正确的,可能有的地方我自己也理解错了。如果有些错的地方,欢迎大家批评指正。如有问题直接在对应的博客评论区指出即可,不需要私聊我。我们交流的内容留下来也有助于其他人查看,说不一定也有其他人遇到了同样的问题呢😂。
相关文章:

4、Linux驱动开发:设备-设备号设备号注册
目录 🍅点击这里查看所有博文 随着自己工作的进行,接触到的技术栈也越来越多。给我一个很直观的感受就是,某一项技术/经验在刚开始接触的时候都记得很清楚。往往过了几个月都会忘记的差不多了,只有经常会用到的东西才有可能真正记…...
C++(MFC)调用Python
环境: phyton版本:3.10 VS版本:VS2017 包含文件头:Python\Python310\include 包含库文件:Python\Python310\libs 程序运行期间,以下函数只需要调用一次即可,重复调用会导致崩溃 void Initial…...

深度学习实践——循环神经网络实践
系列实验 深度学习实践——卷积神经网络实践:裂缝识别 深度学习实践——循环神经网络实践 深度学习实践——模型部署优化实践 深度学习实践——模型推理优化练习 代码可见于: 深度学习实践——循环神经网络实践 0 概况1 架构实现1.1 RNN架构1.1.1 RNN架…...

docker简单web管理docker.io/uifd/ui-for-docker
要先pull这个镜像docker.io/uifd/ui-for-docker 这个软件默认只能使用9000端口,别的不行,因为作者在镜像制作时已加入这一层 刚下下来镜像可以通过docker history docker.io/uifd/ui-for-docker 查看到这个端口已被 设置 如果在没有设置br0网关时&…...

SpringBoot内嵌的Tomcat:
SpringBoot内嵌Tomcat源码: 1、调用启动类SpringbootdemoApplication中的SpringApplication.run()方法。 SpringBootApplication public class SpringbootdemoApplication {public static void main(String[] args) {SpringApplication.run(SpringbootdemoApplicat…...
企业级docker应用注意事项
现在很多企业使用容器化技术部署应用,绕不开的docker技术,在生产环境docker常用操作总结。参考:https://juejin.cn/post/7259275893796651069 1. 尽可能使用官方镜像 在docker hub 官方 使用后面带有 DOCKER OFFICIAL IMAGE 标签的镜像&…...
腾讯云高性能计算集群CPU服务器处理器说明
腾讯云高性能计算集群以裸金属云服务器为节点,通过RDMA互联,提供了高带宽和极低延迟的网络服务,能满足大规模高性能计算、人工智能、大数据推荐等应用的并行计算需求,腾讯云服务器网分享腾讯云服务器高性能计算集群CPU处理器说明&…...

tinkerCAD案例:23.Tinkercad 中的自定义字体
tinkerCAD案例:23.Tinkercad 中的自定义字体 原文 Tinkercad Projects Tinkercad has a fun shape in the Shape Generators section that allows you to upload your own font in SVG format and use it in your designs. I’ve used it for a variety of desi…...
Box-Cox 变换
Box-cox 变化公式如下: y ( λ ) { y λ − 1 λ λ ≠ 0 l n ( y ) λ 0 y^{(\lambda)}\left\{ \begin{aligned} \frac{y^{\lambda} - 1}{\lambda} && \lambda \ne 0 \\ ln(y) && \lambda 0 \end{aligned} \right. y(λ)⎩ ⎨ ⎧λyλ−1ln…...
Linux wc命令用于统计文件的行数,字符数,字节数
Linux wc命令用于计算字数。 利用wc指令我们可以计算文件的Byte数、字数、或是列数,若不指定文件名称、或是所给予的文件名为"-",则wc指令会从标准输入设备读取数据。 语法 wc [-clw][–help][–version][文件…] 参数: -c或–b…...

Python读取多个栅格文件并提取像元的各波段时间序列数据与变化值
本文介绍基于Python语言,读取文件夹下大量栅格遥感影像文件,并基于给定的一个像元,提取该像元对应的全部遥感影像文件中,指定多个波段的数值;修改其中不在给定范围内的异常值,并计算像元数值在每一景遥感影…...
Linux 之 wget curl
wget 命令 wget是非交互式的文件下载器,可以在命令行内下载网络文件 语法: wget [-b] url 选项: -b ,可选,background 后台下载,会将日志写入到 当前工作目录的wget-log文件 参数 url : 下载链…...
AngularJS 和 React区别
目录 1. 背景:2. 版本:3. 应用场景:4. 语法:5. 优缺点:6. 代码示例: AngularJS 和 React 是两个目前最为流行的前端框架之一。它们有一些共同点,例如都是基于 JavaScript 的开源框架,…...

【Solr】Solr搜索引擎使用
文章目录 一、什么是Solr?二 、数据库本身就支持搜索啊,干嘛还要搞个什么solr?三、如果我们想要使用solr那么首先我们得安装它 一、什么是Solr? 其实我们大多数人都使用过Solr,也许你不会相信我说的这句话,但是事实却是如此啊 ! 每当你想买自己喜欢的东东时,你可能会打开某…...

一起学算法(选择排序篇)
距离上次更新已经很久了,以前都是非常认真的写笔记进行知识分享,但是带来的情况并不是很好,一度认为发博客是没有意义的,但是这几天想了很多,已经失去了当时写博客的初心了,但是我觉得应该做点有意义的事&a…...
智能体的主观和能动
摘要 智能体的主动性是提升智能机器的能力的关键。围绕智能体的主动性存在很多思想迷雾,本文继续我们以前的工作,试图清理这些概念上的问题。我们的讨论显示:要研究主动性,并不一定需要研究意识,仅需要研究主观和能动就…...

AB 压力测试
服务器配置 阿里云Ubuntu 64位 CPU1 核 内存2 GB 公网带宽1 Mbps ab -c100 -n1000 http://127.0.0.1:9501/ -n:在测试会话中所执行的请求个数。默认时,仅执行一个请求。 -c:一次产生的请求个数。默认是一次一个。 ab -c 100 -n 200 ht…...

多旋翼物流无人机节能轨迹规划(Python代码实现)
目录 💥1 概述 📚2 运行结果 🌈3 Python代码实现 🎉4 参考文献 💥1 概述 多旋翼物流无人机的节能轨迹规划是一项重要的技术,可以有效减少无人机的能量消耗,延长飞行时间,提高物流效率…...

Vue通过指令 命令将打包好的dist静态文件上传到腾讯云存储桶 (保存原有存储目录结构)
1、在项目根目录创建uploadToCOS.js文件 (建议起简单的名字 方便以后上传输入命令方便) 2、uploadToCOS.js文件代码编写 const path require(path); const fs require(fs); const COS require(cos-nodejs-sdk-v5);// 配置腾讯云COS参数 const cos n…...
Linux 新硬盘分区,挂载
在Linux系统中,当你插入新的硬盘时,你需要进行一些步骤来使系统识别并使用它。以下是一些常见的步骤: 确保硬盘已正确连接到计算机。检查硬盘的电源和数据线是否牢固连接。 打开终端或命令行界面。 运行以下命令来扫描新硬盘: s…...
Java 语言特性(面试系列2)
一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...

2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...

遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...
#Uniapp篇:chrome调试unapp适配
chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器:Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...

C++:多态机制详解
目录 一. 多态的概念 1.静态多态(编译时多态) 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1).协变 2).析构函数的重写 5.override 和 final关键字 1&#…...
【JavaSE】多线程基础学习笔记
多线程基础 -线程相关概念 程序(Program) 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存…...