ARM Linux 驱动开发篇---新版led驱动实验原理(2)--基于 mdev 机制实现设备节点自动创建及--利用私有数据结构体管理设备属性-- Ubuntu20.04

🎬 渡水无言:个人主页渡水无言
❄专栏传送门:linux专栏
⭐️流水不争先,争的是滔滔不绝
📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生
| 省级优秀毕业生获得者 | ZEEKLOG新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生
在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连

目录
前言
上一期介绍了一些新的分配和释放设备号,字符设备注册方法,接下来本期博客将介绍一下自动创建设备节点,设置文件私有数据的方法。
一、自动创建设备节点
在上一期的 Linux LED驱动实验中,当我们使用 modprobe 加载驱动程序以后还需要使用命令
“mknod”手动创建设备节点。接下来就来介绍一下如何实现自动创建设备节点,在驱动中实现自动创建设备节点的功能以后,使用 modprobe 加载驱动模块成功的话就会自动在/dev 目录下创建对应的设备文件。
1.1、mdev 机制
在 Linux 系统中,udev 是一个用户态守护进程,负责动态管理 /dev 目录下的设备节点文件。它通过监听内核事件,实时检测硬件设备的插拔与状态变化,并自动完成设备文件的创建与删除。
例如:当使用 modprobe 命令成功加载驱动模块时,udev 会自动在 /dev 目录下生成对应的设备节点;反之,使用 rmmod 卸载驱动后,相关设备节点也会被自动清理。
在嵌入式 Linux 场景中,为了适应资源受限的环境,通常使用 BusyBox 提供的轻量级工具 mdev。mdev 是 udev 的简化实现,同样承担着设备节点自动管理与热插拔事件处理的核心职责。在基于 BusyBox 构建的根文件系统中,mdev 的初始化配置通常位于 /etc/init.d/rcS 脚本中。
相关语句如下:
echo /sbin/mdev > /proc/sys/kernel/hotplug上述命令设置热插拔事件由 mdev 来管理。接下来我们详细介绍一下如何通过 mdev 来实现设备文件节点的自动创建与删除。
1.2、创建和删除类
在 Linux 驱动开发中,设备节点的自动创建工作,通常是在驱动程序的入口函数中完成的,具体来说,我们一般会在 cdev_add 函数调用之后,补充自动创建设备节点的相关代码。
要实现这一功能,首先需要创建一个 class 类 ——class 是 Linux 内核中用于管理设备的核心结构体,其定义位于内核头文件 include/linux/device.h 中。
而创建这个类的核心接口是 class_create,需要注意的是,class_create 并非一个普通函数,而是内核封装的宏定义,其具体的宏展开内容如下:
#define class_create(owner, name) \ ({ \ static struct lock_class_key __key; \ __class_create(owner, name, &__key); \ }) struct class *__class_create(struct module *owner, const char *name, struct lock_class_key *key)class_create 一共有两个参数,参数 owner 一般为 THIS_MODULE,参数 name 是类名字。 返回值是个指向结构体 class 的指针,也就是创建的类。
卸载驱动程序的时候需要删除掉类,类删除函数为 class_destroy,函数原型如下:
void class_destroy(struct class *cls);参数 cls 就是要删除的类。
1.3、创建设备
创建好类以后还不能实现自动创建设备节点,我们还需要在这个类下创建一个设备。使用device_create 函数在类下面创建设备,device_create 函数原型如下:
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)class:指定该设备要归属的设备类(即创建在哪个类目录下),需传入之前通过 class_create 创建的 struct class 指针,决定了设备节点在 /sys/class 下的归属。
parent:指向父设备的指针,用于标识设备的层级关系,绝大多数场景下无需指定父设备,直接传 NULL 即可。
devt:设备号(包含主设备号和次设备号),是设备在系统中的唯一标识,需与 cdev_add 中注册的设备号保持一致。
drvdata:指向设备私有数据的指针,可用于传递驱动需要的自定义数据,若无特殊需求,通常传 NULL。
fmt:设备名称的格式化字符串(可变参数的核心),比如设置 fmt = "xxx",系统会自动在 /dev 目录下生成 /dev/xxx 这个设备节点文件。
同样的,卸载驱动的时候需要删除掉创建的设备,设备删除函数为 device_destroy,函数原型如下:
void device_destroy(struct class *class, dev_t devt)参数 class 是要删除的设备所处的类,参数 devt 是要删除的设备号。
1.4、示例
在驱动入口函数里面创建类和设备,在驱动出口函数里面删除类和设备,示例如下:
struct class *class; /* 类 */ struct device *device; /* 设备 */ dev_t devid; /* 设备号 */ /* 驱动入口函数 */ static int __init led_init(void) { /* 创建类 */ class = class_create(THIS_MODULE, "xxx"); /* 创建设备 */ device = device_create(class, NULL, devid, NULL, "xxx"); return 0; } /* 驱动出口函数 */ static void __exit led_exit(void) { /* 删除设备 */ device_destroy(newchrled.class, newchrled.devid); /* 删除类 */ class_destroy(newchrled.class); } module_init(led_init); module_exit(led_exit);1.5、总结流程图

左侧是驱动加载 + 自动创建设备节点正向流程,右侧是驱动卸载 + 自动删除设备节点反向流程
注意:
红色背景(keyStep):驱动开发中需要手动编写代码的核心步骤(创建 / 删除类、设备);
蓝色背景(sysStep):系统层面自动执行的步骤(mdev 监听、热插拔事件处理)。
二、设置文件私有数据
2.1具体原理代码
在嵌入式 Linux 驱动开发中,我们经常需要管理设备的各种属性,如主设备号、cdev 结构体、设备类、设备指针等。如果将这些属性零散地定义为全局变量,不仅代码不专业,还会导致维护困难和线程安全问题。就像下面这种写法:
// 零散的全局变量,弊端显著 dev_t devid; /* 设备号 */ struct cdev test_cdev; /* 字符设备结构体 */ struct class *class; /* 设备类 */ struct device *device; /* 设备实例 */ int major; /* 主设备号 */ int minor; /* 次设备号 */所以有必要使用私有数据结构体来封装设备属性,并在open函数中将其作为私有数据挂载到文件结构体中,实现高效、安全的设备管理。如下所示:
/* 设备结构体 */ struct test_dev{ dev_t devid; /* 设备号 */ struct cdev cdev; /* cdev */ struct class *class; /* 类 */ struct device *device; /* 设备 */ int major; /* 主设备号 */ int minor; /* 次设备号 */ }; struct test_dev testdev; /* open函数 */ static int test_open(struct inode *inode, struct file *filp) { filp->private_data = &testdev; /* 设置私有数据 */ return 0; } 重点在于open 函数挂载私有数据
filp->private_data = &testdev;这是整个设计的 “枢纽”,将文件实例与设备结构体绑定;
后续所有文件操作函数(read/write/ioctl)都能通过filp->private_data访问设备属性。
2.2总体流程框图

总结
本期博客主要介绍了自动创建设备节点,设置文件私有数据的方法。