7、Linux驱动开发:设备-自动创建设备节点
目录
🍅点击这里查看所有博文
随着自己工作的进行,接触到的技术栈也越来越多。给我一个很直观的感受就是,某一项技术/经验在刚开始接触的时候都记得很清楚。往往过了几个月都会忘记的差不多了,只有经常会用到的东西才有可能真正记下来。存在很多在特殊情况下有一点用处的技巧,用的不多的技巧可能一个星期就忘了。
想了很久想通过一些手段把这些事情记录下来。也尝试过在书上记笔记,这也只是一时的,书不在手边的时候那些笔记就和没记一样,不是很方便。
很多时候我们遇到了问题,一般情况下都是选择在搜索引擎检索相关内容,这样来的也更快一点,除非真的找不到才会去选择翻书。后来就想到了写博客,博客作为自己的一个笔记平台倒是挺合适的。随时可以查阅,不用随身携带。
同时由于写博客是对外的,既然是对外的就不能随便写,任何人都可以看到。经验对于我来说那就只是经验而已,公布出来说不一定我的一些经验可以帮助到其他的人。遇到和我相同问题时可以少走一些弯路。
既然决定了要写博客,那就只能认真去写。不管写的好不好,尽力就行。千里之行始于足下,一步一个脚印,慢慢来 ,写的多了慢慢也会变好的。权当是记录自己的成长的一个过程,等到以后再往回看时,就会发现自己以前原来这么菜😂。
本系列博客所述资料均来自互联网资料,并不是本人原创(只有博客是自己写的)。出于热心,本人将自己的所学笔记整理并推出相对应的使用教程,方面其他人学习。为国内的物联网事业发展尽自己的一份绵薄之力,没有为自己谋取私利的想法。若出现侵权现象,请告知本人,本人会立即停止更新,并删除相应的文章和代码。
前言
在前面两小节中,我们学习到了设备注册。可以将一个设备驱动注册到内核中。设备注册完成后,还需要通过mknod指令在用户空间中手动创建该驱动对应的设备节点。
root@ubuntu:# mknod /dev/hello_test0 c 237 0
该命令在执行是不会检查参数的合法性。也不会检查设备驱动是否存在。如果系统中所有的驱动都通过该方法创建设备节点,就会出现一个问题。当设备未接入时,就可能会出现很多的设备节点。
实际上Linux内核为我们提供了一组函数,可以在模块加载的时候自动在/dev目录下创建相应设备节点,并在卸载模块时删除该节点,当然前提条件是用户空间移植了udev。
udev
udev是一个工作在用户空间的工具,它能根据系统中硬件设备的状态动态的更新设备文件,包括设备文件的创建,删除,权限等。这些文件通常都定义在/dev 目录下,但也可以在配置文件中指定。
当插入新设备—>加入驱动模块—>在sysfs上注册新的数据后,udev会自动创建新的设备节点。udev运行在用户模式中,而并非内核中。

