分类: LINUX
2014-06-06 09:00:07
原文地址:Linux设备驱动模型中的热插拔机制及实验 作者:zhuqing_739
本文转载地址:
本人CU博客中的文章,本来是希望CU给推荐到首页的,结果周五不知道CU博客出了啥问题,居然没什么更新。所以我干脆把再把它放到这里,因为话题涉及设备驱动模块自动加载,本版已经有同学问过这方面的问题。
当然设备驱动程序一般不会和这些太底层的kobject/kset家伙打交道,因为更高层次的device,bus和driver把kobject/kset那一层的细节实现都给封装了起来。所以设备热插拔的uevent事件最终的源头来自于device_add,本帖这里肯定不会讨论device与driver如何绑定那一摊子事情。下面看看device_add的源码,是如何实现uevent机制的:
int device_add(struct device *dev) { ... kobject_uevent(&dev->kobj, KOBJ_ADD); ... }
热插拔的核心实现就那一个函数调用,这里device_add对应的是KOBJ_ADD,那么移除设备自然对应KOBJ_REMOVE了。kobject_uevent函数最终调用的是kobject_uevent_env,后者才是真正干事的伙计。
下面给出kobject_uevent_env函数的核心框架:
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action, char *envp_ext[]) { ... #if defined(CONFIG_NET) /* send netlink message */ ... #endif /* call uevent_helper, usually only enabled during early boot */ if (uevent_helper[0] && !kobj_usermode_filter(kobj)) { char *argv [3]; 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; retval = call_usermodehelper(argv[0], argv, env->envp, UMH_WAIT_EXEC); } ... }
怎么样,够简洁吧,其实看实际的代码比这要郁闷地多,不过骨架清晰就行了。代码中的netlink message就不用多说了吧,给udev发通知用(有时间的话可以分析分析udev的代码)。本帖重点讨论后半段的if (uevent_helper[0] && !kobj_usermode_filter(kobj))代码,这里的核心调用是call_usermodehelper,这个函数最有意思的地方就在于在内核空间调用用户空间的程序,它的详细实现机制在书中已经讲得很多,这里就不再赘述了。call_usermodehelper在kobject_uevent_env函数中要调用的用户空间程序由uevent_helper[0]来指定,所以如果我们能控制这个uevent_helper[0],就能接收到设备加入系统移出系统等事件。那个if中的kobj_usermode_filter条件一般都会满足(除非这是个特别注意个人隐私的设备,那就不好说了,人家偷偷加入系统就是不想让你知道你也没有办法,但是udev还是能知道的)。
貌似要通过内核配置来指定,我看了一下我系统中Linux目录下的.config文件,找到了下面这行:char uevent_helper[UEVENT_HELPER_PATH_LEN] = CONFIG_UEVENT_HELPER_PATH;
# # Generic Driver Options # CONFIG_UEVENT_HELPER_PATH=""
丫的,居然没指定,那么uevent_helper[0]="",这样的话我们在kobject_uevent_env函数中的那个if语句就没法满足了,看来要重新配置再编译内核了。不过想想sysfs这么强大,内核开发的那帮人好歹给留个用户空间的接口出来吧,一查看还真有:
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[count] = '\0'; if (count && uevent_helper[count-1] == '\n') uevent_helper[count-1] = '\0'; return count; }有个uevent_helper文件不是?那么我们现在可以把我们用户空间的程序给打进去了,我打算做个最简单的脚本/sbin/myhotplug,这个脚本只干一件事,在/home/dennis目录下生成一个hotplug文件:
#!/bin/sh cd /home/dennis touch hotplug然后把这个脚本程序的文件名给打入到内核空间的uevent_helper[0]上:
root@build-server:/sys/kernel# echo "/sbin/myhotplug" > uevent_helper root@build-server:/sys/kernel# cat uevent_helper /sbin/myhotplug好了,现在检查一下你的/home/dennis目录下面有没有hotplug这个文件,有的话就删掉,否则怎么知道是新生成的呢。现在,找个U盘插到你的电脑里,然后再看一下/home/dennis目录,有个hotplug文件对吧?如果你现在删除这个文件,再把U盘给拔了,你会再次发现这个文件。这意味着什么,意味着你可以轻而易举地捕捉到设备加入/移出系统等事件,如果你的脚本足够智能,那么你就会想到很多很有创意的玩法对吧?