内核模块的热插拔事件的通知基于uevent机制。

当kobject的状态发生改变(如,add, remove等)时,会通知用户空间,用户空间接收到事件通知后可以做相应的处理。
uevent把事件上报给用户空间的两种途径:
  1.通过kmod模块,直接调用用户空间的可执行程序或脚本。
  2.通过netlink通信机制,将事件从内核空间传递到用户空间。

linux-3.5/include/linux/kobject.h

//    ADD/REMOVE,Kobject(或上层数据结构)的添加/移除事件。
// ONLINE/OFFLINE,Kobject(或上层数据结构)的上线/下线事件,其实是是否使能。
// CHANGE,Kobject(或上层数据结构)的状态或者内容发生改变。
// MOVE,Kobject(或上层数据结构)更改名称或者更改Parent(意味着在sysfs中更改了目录结构)。
//CHANGE,如果设备驱动需要上报的事件不再上面事件的范围内,或者是自定义的事件,可以使用该event,并携带相应的参数。
enum kobject_action {
KOBJ_ADD,
KOBJ_REMOVE,
KOBJ_CHANGE,
KOBJ_MOVE,
KOBJ_ONLINE,
KOBJ_OFFLINE,
KOBJ_MAX
}; #define UEVENT_HELPER_PATH_LEN 256
#define UEVENT_NUM_ENVP 32 /* number of env pointers */
#define UEVENT_BUFFER_SIZE 2048 /* buffer for the variables */ //在利用kmod模块向用户空间上报event事件时,会直接执行用户空间的可执行文件。而在linux系统中,可执行文件的执行,依赖于环境变量,
//因此kobj_uevent_env用于组织此次事件上报是的环境变量。
struct kobj_uevent_env {
char *envp[UEVENT_NUM_ENVP];//指针数组,用于保存每个环境变量的地址,最多支持32个环境变量
int envp_idx;//用户访问环境变量数组的索引
char buf[UEVENT_BUFFER_SIZE];//保存环境变量的buffer
int buflen;//???
}; struct kset_uevent_ops {
int (* const filter)(struct kset *kset, struct kobject *kobj);//当任何kobject需要上报uevent时,它所属的kset可以通过filter借口过滤,阻止不希望上报的uevent。
const char *(* const name)(struct kset *kset, struct kobject *kobj);//该接口可以返回kset的名称。如果一个kset没有合法的名称,则其下的所有kobject将不允许上报uevent
int (* const uevent)(struct kset *kset, struct kobject *kobj,
struct kobj_uevent_env *env);//当任何kobject需要上报uevent时,它所属的kset可以通过该接口统一为这些event添加环境变量。
//因为很多时候上报uevent时的环境变量都是相同的,因此可以由kset统一处理,就不需要让每个Kobject独自添加了。
};
#if defined(CONFIG_HOTPLUG)
int kobject_uevent(struct kobject *kobj, enum kobject_action action);
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
char *envp[]); __printf(, )
int add_uevent_var(struct kobj_uevent_env *env, const char *format, ...); int kobject_action_type(const char *buf, size_t count,
enum kobject_action *type); kobject_uevent_env ,以 envp 为环境变量,上报一个指定action的uevent。环境变量的作用是为执行用户空间程序指定运行环境。
int kobject_uevent(struct kobject *kobj, enum kobject_action action)
{
return kobject_uevent_env(kobj, action, NULL);
} 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];
const char *devpath = NULL;
const char *subsystem;
struct kobject *top_kobj;
struct kset *kset;
const struct kset_uevent_ops *uevent_ops;
int i = ;
int retval = ;
#ifdef CONFIG_NET
struct uevent_sock *ue_sk;
#endif pr_debug("kobject: '%s' (%p): %s\n",
kobject_name(kobj), kobj, __func__); /* search the kset we belong to */
//1.查找当前kobject或其parent是否从属于某个kset;如果都不从属于某个kset,则返回错误。(说明一个kobject若没有加入kset,是不会上报uevent的)
top_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;
} kset = top_kobj->kset;
uevent_ops = kset->uevent_ops; /* skip the event, if uevent_suppress is set*/
//2.查看kobj->uevent_suppress是否被设置;如果设置了,则忽略所有的uevent上报,并返回0.
if (kobj->uevent_suppress) {
pr_debug("kobject: '%s' (%p): %s: uevent_suppress "
"caused the event to drop!\n",
kobject_name(kobj), kobj, __func__);
return ;
}
/* skip the event, if the filter returns zero. */
//3.如果所属的kset有uevent_ops->filter,则调用该函数,若该函数返回0,则过滤此次上报。(kset 可以通过filter接口过滤不希望上报的event)
if (uevent_ops && uevent_ops->filter)
if (!uevent_ops->filter(kset, kobj)) {
pr_debug("kobject: '%s' (%p): %s: filter function "
"caused the event to drop!\n",
kobject_name(kobj), kobj, __func__);
return ;
} /* originating subsystem */
//4.判断所属的kset是否有合法的名称,若uevent_ops->name存在就用其返回的名称作为subsystem;若uevent_ops->name不存在就用kset本身的kobject的名称作为subsystem;
//若没有合法的名称,则不上报uevent
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 ;
} /* environment buffer */
//5.分配一个此次上报的用于保存环境变量的buffer,
env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
if (!env)
return -ENOMEM; /* complete object path */
//6.获得该kobject在sysfs中路径
devpath = kobject_get_path(kobj, GFP_KERNEL);
if (!devpath) {
retval = -ENOENT;
goto exit;
} /* default keys */
//7.添加ACTION到env
retval = add_uevent_var(env, "ACTION=%s", action_string);
if (retval)
goto exit;
//8.添加DEVPATH(kobject路径信息)到env
retval = add_uevent_var(env, "DEVPATH=%s", devpath);
if (retval)
goto exit;
//9.添加SUBSYSTEM到env
retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
if (retval)
goto exit; /* keys passed in from the caller */
//10.如果传入的envp_ext不空,则解析传入的环境变量中,同样调用add_uevent_var接口,添加到env指针中
if (envp_ext) {
for (i = ; envp_ext[i]; i++) {
retval = add_uevent_var(env, "%s", envp_ext[i]);
if (retval)
goto exit;
}
} /* let the kset specific function add its stuff */
//11.如果 uevent_ops->uevent 存在,调用该接口,添加kset统一的环境变量到env指针
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.
*/
//12.根据ACTION的类型,设置kobj->state_add_uevent_sent和kobj->state_remove_uevent_sent变量,以记录正确的状态
if (action == KOBJ_ADD)
kobj->state_add_uevent_sent = ;
else if (action == KOBJ_REMOVE)
kobj->state_remove_uevent_sent = ; mutex_lock(&uevent_sock_mutex);
/* we will send an event, so request a new sequence number */
//13.调用add_uevent_var接口,添加格式为"SEQNUM=%llu”的序列号
retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)++uevent_seqnum);
if (retval) {
mutex_unlock(&uevent_sock_mutex);
goto exit;
}
//14.如果定义了"CONFIG_NET”,则使用netlink发送该uevent
#if defined(CONFIG_NET)
/* send netlink message */
list_for_each_entry(ue_sk, &uevent_sock_list, list) {
struct sock *uevent_sock = ue_sk->sk;
struct sk_buff *skb;
size_t len; if (!netlink_has_listeners(uevent_sock, ))
continue; /* allocate message with the maximum possible size */
len = strlen(action_string) + strlen(devpath) + ;
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 = ; i < env->envp_idx; i++) {
len = strlen(env->envp[i]) + ;
scratch = skb_put(skb, len);
strcpy(scratch, env->envp[i]);
} NETLINK_CB(skb).dst_group = ;
retval = netlink_broadcast_filtered(uevent_sock, skb,
, , GFP_KERNEL,
kobj_bcast_filter,
kobj);
/* ENOBUFS should be handled in userspace */
if (retval == -ENOBUFS || retval == -ESRCH)
retval = ;
} else
retval = -ENOMEM;
}
#endif
mutex_unlock(&uevent_sock_mutex); /* call uevent_helper, usually only enabled during early boot */
//15.以uevent_helper、 subsystem 以及添加了标准环境变量(HOME=/,PATH=/sbin:/bin:/usr/sbin:/usr/bin)的env指针为参数,
// 调用kmod模块提供的call_usermodehelper函数,上报uevent。
if (uevent_helper[] && !kobj_usermode_filter(kobj)) {
char *argv []; argv [] = uevent_helper;//在/sys/kernel/uevent_helper文件中可以存入用户空间可执行程序的路径,当内核有事件发生时,将会执行该程序
argv [] = (char *)subsystem;
argv [] = 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; retval = call_usermodehelper(argv[], argv,
env->envp, UMH_WAIT_EXEC);
} exit:
kfree(devpath);
kfree(env);
return retval;
}

