Linux 设备模型浅析之 uevent 篇

2024-03-14 12:36

本文主要是介绍Linux 设备模型浅析之 uevent 篇,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Linux 设备模型,仅仅看理论介绍,比如 LDD3 的第十四章,会感觉太抽象不易理解,而
通过阅读内核代码就更具体更易理解,所以结合理论介绍和内核代码阅读能够更快速的理解掌
linux 设备模型。这一序列的文章的目的就是在于此,看这些文章之前最好能够仔细阅读
LDD3 的第十四章。 uevent ,即 user space event ,就是内核向用户空间发出的一个事件通知,使
得应用程序能有机会对该 event 作出反应, udev mdev(busybox) 就是这种应用程序。阅读这篇
文章之前,最好先阅读文章《 Linux 设备模型浅析之设备篇》和《 Linux 设备模型浅析之驱动
篇》。
一、在《 Linux 设备模型浅析之设备篇》中介绍过 device_add() 例程,其用于将一个 device
注册到 device model ,其中调用了 kobject_uevent (&dev->kobj, KOBJ_ADD) 例程向用户空间发出
KOBJ_ADD 事件并输出环境变量,以表明一个 device 被添加了。在《 Linux 设备模型浅析之设
备篇》中介绍过 rtc_device_register() 例程 ,其最终调用
device_add() 例程添加了一个 rtc0
device ,我们就以它为例子来完成 uevent 的分析。让我们看看 kobject_uevent() 这个例程的代
码,如下:
int kobject_uevent(struct kobject *kobj, enum kobject_action action)
{
return kobject_uevent_env(kobj, action, NULL);
}
它又调用了 kobject_uevent_env() 例程,部分代码如下:
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
char *envp_ext[])
{
struct kobj_uevent_env *env;
const char *action_string = kobject_actions[action]; // 本例是 add” 命令
const char *devpath = NULL;
const char *subsystem;
struct kobject *top_kobj;
struct kset *kset;
struct kset_uevent_ops *uevent_ops;
u64 seq;
int i = 0;
int retval = 0;
pr_debug("kobject: '%s' (%p): %s\n",
kobject_name(kobj), kobj, __func__);
/* search the kset we belong to */
top_kobj = kobj;
/* 找到其所属的 kset 容器,如果没找到就从其父 kobj 找,一直持续下去,直到父 kobj
存在 */
while (!top_kobj->kset && top_kobj->parent) top_kobj = top_kobj->parent;
if (!top_kobj->kset) {
pr_debug("kobject: '%s' (%p): %s: attempted to send uevent "
"without kset!\n", kobject_name(kobj), kobj,
__func__);
return -EINVAL;
}
/* 在本例中是 devices_kset 容器,详细介绍可参照《 Linux 设备模型浅析之设备篇》,后
面将列出 devices_kset 的定义 */
kset = top_kobj->kset;
uevent_ops = kset->uevent_ops;
// 本例中 uevent_ops = device_uevent_ops
/* 回调 uevent_ops->filter () 例程,本例中是 dev_uevent_filter() 例程,主要是检查是否
uevent suppress ,后面分析 */
/* skip the event, if the filter returns zero. */
if (uevent_ops && uevent_ops->filter)
if (!uevent_ops->filter(kset, kobj)) { // 如果不成功,即 uevent suppress ,则直接返回
pr_debug("kobject: '%s' (%p): %s: filter function "
"caused the event to drop!\n",
kobject_name(kobj), kobj, __func__);
return 0;
}
/* 回调 uevent_ops-> name () ,本例中是 dev_uevent_name() 例程,获取 bus class 的名
字,本例中 rtc0 不存在 bus ,所以是 class 的名字 rtc” ,后面分析 */
/* originating subsystem */
if (uevent_ops && uevent_ops->name)
subsystem = uevent_ops->name(kset, kobj);
else
subsystem = kobject_name(&kset->kobj);
if (!subsystem) {
pr_debug("kobject: '%s' (%p): %s: unset subsystem caused the "
"event to drop!\n", kobject_name(kobj), kobj,
__func__);
return 0;
}
// 获得用于存放环境变量的 buffer
/* environment buffer */
env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
if (!env)
return -ENOMEM;
/* 获取该 kobj sysfs 的路径,通过遍历其父 kobj 来获得,本例是 /sys/devices/platform/
s3c2410-rtc/rtc/rtc0 */
/* complete object path */ devpath = kobject_get_path(kobj, GFP_KERNEL);
if (!devpath) {
retval = -ENOENT;
goto exit;
}
// 添加 ACTION 环境变量,本例是 add” 命令
/* default keys */
retval = add_uevent_var(env, "ACTION=%s", action_string);
if (retval)
goto exit;
// 添加 DEVPATH 环境变量,本例是 /sys/devices/platform/s3c2410-rtc/rtc/rtc0
retval = add_uevent_var(env, "DEVPATH=%s", devpath);
if (retval)
goto exit;
// 添加 SUBSYSTEM 环境变量,本例中是 rtc”
retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
if (retval)
goto exit;
/* keys passed in from the caller */
if (envp_ext) {
// NULL ,不执行
for (i = 0; envp_ext[i]; i++) {
retval = add_uevent_var(env, "%s", envp_ext[i]);
if (retval)
goto exit;
}
}
// 回调 uevent_ops->uevent() ,本例中是 dev_uevent() 例程,输出一些环境变量,后面分析
/* let the kset specific function add its stuff */
if (uevent_ops && uevent_ops->uevent) {
retval = uevent_ops->uevent(kset, kobj, env);
if (retval) {
pr_debug("kobject: '%s' (%p): %s: uevent() returned "
"%d\n", kobject_name(kobj), kobj,
__func__, retval);
goto exit;
}
}
/*
* Mark "add" and "remove" events in the object to ensure proper
* events to userspace during automatic cleanup. If the object did
* send an "add" event, "remove" will automatically generated by
* the core, if not already done by the caller.
*/
if (action == KOBJ_ADD) kobj->state_add_uevent_sent = 1;
else if (action == KOBJ_REMOVE)
kobj->state_remove_uevent_sent = 1;
/* 增加 event 序列号的值,并输出到环境变量的 buffer 。该系列号可以从 /sys/kernel/
uevent_seqnum 属性文件读取,至于 uevent_seqnum 属性文件及 /sys/kernel/ 目录是怎样产
生的,后面会分析 */
/* we will send an event, so request a new sequence number */
spin_lock(&sequence_lock);
seq = ++uevent_seqnum;
spin_unlock(&sequence_lock);
retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)seq);
if (retval)
goto exit;
/* 如果配置了网络,那么就会通过 netlink socket 向用户空间发送环境标量,而用户空间
则通过 netlink socket 接收,然后采取一些列的动作。这种机制目前用在 udev 中,也就是
pc 机系统中,后面会分析 */
#if defined(CONFIG_NET)
/* send netlink message */
/* 如果配置了 net ,则会在 kobject_uevent_init() 例程中将全局比昂俩 uevent_sock 初试化
NETLINK_KOBJECT_UEVENT 类型的 socket */
if (uevent_sock) {
struct sk_buff *skb;
size_t len;
/* allocate message with the maximum possible size */
len = strlen(action_string) + strlen(devpath) + 2;
skb = alloc_skb(len + env->buflen, GFP_KERNEL);
if (skb) {
char *scratch;
/* add header */
scratch = skb_put(skb, len);
sprintf(scratch, "%s@%s", action_string, devpath);
/* copy keys to our continuous event payload buffer */
for (i = 0; i < env->envp_idx; i++) {
len = strlen(env->envp[i]) + 1;
scratch = skb_put(skb, len);
strcpy(scratch, env->envp[i]);
}
NETLINK_CB(skb).dst_group = 1;
retval = netlink_broadcast(uevent_sock, skb, 0, 1,
GFP_KERNEL); // 广播
} else
retval = -ENOMEM;
} #endif
/* 对于嵌入式系统来说, busybox 采用的是 mdev ,在系统启动脚本 rcS 中会使用 echo /
sbin/mdev > /proc/sys/kernel/hotplug 命令,而这个 hotplug 文件通过一定的方法映射到了 u
event_helper[] 数组,所以 uevent_helper[] = “/sbin/mdev” 。所以对于采用 busybox 的嵌
入式系统来说会执行里面的代码,而 pc 机不会。也就是说内核会 call 用户空间
/sbin/mdev 这个应用程序来做动作,后面分析 */
/* call uevent_helper, usually only enabled during early boot */
if (uevent_helper[0]) {
char *argv [3];
// 加入到环境变量 buffer
argv [0] = uevent_helper;
argv [1] = (char *)subsystem;
argv [2] = NULL;
retval = add_uevent_var(env, "HOME=/");
if (retval)
goto exit;
retval = add_uevent_var(env,
"PATH=/sbin:/bin:/usr/sbin:/usr/bin");
if (retval)
goto exit;
// 呼叫应用程序来处理, UMH_WAIT_EXEC 表明等待应用程序处理完
retval = call_usermodehelper(argv[0], argv,
env->envp, UMH_WAIT_EXEC);
}
exit:
kfree(devpath);
kfree(env);
return retval;
}
代码中,
1. devices_kset 容器指针定义在 drivers/base/core.c 中,在同文件里的 devices_init() 例程中初
始化, devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL) 。显然初始化后
devices_kset ->uevent_ops = &device_uevent_ops 。而 device_uevent_ops 结构体定义如下:
static struct kset_uevent_ops device_uevent_ops = {
.filter =
dev_uevent_filter,
.name =
dev_uevent_name,
.uevent =
dev_uevent,
}
通过该结构体的定义,就可以知道上面分析的一些回调例程的出处了。
2. dev_uevent_filter() 例程可以让程序发送 uevent 事件之前做些检查和过滤,然后再决定是
否发送 uevent 事件,其代码定义如下:
static int dev_uevent_filter(struct kset *kset, struct kobject *kobj)
{
struct kobj_type *ktype = get_ktype(kobj); if (ktype == &device_ktype) {
// 做个检测
struct device *dev = to_dev(kobj);
if (dev->uevent_suppressha) // 可以通过设置这个变量为 1 来阻止发送 uevent 事件
return 0;
if (dev->bus)
return 1;
if (dev->class) // bus class 如果都没设置,那么也不会发送 uevent 事件
return 1;
}
return 0;
}
3. dev_uevent_name() 例程用于获取 bus class name bus 优先,其代码定义如下:
static const char *dev_uevent_name(struct kset *kset, struct kobject *kobj)
{
struct device *dev = to_dev(kobj);
if (dev->bus) // 如果设置了 bus ,则返回 bus name
return dev->bus->name;
if (dev->class) // 如果没有设置 bus 而设置了 class ,则返回 class name
return dev->class->name;
return NULL; // 否则,返回 NULL
}
4. dev_uevent() 例程用于输出一定的环境变量,其代码定义如下:
static int dev_uevent(struct kset *kset, struct kobject *kobj,
struct kobj_uevent_env *env)
{
struct device *dev = to_dev(kobj);
int retval = 0;
/* add the major/minor if present */
if (MAJOR(dev->devt)) {
// 本例中 rtc0 devt ,所以会输出下面的环境变量
add_uevent_var(env, "MAJOR=%u", MAJOR(dev->devt));
add_uevent_var(env, "MINOR=%u", MINOR(dev->devt));
}
// 本例中没有设置 type
if (dev->type && dev->type->name)
add_uevent_var(env, "DEVTYPE=%s", dev->type->name);
// 本例中没有设置 driver
if (dev->driver)
add_uevent_var(env, "DRIVER=%s", dev->driver->name);
/* have the bus specific function add its stuff */
/* 本例中没有设置 bus ,若果设置了 platform_bus_type ,则调用 platform_uevent() 例程,
可参考 platform.c */
if (dev->bus && dev->bus->uevent) {
retval = dev->bus->uevent(dev, env); if (retval)
pr_debug("device: '%s': %s: bus uevent() returned %d\n",
dev_name(dev), __func__, retval);
}
// 本例中设置了 class ,是 rtc class ,但是没有实现 class->dev_uevent() 例程
/* have the class specific function add its stuff */
if (dev->class && dev->class->dev_uevent) {
retval = dev->class->dev_uevent(dev, env);
if (retval)
pr_debug("device: '%s': %s: class uevent() "
"returned %d\n", dev_name(dev),
__func__, retval);
}
/* 本例中没有设置 type
/* have the device type specific fuction add its stuff */
if (dev->type && dev->type->uevent) {
retval = dev->type->uevent(dev, env);
if (retval)
pr_debug("device: '%s': %s: dev_type uevent() "
"returned %d\n", dev_name(dev),
__func__, retval);
}
return retval;
}
下面开始就分析 /sys/kernel 目录如何生成的,以及该目录下有些什么文件夹和文件。
二、在 kernel/ksysfs.c ksysfs_init() 例程中初始化了全局指针 kernel_kobj ,并生
/sys/kernel 目录,代码如下:
static int __init ksysfs_init(void)
{
int error;
// 获得一个 kobj ,无 parent ,故生成 /sys/kernel 目录
kernel_kobj = kobject_create_and_add("kernel", NULL);
if (!kernel_kobj) {
error = -ENOMEM;
goto exit;
}
// /sys/kernel 目录下生成一些属性文件(包含在属性组里)
error = sysfs_create_group(kernel_kobj, &kernel_attr_group);
if (error)
goto kset_exit;
if (notes_size > 0) {
notes_attr.size = notes_size; // 生成 /sys/kernel/notes 二进制属性文件,可用来读取二进制 kernel .notes section
error = sysfs_create_bin_file(kernel_kobj, ¬es_attr);
if (error)
goto group_exit;
}
/* 生成 /sys/kernel/uids 目录和 /sys/kernel/uids/0 目录以及 /sys/kernel/uids/0/cpu_share 属性文
件,后两者都是针对 root_user */
/* create the /sys/kernel/uids/ directory */
error = uids_sysfs_init();
if (error)
goto notes_exit;
return 0;
notes_exit:
if (notes_size > 0)
sysfs_remove_bin_file(kernel_kobj, ¬es_attr);
group_exit:
sysfs_remove_group(kernel_kobj, &kernel_attr_group);
kset_exit:
kobject_put(kernel_kobj);
exit:
return error;
}
代码中,
1. kernel_attr_group 的定义如下:
static struct attribute_group kernel_attr_group = {
.attrs = kernel_attrs,
}
kernel_attrs 的代码如下:
static struct attribute * kernel_attrs[] = {
#if defined(CONFIG_HOTPLUG)
&uevent_seqnum_attr.attr,
// 这个之前提过,用于获取 event 序列号
&uevent_helper_attr.attr,
// 这个之前也提过,用于存取用户提供的程序
#endif
#ifdef CONFIG_PROFILING
&profiling_attr.attr,
#endif
#ifdef CONFIG_KEXEC
&kexec_loaded_attr.attr,
&kexec_crash_loaded_attr.attr,
&vmcoreinfo_attr.attr,
#endif
NULL
}
2. uevent_seqnum_attr 是通过 KERNEL_ATTR_RO(uevent_seqnum) 定义的,也就是说,生成
/sys/kernel/uevent_seqnum 可读的名属性文件,读取的方法是 uevent_seqnum_show() 例程,其 代码如下:
static ssize_t uevent_seqnum_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
// uevent_seqnum 全局标量的值读取到 buf ,最终输出到用户空间
return sprintf(buf, "%llu\n", (unsigned long long)uevent_seqnum);
}
3. uevent_helper_attr 是通过 KERNEL_ATTR_RW(uevent_helper) 定义的,也就是说,生成名
/sys/kernel/uevent_helper 可读写的属性文件,其作用与 /proc/sys/kernel/hotplug 相同,最终是
读写 uevent_helper[] 数组。读写的方法分别是 uevent_helper_show() uevent_helper_store()
程,前者的代码如下:
static ssize_t uevent_helper_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
// 将数组 uevent_helper[] 中的字符读取到 buf ,最终输出到用户空间
return sprintf(buf, "%s\n", uevent_helper);
}
后者的代码如下:
static ssize_t uevent_helper_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
if (count+1 > UEVENT_HELPER_PATH_LEN) // 作个判读,字符串长度不能超标
return -ENOENT;
memcpy(uevent_helper, buf, count); // 拷贝用户传过来的字符串到 uevent_helper 数组
uevent_helper[count] = '\0';
if (count && uevent_helper[count-1] == '\n')
uevent_helper[count-1] = '\0';
return count;
}
三、通过前面的分析可知,要实现 hotplug 机制,需要有用户空间的程序配合才行。对于
pc 机的 linux 系统,采用的是 udevd 服务程序,其通过监听 NETLINK_KOBJECT_UEVENT
得内核发出的 uevent 事件和环境变量,然后再查找匹配的 udev rules ,根据找到的 rules 做动
作, udev 的具体实现原理可参照网上的一些文章。在《 Linux 设备模型浅析之设备篇》中讲
过,在每个注册的 device 文件夹下会生成一个 uevent 属性文件,其作用就是实现手动触发
hotplug 机制。可以向其中写入 add” remove” 等命令,以添加和移除设备。在系统启动后注
册了很多 device ,但用户空间还没启动,所以这些事件并没有处理, udevd 服务启动后,会扫
/sys 目录里所有的 uevent 属性文件,向其写入 "add” 命令,从而触发 uevent 事,这样 udevd
务程序就有机会处理这些事件了。在嵌入式系统中使用的是 mdev ,是 udev 的简化版本,在启
动脚本 rcS 中会有这样一句命令 /sbin/mdev -s ,其作用就是刚刚讲到的,扫描 /sys 目录里所有的
uevent 属性文件,向其写入 "add” 命令,触发 uevent 事件,从而 mdev 有机会处理这些事件。
从上面的分析可以看出,每当内核注册设备或驱动时都会产生 uevent 事件,这样用户空间
udev mdev 就有机会捕捉到这些事件,根据匹配的规则作一定的处理,比如在 /dev 目录下
生成设备节点或使用 modprobe 加载驱动程序,等等。从而实现自动生成设备节点、加载驱动程

