Linux 设备模型浅析之 uevent 篇
本文属本人原创,欢迎转载,转载请注明出处。由于个人的见识和能力有限,不可能面
面俱到,也可能存在谬误,敬请网友指出,本人的邮箱是 yzq.seen@gmail.com,博客是
http://zhiqiang0071.cublog.cn。
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, &notes_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, &notes_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 篇(2)的更多相关文章

  1. linux设备模型:扩展篇

    Linux设备模型组件:总线  一.定义:总线是不同IC器件之间相互通讯的通道;在计算机中,一个总线就是处理器与一个或多个不同外设之间的通讯通道;为了设备模型的目的,所有的设备都通过总线相互连接,甚至 ...

  2. Linux设备模型:基础篇

    linux提供了新的设备模型:总线(bus).设备(device).驱动(driver).其中总线是处理器与设备之间通道,在设备模型中,所有的设备都通过总线相连:设备是对于一个设备的详细信息描述,驱动 ...

  3. Linux设备模型——设备驱动模型和sysfs文件系统解读

    本文将对Linux系统中的sysfs进行简单的分析,要分析sysfs就必须分析内核的driver-model(驱动模型),两者是紧密联系的.在分析过程中,本文将以platform总线和spi主控制器的 ...

  4. Linux设备模型(热插拔、mdev 与 firmware)【转】

    转自:http://www.cnblogs.com/hnrainll/archive/2011/06/10/2077469.html 转自:http://blog.chinaunix.net/spac ...

  5. Linux设备模型(总线、设备、驱动程序和类)

    Linux设备驱动程序学习(13) -Linux设备模型(总线.设备.驱动程序和类)[转] 文章的例子和实验使用<LDD3>所配的lddbus模块(稍作修改). 提示:在学习这部分内容是一 ...

  6. Linux设备模型 学习总结

    看LDD3中设备模型一章,觉得思维有些混乱.这里从整体的角度来理理思路.本文从四个方面来总结一些内容: 1.底层数据结构:kobject,kset.2.linux设备模型层次关系:bus_type,d ...

  7. Linux设备模型(总结)

    转:http://www.360doc.com/content/11/1219/16/1299815_173418267.shtml 看了一段时间的驱动编程,从LDD3的hello wrod到后来的字 ...

  8. linux设备模型与内核中的面向对象思想

    linux内核用C语言实现了C++面向对象的大部分特性:封装,继承,多态.在看内核的过程中,开始追寻其中的设计思想,封装.继承.多态.恰好今天又在看Linux设备模型,找了很多资料.总结如下: 1.l ...

  9. Linux设备模型之kobject

    阿辉原创,转载请注明出处 参考文档:LDD3-ch14.内核文档Documentation/kobject.txt,本文中使用到的代码均摘自Linux-3.4.75 ----------------- ...

随机推荐

  1. call(this)引起的对闭包的重新理解

    call(this)引起的对闭包的重新理解.md 变量的作用域 全局变量局部变量 Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量. 函数外部无法读取函数内的局部变量. 函数内部 ...

  2. javascript操作html元素CSS属性

    下面先记录一下JS控制CSS所使用的方法. 1.使用javascript更改某个css class的属性... <style type="text/css"> .ori ...

  3. opengl雾开启

    #include <GL/glut.h> #include <stdio.h> #include <iostream> using namespace std; s ...

  4. ASP.NET 学习小记 -- “迷你”MVC实现(1)

    ASP.NET 由于采用了管道式设计,具有很好的扩展性.整个ASP.NET MVC应用框架就是通过扩展ASP.NET实现的.通过ASP.NET的管道设计,我们知道,ASP.NET的扩展点主要是体现在H ...

  5. [C和指针] rearrange.c

    C和指针_程序1.1_重排字符 /* ** 这个程序从标准输入(键盘)中读取输入行并按需求处理后在标准输出(屏幕)中打印, ** 每个输入行的后面一行是该行按需求处理后的输出内容. ** ** 输入的 ...

  6. Word分栏

    情景描述 Word分栏在小论文的撰写过程中是很常用的技术.但是,我们经常会遇到很难过的情况: 一段文字本来是连续分布的,可是当选择了分两栏         之后,开始部分在左边一栏,中间在右边一栏. ...

  7. ArcGIS API for JavaScript 学习笔记(一)

    终于开始了 噩梦一般的ArcGIS 我特别不习惯这种结构化的程序写法 写起来特别吃力 特别是把几个功能整合到同一个页面去的时候. 写程序的时候,一般我喜欢先写个Demo然后再把Demo上面的功能加到页 ...

  8. Provider Communication with Apple Push Notification Service

    This chapter describes the interfaces that providers use for communication with Apple Push Notificat ...

  9. 在安装MySQL Workbentch的时候出现如下问题,已经解决。

    mysql workbench cannot be executed from a path that contains non-ASCII characters. this problem is i ...

  10. unity3d 下操作excel 与打印

    原地址:http://www.colabug.com/thread-1146390-1-1.html 因项目需要,需在unity下实现写入excel.打印功能.因之前在winform里操作过excel ...