uevent模块通过kmod上报uevent时,会通过call_usermodehelper函数,调用用户空间的可执行文件(或者脚本,简称uevent helper)处理该event。
而该uevent helper的路径保存在uevent_helper数组中。

可以在编译内核时,通过CONFIG_UEVENT_HELPER_PATH配置项,静态指定uevent helper。
但这种方式会为每个event fork一个进程,随着内核支持的设备数量的增多,这种方式在系统启动时将会是致命的(可以导致内存溢出等)。
因此只有在早期的内核版本中会使用这种方式,现在内核不再推荐使用该方式。因此内核编译时,需要把该配置项留空。

在系统启动后,大部分的设备已经ready,可以根据需要,重新指定一个uevent helper,以便检测系统运行过程中的热拔插事件。
这可以通过把helper的路径写入到"/sys/kernel/uevent_helper"文件中实现。
实际上,内核通过sysfs文件系统的形式,将uevent_helper数组开放到用户空间,供用户空间程序修改访问,具体可参考"./kernel/ksysfs.c”中相应的代码。

在/etc/init.d/rcS脚本中添加 echo "/sbin/mdev" > /proc/sys/kernel/hotplug,会发现cat /sys/kernel/uevent_helper 即是/sbin/mdev。
说明/proc/sys/kernel/hotplug中的可执行文件路径最终还是会写到/sys/kernel/uevent_helper中。