序等等这些热拔插机制。 

这篇关于Linux 设备模型浅析之 uevent 篇的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/808443

相关文章

Linux中压缩、网络传输与系统监控工具的使用完整指南

《Linux中压缩、网络传输与系统监控工具的使用完整指南》在Linux系统管理中,压缩与传输工具是数据备份和远程协作的桥梁,而系统监控工具则是保障服务器稳定运行的眼睛,下面小编就来和大家详细介绍一下它... 目录引言一、压缩与解压:数据存储与传输的优化核心1. zip/unzip:通用压缩格式的便捷操作2.

Linux中SSH服务配置的全面指南

《Linux中SSH服务配置的全面指南》作为网络安全工程师,SSH(SecureShell)服务的安全配置是我们日常工作中不可忽视的重要环节,本文将从基础配置到高级安全加固,全面解析SSH服务的各项参... 目录概述基础配置详解端口与监听设置主机密钥配置认证机制强化禁用密码认证禁止root直接登录实现双因素

在Linux终端中统计非二进制文件行数的实现方法

《在Linux终端中统计非二进制文件行数的实现方法》在Linux系统中,有时需要统计非二进制文件(如CSV、TXT文件)的行数,而不希望手动打开文件进行查看,例如,在处理大型日志文件、数据文件时,了解... 目录在linux终端中统计非二进制文件的行数技术背景实现步骤1. 使用wc命令2. 使用grep命令