接口
内核中定义了struct class结构体,顾名思义,一个struct class结构体类型变量对应一个类。代码中出现的class指的是 设备类(device classes),是对于设备的高级抽象。但 实际上class也是一个结构体,只不过class结构体在声明时是按照类的思想来组织其成员的。
/*** struct class - device classes* @name: Name of the class.* @owner: The module owner.* @class_attrs: Default attributes of this class.* @dev_groups: Default attributes of the devices that belong to the class.* @dev_kobj: The kobject that represents this class and links it into the hierarchy.* @dev_uevent: Called when a device is added, removed from this class, or a* few other things that generate uevents to add the environment* variables.* @devnode: Callback to provide the devtmpfs.* @class_release: Called to release this class.* @dev_release: Called to release the device.* @suspend: Used to put the device to sleep mode, usually to a low power* state.* @resume: Used to bring the device from the sleep mode.* @ns_type: Callbacks so sysfs can detemine namespaces.* @namespace: Namespace of the device belongs to this class.* @pm: The default device power management operations of this class.* @p: The private data of the driver core, no one other than the* driver core can touch this.** A class is a higher-level view of a device that abstracts out low-level* implementation details. Drivers may see a SCSI disk or an ATA disk, but,* at the class level, they are all simply disks. Classes allow user space* to work with devices based on what they do, rather than how they are* connected or how they work.*/
struct class {const char *name;struct module *owner;struct class_attribute *class_attrs;const struct attribute_group **dev_groups;struct kobject *dev_kobj;int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);char *(*devnode)(struct device *dev, umode_t *mode);void (*class_release)(struct class *class);void (*dev_release)(struct device *dev);int (*suspend)(struct device *dev, pm_message_t state);int (*resume)(struct device *dev);const struct kobj_ns_type_operations *ns_type;const void *(*namespace)(struct device *dev);const struct dev_pm_ops *pm;struct subsys_private *p;
};
内核同时提供了class_create宏。用于动态创建设备的逻辑类,并完成部分字段的初始化,然后将其添加进Linux内核系统中。此函数的执行效果就是在**/sys/class/**目录下创建一个新的文件夹,此文件夹的名字为此函数的第二个输入参数。
class_create 一共有两个参数,参数 owner 一般为 THIS_MODULE,参数 name 是类名字。返回值是个指向结构体 class 的指针,也就是创建的类。
/* This is a #define to keep the compiler from merging different* instances of the __key variable */
#define class_create(owner, name) \
({ \static struct lock_class_key __key; \__class_create(owner, name, &__key); \
})/*** class_create - create a struct class structure* @owner: pointer to the module that is to "own" this struct class* @name: pointer to a string for the name of this class.* @key: the lock_class_key for this class; used by mutex lock debugging** This is used to create a struct class pointer that can then be used* in calls to device_create().** Returns &struct class pointer on success, or ERR_PTR() on error.** Note, the pointer created here is to be destroyed when finished by* making a call to class_destroy().*/
struct class *__class_create(struct module *owner, const char *name,struct lock_class_key *key)
{struct class *cls;int retval;cls = kzalloc(sizeof(*cls), GFP_KERNEL);if (!cls) {retval = -ENOMEM;goto error;}cls->name = name;cls->owner = owner;cls->class_release = class_create_release;retval = __class_register(cls, key);if (retval)goto error;return cls;
error:kfree(cls);return ERR_PTR(retval);
}
函数device_create用于动态创建逻辑设备,对新的逻辑设备进行相应初始化,然后将此逻辑设备加入到Linux内核系统的设备驱动程序模型中。
device_create是个可变参数函数,参数 class 就是设备要创建在哪个类下面。参数 parent 是父设备,一般为 NULL,也就是没有父设备。参数 devt 是设备号。参数 drvdata 是设备可能会使用的一些数据,一般为 NULL。参数 fmt 是设备名字,如果设置 fmt=xxx 的话,就会生成/dev/xxx这个设备文件。返回值就是创建好的设备。
/*** device_create - creates a device and registers it with sysfs* @class: pointer to the struct class that this device should be registered to* @parent: pointer to the parent struct device of this new device, if any* @devt: the dev_t for the char device to be added* @drvdata: the data to be added to the device for callbacks* @fmt: string for the device's name** This function can be used by char device classes. A struct device* will be created in sysfs, registered to the specified class.** A "dev" file will be created, showing the dev_t for the device, if* the dev_t is not 0,0.* If a pointer to a parent struct device is passed in, the newly created* struct device will be a child of that device in sysfs.* The pointer to the struct device will be returned from the call.* Any further sysfs files that might be required can be created using this* pointer.** Returns &struct device pointer on success, or ERR_PTR() on error.** Note: the struct class passed to this function must have previously* been created with a call to class_create().*/
struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)
{va_list vargs;struct device *dev;va_start(vargs, fmt);dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);va_end(vargs);return dev;
}
该函数会自动地在/sys/devices/virtual目录下创建新的逻辑设备目录。并将其软连接到/sys/class/目录中对应的类下。同时还会在/dev目录下创建与逻辑类对应地设备文件。
root@ubuntu:# ll /sys/class/hellocls/
total 0
lrwxrwxrwx 1 root root 0 Sep 17 06:11 hellodev -> ../../devices/virtual/hellocls/hellodevroot@ubuntu:# ll /dev/hellodev
crw------- 1 root root 237, 0 Sep 17 06:11 /dev/hellodev
代码实现
示例代码实现也比较简单,完成设备的注册后。class_create创建一个hellocls的类,该函数最终会在/sys/class目录中创建一个名为hellocls的文件夹。device_create函数将设备驱动存放到hellocls类中,并创建对应的设备文件。
static int hello_init(void)
{int result; printk("hello_init \n");result = register_chrdev( major, "hello", &hello_ops);if(result < 0){printk("register_chrdev fail \n");return result;}cls = class_create(THIS_MODULE, "hellocls");if (IS_ERR(cls)) {printk(KERN_ERR "class_create() failed for cls\n");result = PTR_ERR(cls);goto out_err_1;}devno = MKDEV(major, minor); class_dev = device_create(cls, NULL, devno, NULL, "hellodev");if (IS_ERR(class_dev)) {result = PTR_ERR(class_dev);goto out_err_2;}return 0;
out_err_2:class_destroy(cls);
out_err_1:unregister_chrdev(major,"hello");return result;
}static void hello_exit(void)
{printk("hello_exit \n");device_destroy(cls, devno);class_destroy(cls);unregister_chrdev(major,"hello");return;
}
实验结果
测试程序如下,打开/dev/hellodev字符设备。紧接着关闭掉。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
main()
{int fd;fd = open("/dev/hellodev",O_RDWR);if(fd<0){perror("open fail \n");return;}printf("open ok \n");close(fd);printf("close ok \n");
}
加载模块,用户空间编译测试程序。运行测试程序对驱动进行打开和关闭的操作。日志可以看到驱动中的hello_open,hello_release都被正常调用。
root@ubuntu:# insmod ./hello.ko
root@ubuntu:# gcc ./test.c
root@ubuntu:# ./a.out
open ok
close ok
root@ubuntu:# dmesg
[170236.680298] hello_exit()
[170280.990839] hello_init
[222202.880295] hello_open()
[222202.880418] hello_release()
进入到系统的类目录,查看dev文件和uevent文件。其中记录的就是驱动模块中注册的设备号。
root@ubuntu:# cd /sys/class//hellocls/hellodev
root@ubuntu:# $ cat dev
237:0
root@ubuntu:# cat uevent
MAJOR=237
MINOR=0
DEVNAME=hellodev
root@ubuntu:# ll /dev/hellodev
crw------- 1 root root 237, 0 Sep 17 06:11 /dev/hellodev
那么本篇博客就到此结束了,这里只是记录了一些我个人的学习笔记,其中存在大量我自己的理解。文中所述不一定是完全正确的,可能有的地方我自己也理解错了。如果有些错的地方,欢迎大家批评指正。如有问题直接在对应的博客评论区指出即可,不需要私聊我。我们交流的内容留下来也有助于其他人查看,说不一定也有其他人遇到了同样的问题呢😂。
相关文章:
7、Linux驱动开发:设备-自动创建设备节点
目录 🍅点击这里查看所有博文 随着自己工作的进行,接触到的技术栈也越来越多。给我一个很直观的感受就是,某一项技术/经验在刚开始接触的时候都记得很清楚。往往过了几个月都会忘记的差不多了,只有经常会用到的东西才有可能真正记…...
用Python解析HTML页面
用Python解析HTML页面 文章目录 用Python解析HTML页面HTML 页面的结构XPath 解析CSS 选择器解析简单的总结 在前面的课程中,我们讲到了使用 request三方库获取网络资源,还介绍了一些前端的基础知识。接下来,我们继续探索如何解析 HTML 代码&…...
官方认证:研发效能(DevOps)工程师职业技术认证
培养端到端的研发效能人才 为贯彻落实《关于深化人才发展体制机制改革的意见》,推动实施人才强国战略,促进专业技术人员提升职业素养、补充新知识新技能,实现人力资源深度开发,推动经济社会全面发展,根据《中华人民共…...
搭建GPFS双机集群
1.环境说明: 系统主机名IP地址内存添加共享磁盘大小Centos7.9gpfs1192.168.10.1012G20GCentos7.9gpfs2192.168.10.1022G20G 2.环境配置: 配置网路IP地址: 修改网卡会话: nmcli connection modify ipv4.method manual ipv4.addre…...
【试题032】C语言关系运算符例题
1.题目:设int a2,b4,c5;,则表达式ab!c>b>a的值为? 2.代码分析: //设int a2,b4,c5;,则表达式ab!c>b>a的值为?int a 2, b 4, c 5;printf("%d\n", (a b ! c > b > a));//分析ÿ…...
系列四、FileReader和FileWriter
一、概述 FileReader 和 FileWriter 是字符流,按照字符来操作IO。 1.1、继承体系 二、FileReader常用方法 new FileReader(File/String)# 每次读取单个字符就返回,如果读取到文件末尾返回-1 read()# 批量读取多个字符到数组,返回读取的字节…...
【C++面向对象】2.构造函数、析构函数
文章目录 【 1. 构造函数 】1.1 带参构造函数--传入数据1.2 无参构造函数--不传入数据1.3 实例1.4 拷贝构造函数 【 2. 析构函数 】 【 1. 构造函数 】 类的构造函数是类的一种特殊的成员函数,它会 在每次创建类的新对象时执行。 构造函数的名称与类的名称是完全相同…...
uniapp:使用subNVue原生子窗体在map上层添加自定义组件
我们想要在地图上层添加自定义组件,比如一个数据提示框,点一下会展开,再点一下收起,在h5段显示正常,但是到app端真机测试发现组件显示不出来,这是因为map是内置原生组件,层级最高,自…...
Flutter开发GridView控件详解
GridView跟ListView很类似,Listview主要以列表形式显示数据,GridView则是以网格形式显示数据,掌握ListView使用方法后,会很轻松的掌握GridView的使用方法。 在某种界面设计中,如果需要很多个类似的控件整齐的排列&…...
Vue3.0里为什么要用 Proxy API 替代 defineProperty API ?
一、Object.defineProperty 定义:Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象 为什么能实现响应式 通过defineProperty 两个属性,get及set get 属性的 getter 函…...
pytest利用request fixture实现个性化测试需求详解
这篇文章主要为大家详细介绍了pytest如何利用request fixture实现个性化测试需求,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起了解一下− 前言 在深入理解 pytest-repeat 插件的工作原理这篇文章中,我们看到pytest_repeat源码中有这样一段 import pyt…...
算法练习16——O(1) 时间插入、删除和获取随机元素
LeetCode 380 O(1) 时间插入、删除和获取随机元素 实现RandomizedSet 类: RandomizedSet() 初始化 RandomizedSet 对象 bool insert(int val) 当元素 val 不存在时,向集合中插入该项,并返回 true ;否则,返回 false 。 …...
实时数据更新与Apollo:探索GraphQL订阅
前言 「作者主页」:雪碧有白泡泡 「个人网站」:雪碧的个人网站 「推荐专栏」: ★java一站式服务 ★ ★ React从入门到精通★ ★前端炫酷代码分享 ★ ★ 从0到英雄,vue成神之路★ ★ uniapp-从构建到提升★ ★ 从0到英雄ÿ…...
VMware Workstation里面安装ubuntu20.04的流程
文章目录 前言一、获取 desktop ubuntu20.04 安装镜像二、VMware Workstation下安装ubuntu20.041. VMware Workstation 创建一个新的虚拟机2. ubuntu20.04的安装过程3. 登录ubuntu20.044. 移除 ubuntu20.04 安装镜像总结参考资料前言 本文主要介绍如何在PC上的虚拟机(VMware W…...
pnpm的环境安装以及安装成功后无法使用的问题
文章目录 前言1、使用npm 安装2、安装后的注意点3、遇到问题4、配置path的环境变量(1)找到环境变量(2)找到并双击path的系统变量(3)复制第1步中使用npm安装的红框部分的路径(4)将第&…...
华为eNSP配置专题-浮动路由及BFD的配置
文章目录 华为eNSP配置专题-浮动路由及BFD的配置0、参考文档1、前置环境1.1、宿主机1.2、eNSP模拟器 2、基本环境搭建2.1、基本终端构成和连接2.2、基本终端配置 3、浮动路由配置3.1、浮动路由的基本配置3.2、浮动路由的负载均衡问题3.3、浮动路由的优先级调整 4、BFD的配置4.1…...
光储并网直流微电网simulink仿真模型,光伏采用mppt实现最大功率输出研究
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
面试题-React(十六):理解Redux及其工作原理
在现代前端开发中,状态管理是一个关键的问题。Redux是一个广泛使用的状态管理库,可以帮助开发者更有效地管理应用的状态。 一、什么是Redux? Redux是一个JavaScript状态管理库,用于管理应用中的状态(state࿰…...
Crypto(4)NewStarCTF 2023 week2 Crypto Rotate Xor
题目代码: # 导入所需的库和从secret模块加载"flag" from secret import flag from os import urandom from pwn import xor from Cryptodome.Util.number import *# 生成两个随机的 64 位素数,分别存储在变量 k1 和 k2 中 k1 getPrime(64) k2 getPrim…...
小程序-uni-app:将页面(html+css)生成图片/海报/名片,进行下载 保存到手机
一、需要描述 本文实现,uniapp微信小程序,把页面内容保存为图片,并且下载到手机上。 说实话网上找了很多资料,但是效果不理想,直到看了一个开源项目,我知道可以实现了。 本文以开源项目uniapp-wxml-to-can…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...
排序算法总结(C++)
目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指:同样大小的样本 **(同样大小的数据)**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...
STM32HAL库USART源代码解析及应用
STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...
Bean 作用域有哪些?如何答出技术深度?
导语: Spring 面试绕不开 Bean 的作用域问题,这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开,结合典型面试题及实战场景,帮你厘清重点,打破模板式回答,…...
人工智能--安全大模型训练计划:基于Fine-tuning + LLM Agent
安全大模型训练计划:基于Fine-tuning LLM Agent 1. 构建高质量安全数据集 目标:为安全大模型创建高质量、去偏、符合伦理的训练数据集,涵盖安全相关任务(如有害内容检测、隐私保护、道德推理等)。 1.1 数据收集 描…...
前端中slice和splic的区别
1. slice slice 用于从数组中提取一部分元素,返回一个新的数组。 特点: 不修改原数组:slice 不会改变原数组,而是返回一个新的数组。提取数组的部分:slice 会根据指定的开始索引和结束索引提取数组的一部分。不包含…...
MySQL的pymysql操作
本章是MySQL的最后一章,MySQL到此完结,下一站Hadoop!!! 这章很简单,完整代码在最后,详细讲解之前python课程里面也有,感兴趣的可以往前找一下 一、查询操作 我们需要打开pycharm …...
相关类相关的可视化图像总结
目录 一、散点图 二、气泡图 三、相关图 四、热力图 五、二维密度图 六、多模态二维密度图 七、雷达图 八、桑基图 九、总结 一、散点图 特点 通过点的位置展示两个连续变量之间的关系,可直观判断线性相关、非线性相关或无相关关系,点的分布密…...
MySQL体系架构解析(三):MySQL目录与启动配置全解析
MySQL中的目录和文件 bin目录 在 MySQL 的安装目录下有一个特别重要的 bin 目录,这个目录下存放着许多可执行文件。与其他系统的可执行文件类似,这些可执行文件都是与服务器和客户端程序相关的。 启动MySQL服务器程序 在 UNIX 系统中,用…...