自己手动echo "/kernel/main" > uevent_helper(之前的/sbin/mdev会被覆盖),当lsmod、rmmod时,/sys/kernel/uevent_helper中的/kernel/main会执行,
表明事件已经上报给用户空间。

Q1:用户空间怎样去识别上报的事件到底是什么事件?下一步研究

call_usermodehelper函数能够方便的在内核中直接新建和运行用户空间的程序,并且该程序有root权限。
call_usermodeheler函数的参数用法和execve函数一致。
call_usermodehelper()->call_usermodehelper_exec()

最新文章

  1. PHP curl报错“Problem (2) in the Chunked-Encoded data”解决方案
  2. UDP丢包严重
  3. Hadoop 2.6.0 集群部署
  4. JAVA:类,对象,成员属性,成员方法,构造方法,类变量,类方法&lt;2&gt;
  5. Java字符流和字节流对文件操作
  6. 对拍 For Linux
  7. MongoDB appendix
  8. uva11600 状压期望dp
  9. html精确定位
  10. oracle事物总结(转)
  11. SQL语句整理1
  12. JVM 学习(二)Java 内存模型、方法内联、逃逸 --- 2019年4月
  13. burp基本使用
  14. Keepalive工作原理
  15. 使用 Django-debug-toolbar 优化Query 提高代码效率
  16. UIView的背景颜色
  17. APP强制退出
  18. win10上Tensorflow的安装教程
  19. C#调用DLL报“试图加载格式不正确的程序”
  20. GBDT理论知识总结

热门文章

  1. [BZOJ2655]calc(拉格朗日插值法+DP)
  2. flask 开发环境搭建
  3. 求斐波那契数的python语言实现---递归和迭代
  4. RxJava 2.x 理解-1
  5. JAVA 基本概念和编码规范
  6. 移植u-boot-2012.4到Tiny6410_1G_Nandflash
  7. 使用Rabbitmq.client反序列化包含Mongo.Bson.ObjectId属性实体类时抛异常
  8. ASP.NET Core 1.0基础之静态文件处理
  9. php漏洞挖掘与代码审计方法
  10. linux导入so文件