4、Linux驱动开发:设备-设备号设备号注册

news2024/11/24 23:46:34

目录

🍅点击这里查看所有博文

  随着自己工作的进行,接触到的技术栈也越来越多。给我一个很直观的感受就是,某一项技术/经验在刚开始接触的时候都记得很清楚。往往过了几个月都会忘记的差不多了,只有经常会用到的东西才有可能真正记下来。存在很多在特殊情况下有一点用处的技巧,用的不多的技巧可能一个星期就忘了。

  想了很久想通过一些手段把这些事情记录下来。也尝试过在书上记笔记,这也只是一时的,书不在手边的时候那些笔记就和没记一样,不是很方便。

  很多时候我们遇到了问题,一般情况下都是选择在搜索引擎检索相关内容,这样来的也更快一点,除非真的找不到才会去选择翻书。后来就想到了写博客,博客作为自己的一个笔记平台倒是挺合适的。随时可以查阅,不用随身携带。

  同时由于写博客是对外的,既然是对外的就不能随便写,任何人都可以看到。经验对于我来说那就只是经验而已,公布出来说不一定我的一些经验可以帮助到其他的人。遇到和我相同问题时可以少走一些弯路。

  既然决定了要写博客,那就只能认真去写。不管写的好不好,尽力就行。千里之行始于足下,一步一个脚印,慢慢来 ,写的多了慢慢也会变好的。权当是记录自己的成长的一个过程,等到以后再往回看时,就会发现自己以前原来这么菜😂。

  本系列博客所述资料均来自互联网资料,并不是本人原创(只有博客是自己写的)。出于热心,本人将自己的所学笔记整理并推出相对应的使用教程,方面其他人学习。为国内的物联网事业发展尽自己的一份绵薄之力,没有为自己谋取私利的想法。若出现侵权现象,请告知本人,本人会立即停止更新,并删除相应的文章和代码。

设备分类

  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 mem
  4 /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 mem
  4 /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

  那么本篇博客就到此结束了,这里只是记录了一些我个人的学习笔记,其中存在大量我自己的理解。文中所述不一定是完全正确的,可能有的地方我自己也理解错了。如果有些错的地方,欢迎大家批评指正。如有问题直接在对应的博客评论区指出即可,不需要私聊我。我们交流的内容留下来也有助于其他人查看,说不一定也有其他人遇到了同样的问题呢😂。

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

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

相关文章

Unity游戏源码分享-ARPG游戏Darklight.rar

Unity游戏源码分享-ARPG游戏Darklight.rar 玩法 项目地址&#xff1a;https://download.csdn.net/download/Highning0007/88105464

1.jquery遍历数组2.layui框架的理解