Linux如何快速检查服务器的硬件配置和性能指标

《Linux如何快速检查服务器的硬件配置和性能指标》在运维和开发工作中,我们经常需要快速检查Linux服务器的硬件配置和性能指标,本文将以CentOS为例,介绍如何通过命令行快速获取这些关键信息,... 目录引言一、查询CPU核心数编程(几C?)1. 使用 nproc(最简单)2. 使用 lscpu(详细信

linux重启命令有哪些? 7个实用的Linux系统重启命令汇总

《linux重启命令有哪些?7个实用的Linux系统重启命令汇总》Linux系统提供了多种重启命令,常用的包括shutdown-r、reboot、init6等,不同命令适用于不同场景,本文将详细... 在管理和维护 linux 服务器时,完成系统更新、故障排查或日常维护后,重启系统往往是必不可少的步骤。本文

浅析如何保证MySQL与Redis数据一致性

《浅析如何保证MySQL与Redis数据一致性》在互联网应用中,MySQL作为持久化存储引擎,Redis作为高性能缓存层,两者的组合能有效提升系统性能,下面我们来看看如何保证两者的数据一致性吧... 目录一、数据不一致性的根源1.1 典型不一致场景1.2 关键矛盾点二、一致性保障策略2.1 基础策略:更新数

基于Linux的ffmpeg python的关键帧抽取

《基于Linux的ffmpegpython的关键帧抽取》本文主要介绍了基于Linux的ffmpegpython的关键帧抽取,实现以按帧或时间间隔抽取关键帧,文中通过示例代码介绍的非常详细,对大家的学... 目录1.FFmpeg的环境配置1) 创建一个虚拟环境envjavascript2) ffmpeg-py

Linux脚本(shell)的使用方式

《Linux脚本(shell)的使用方式》:本文主要介绍Linux脚本(shell)的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录概述语法详解数学运算表达式Shell变量变量分类环境变量Shell内部变量自定义变量:定义、赋值自定义变量:引用、修改、删

Linux链表操作方式

《Linux链表操作方式》:本文主要介绍Linux链表操作方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、链表基础概念与内核链表优势二、内核链表结构与宏解析三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势六、典型应用场景七、调试技巧与

详解Linux中常见环境变量的特点与设置

《详解Linux中常见环境变量的特点与设置》环境变量是操作系统和用户设置的一些动态键值对,为运行的程序提供配置信息,理解环境变量对于系统管理、软件开发都很重要,下面小编就为大家详细介绍一下吧... 目录前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变