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

相关文章

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

Retrieval-based-Voice-Conversion-WebUI模型构建指南

一、模型介绍 Retrieval-based-Voice-Conversion-WebUI(简称 RVC)模型是一个基于 VITS(Variational Inference with adversarial learning for end-to-end Text-to-Speech)的简单易用的语音转换框架。 具有以下特点 简单易用:RVC 模型通过简单易用的网页界面,使得用户无需深入了

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

图神经网络模型介绍(1)

我们将图神经网络分为基于谱域的模型和基于空域的模型,并按照发展顺序详解每个类别中的重要模型。 1.1基于谱域的图神经网络         谱域上的图卷积在图学习迈向深度学习的发展历程中起到了关键的作用。本节主要介绍三个具有代表性的谱域图神经网络:谱图卷积网络、切比雪夫网络和图卷积网络。 (1)谱图卷积网络 卷积定理:函数卷积的傅里叶变换是函数傅里叶变换的乘积,即F{f*g}