1.jquery遍历数组 2.layui框架的理解 layui.use(["form", "laydate"], 是使用 layui 框架中的模块加载方法 use 来加载并使用 form 和 laydate 这两个模块。 在 layui 框架中&#xff0c;可以使用 use 方法来加载所需的模块&#xff0c;然后使用这些模块…

[CrackMe]Chafe.1.exe的逆向及注册机编写

上手先试一下, 发现其没有对话框, 只有字符串, 搜索"Your serial is not valid"字符串 \ 上来就直接发现关键跳转, 难道这题这么简单吗? 仔细一看实际上远远要复杂 往上一翻发现没有生成serial key的代码, 而是看到了一个SetTimer, 时间间隔设置成了1ms, 之前输入…

访问:http://localhost:8070/actuator/bus-refresh 问题

1、请求发送不出去 原因&#xff1a; 自己 config-server端 application.yml 配置的端口号是8888&#xff0c;访问server修改为配置的端口号 2、请求报错405 几个解决办法&#xff1a; 1、版本问题变为busrefresh 2、bus-refresh加单引号或双引号尝试 3、加配置尝试&#xff1a…

嵌入式_GD32看门狗配置

嵌入式_GD32独立看门狗配置与注意事项 文章目录 嵌入式_GD32独立看门狗配置与注意事项前言一、什么是独立看门狗定时器&#xff08;FWDGT&#xff09;二、独立看门狗定时器原理三、独立看门狗定时器配置过程与注意事项总结 前言 使用GD3单片机时&#xff0c;为了提供了更高的安…

服务器数据恢复-Windows服务器RAID5数据恢复案例

服务器数据恢复环境&#xff1a; 一台服务器挂载三台IBM某型号存储设备&#xff0c;共64块SAS硬盘&#xff0c;组建RAID5磁盘阵列&#xff1b; 服务器操作系统&#xff1a;Windows Server&#xff1b;文件系统&#xff1a;NTFS。 服务器故障&#xff1a; 一台存储中的一块硬盘离…

STM32 串口学习(二)

要用跳线帽将PA9与RXD相连&#xff0c;PA10与TXD相连。 软件设计 void uart_init(u32 baud) {//UART 初始化设置UART1_Handler.InstanceUSART1; //USART1UART1_Handler.Init.BaudRatebound; //波特率UART1_Handler.Init.WordLengthUART_WORDLENGTH_8B; //字长为 8 位数据格式U…

Java语言跨平台执行的核心JVM

本文重点 在前面的课程中,我们介绍了java中的三层JDK->JRE->JVM,其中JVM称为Java的虚拟机,只是用来执行的,JRE是运行环境,要想在操作系统中运行,除了JVM还需要类库,JDK=JRE+开发的包和工具。本文就将介绍一下JVM究竟为何物? JVM 有的人会认为JVM是java中的东西…

29.双击喜欢

双击喜欢 html部分 <h3>双击喜欢这个图 <i style"color: red;" class"iconfont icon-xin"></i></h3> <small>你喜欢了 <span class"count">0</span> 次</small> <div class"area&quo…

【iOS】Cocoapods的安装以及使用

文章目录 前言一、Cocoapods的作用二、安装Cocoapods三、使用Cocoapods总结 前言 最近笔者在仿写天气预报App时用到了api调用数据&#xff0c;一般的基本数据类型我们用Xcode中自带的框架就可以转换得到。但是在和风天气api中的图标的格式为svg格式。 似乎iOS13之后Xcode中可…

Baumer工业相机堡盟工业相机如何通过BGAPI SDK获取相机当前实时帧率(C++)

Baumer工业相机堡盟工业相机如何通过BGAPISDK里函数来计算相机的实时帧率&#xff08;C&#xff09; Baumer工业相机Baumer工业相机的帧率的技术背景Baumer工业相机的帧率获取方式CameraExplorer如何查看相机帧率信息在BGAPI SDK里通过函数获取相机帧率 Baumer工业相机通过BGAP…

去除重复字母(力扣)贪心 + 队列 JAVA

给你一个字符串 s &#xff0c;请你去除字符串中重复的字母&#xff0c;使得每个字母只出现一次。需保证 返回结果的字典序最小&#xff08;要求不能打乱其他字符的相对位置&#xff09;。 示例 1&#xff1a; 输入&#xff1a;s “bcabc” 输出&#xff1a;“abc” 示例 2&am…

【亲测可用】Linux上安装Redis教程

一、下载并解压Redis 1、执行下面的命令下载redis&#xff1a; wget https://download.redis.io/releases/redis-6.2.6.tar.gz 2、解压redis&#xff1a; tar xzf redis-6.2.6.tar.gz 3、移动redis目录&#xff0c;一般都会将redis目录放置到 /usr/local/redis目录&#xff1a…

如何利用设备数字化平台推动精益制造?

人工智能驱动技术的不断发展&#xff0c;尤其是基于机器学习的预测分析工具的使用&#xff0c;为制造业带来了全新的效率和价值水平。一直以来&#xff0c;精益生产&#xff08;也叫精益制造&#xff09;在制造业中扮演着重要角色&#xff0c;而现在通过与工业 4.0的融合&#…

rpc通信原理浅析

rpc通信原理浅析 rpc(remote procedure call)&#xff0c;即远程过程调用&#xff0c;广泛用于分布式或是异构环境下的通信&#xff0c;数据格式一般采取protobuf。 protobuf&#xff08;protocol buffer&#xff09;是google 的一种数据交换的格式&#xff0c;它独立于平台语…

pytest---环境切换(base-url)

前言 前面小编介绍了如何通过pytest的插件来实现自动化测试的环境的切换&#xff0c;当时使用的方法是通过钩子函数进行获取命令行参数值&#xff0c;然后通过提前配置好的参数进行切换测试环境地址&#xff0c;今天小编再次介绍一种方法&#xff0c;通过pytest的插件&#xff…

three.js入门二:相机的zoom参数

环境&#xff1a; threejs&#xff1a;129 &#xff08;在浏览器的控制台下输入&#xff1a; window.__THREE__即可查看版本&#xff09;vscodewindowedge 透视相机或正交相机都有一个zoom参数&#xff0c;它可以用来将相机排到的内容在canvas上缩放显示。 注意&#xff1a;…

二、SQL-5.DQL-9).执行顺序

一、案例&#xff1a; 查询年龄大于15的员工的姓名、年龄&#xff0c;并根据年龄进行升序排序 select name, age from emp where age > 15 order by age asc; 先执行①from&#xff08;定义emp的别名为e&#xff09;&#xff0c;再执行②where&#xff08;调用别名e&…

基于Spring包扫描工具和MybatisPlus逆向工程组件的数据表自动同步机制

公司产品产出的项目较多。同步数据库表结构工作很麻烦。一个alter语句要跑到N个客户机上执行脚本。超级费时麻烦。介于此&#xff0c;原有方案是把增量脚本放到一resource包下&#xff0c;项目启动时执行逐行执行一次。但由于模块开发人员较多&#xff0c;总有那么一两个机灵鬼…

大数据课程D2——hadoop的概述

文章作者邮箱&#xff1a;yugongshiyesina.cn 地址&#xff1a;广东惠州 ▲ 本章节目的 ⚪ 了解hadoop的定义和特点&#xff1b; ⚪ 掌握hadoop的基础结构&#xff1b; ⚪ 掌握hadoop的常见命令&#xff1b; ⚪ 了解hadoop的执行流程&#xff1b; 一、简介 1…