Linux 驱动开篇,内核是如何发送事件到用户空间
前言:
驱动热插拔事件初识,了解内核如何把事件发送给用户空间。
基础知识储备-参考资料
前面了解过数据模型,热插拔事件本身就基于数据模型 kObject/kset。 这里我们最好重温一下kObject/keyset 基础知识。
设备模型基本框架-kobject-kset
迅为-驱动-热插拔
内核发送事件到用户空间的方法
一、发送事件API函数-kobject_uevent
基础知识梳理
核心概念
kobject_uevent 是 Linux 内核设备模型(Device Model)的“信使”。它的唯一职责就是向用户空间(Userspace)广播一个事件,通知它内核中某个对象(通常代表一个设备)的状态发生了改变,例如被添加、移除或状态发生变化。
这套机制是 热插拔(Hotplug) 事件的基石。用户空间的守护进程(如 udev 或 mdev)监听这些事件,并据此动态地管理设备节点、加载驱动、设置权限等。
函数原型
// 最常用的函数,发送一个基本事件
int kobject_uevent(struct kobject *kobj, enum kobject_action action);
// 更强大的版本,允许附加自定义环境变量
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action, char *envp[]);
参数解释
*kobject kobj
指向事件源的内核对象指针。这个 kobject 至关重要,因为它提供了事件的上下文:
-
它的
kobj->name和它在sysfs中的路径 (kobj->kobj_path) 用于标识是哪个对象发出了事件。 -
它所属的
kset决定了事件的“子系统”(Subsystem),例如action参数是一个枚举值,定义了事件的具体类型:
enum kobject_action action:
事件的具体类型。这是一个枚举值,定义了事件的具体类型:
KOBJ_ADD: 对象被创建并添加到系统中。(最常用)KOBJ_REMOVE: 对象从系统中被移除。(最常用)KOBJ_CHANGE: 对象的状态或属性发生了改变(例如,电池电量变化、网线插拔)。KOBJ_MOVE: 对象被重命名或移动。KOBJ_ONLINE: 对象上线。KOBJ_OFFLINE: 对象下线。
*char envp[] (仅用于 kobject_uevent_env):
-
一个以 NULL 结尾的字符串数组,用于向用户空间传递额外的、自定义的信息。每个字符串都是一个 “KEY=VALUE” 格式的环境变量。这极大地增强了事件的灵活性。
-
例如,你可以传递 “PROFILE=performance” 或 “TRIGGER=low_battery”。
测试程序
源码程序如下:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/configfs.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
// 定义kobject结构体指针,用于表示第一个自定义内核对象
struct kobject *mykobject01;
// 定义kset结构体指针,用于表示自定义内核对象的集合
struct kset *mykset;
// 定义kobj_type结构体,用于定义自定义内核对象的类型
struct kobj_type mytype;
// 模块的初始化函数
static int mykobj_init(void)
{
int ret;
// 创建并添加kset,名称为"mykset",父kobject为NULL,属性为NULL
mykset = kset_create_and_add("mykset", NULL, NULL);
// 为mykobject01分配内存空间,大小为struct kobject的大小,标志为GFP_KERNEL
mykobject01 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
// 将mykset设置为mykobject01的kset属性
mykobject01->kset = mykset;
// 初始化并添加mykobject01,类型为mytype,父kobject为NULL,格式化字符串为"mykobject01"
ret = kobject_init_and_add(mykobject01, &mytype, NULL, "%s", "mykobject01");
// 触发一个uevent事件,表示kobject的属性发生了变化
ret = kobject_uevent(mykobject01, KOBJ_CHANGE);
return 0;
}
// 模块退出函数
static void mykobj_exit(void)
{
// 释放mykobject01的引用计数
kobject_put(mykobject01);
kset_unregister(mykset);
}
module_init(mykobj_init); // 指定模块的初始化函数
module_exit(mykobj_exit); // 指定模块的退出函数
MODULE_LICENSE("GPL"); // 模块使用的许可证
MODULE_AUTHOR("fangchen"); // 模块的作者
这里结合kset 、kobject,然后 模拟发送一个uevent 事件出去: kobject_init_and_add
我们其实就是模拟一次内核发送uevent 事件出去 来 理解内核发送uevent 的机制。
udevadm 监控命令
这里可以参考上面的资料:
udev简述
udevadm命令详解
我们用这个命令目的就是监听系统uevent 事件,查看上面源码程序测试现象和效果。
这里掌握一个命令:udevadm monitor & ,监听和显示当前系统uevent 事件。
核心命令如下:
udevadm info: 用于获取设备的详细信息, 包括设备路径、 属性、 驱动程序等。udevadm monitor: 用于监视和显示当前系统中的uevent事件。 它会实时显示设备的插入、拔出以及其他相关事件。udevadm trigger: 用于手动触发设备的uevent事件。 可以使用该命令模拟设备的插入、 拔出等操作, 以便触发相应的事件处理。udevadm settle: 用于等待udev处理所有已排队的uevent事件。 它会阻塞直到udev完成当前所有的设备处理操作。udevadm control: 用于与udev守护进程进行交互, 控制其行为。 例如, 可以使用该命令重新加载 udev 规则、 设置日志级别等。udevadm test: 用于测试udev规则的匹配和执行过程。 可以通过该命令测试特定设备是否能够正确触发相应的规则。
udevadm info - 查询设备信息
这是最常用的命令之一,用于查看 udev 数据库中的设备属性,这些属性是编写 udev 规则的关键。
常用选项:
- -q all:查询所有信息。
- -q path/-q name:查询设备路径或节点名。
- -a:显示设备的所有父设备的属性(非常重要,用于找规则匹配项)。
- -n:指定设备节点名。
- –attribute-walk:类似于 -a,但显示更详细的层次结构
# 最基本的方式:查看 /dev/sdb1 的所有 udev 属性
udevadm info -n /dev/sdb1
# 更强大的方式:显示 /dev/sdb1 及其所有父级设备(USB控制器、总线等)的属性
# 这能帮你找到最唯一、最稳定的属性来编写规则
udevadm info -a -n /dev/sdb1
udevadm monitor - 监视设备事件
这个命令用于实时监听内核发出的设备事件(如插拔),是调试规则的第一步。
常用选项:
- –kernel:只显示内核发出的事件(默认)。
- –udev:只显示 udev 处理完成后的事件。
- –property:显示事件的详细属性。
- –subsystem-match=:只监听特定子系统(如 usb, input)的事件
监听 USB 设备的插拔
打开一个终端,开始监听所有事件:
udevadm monitor --kernel --udev --property
插入你的 U 盘或鼠标。终端会立即打印出类似下面的信息:
KERNEL

最低0.47元/天 解锁文章
5325

被折叠的 条评论
为什么被折叠?



