/*
* kobject_uevent - notify userspace by sending an uevent
*
* @action: action that is happening
* @kobj: struct kobject that the action is happening to
*
* Returns 0 if kobject_uevent() is completed with success or the
* corresponding error when it fails.
*/
int kobject_uevent(struct kobject *kobj, enum kobject_action action)
{
return kobject_uevent_env(kobj, action, NULL);
}
EXPORT_SYMBOL_GPL(kobject_uevent);
/*
* kobject_uevent_env - send an uevent with environmental data
*
* @action: action that is happening
* @kobj: struct kobject that the action is happening to
* @envp_ext: pointer to environmental data
*
* Returns 0 if kobject_uevent_env() is completed with success or the
* corresponding error when it fails.
*/
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 = 0;
int retval = 0;
#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 */
//找到其所属的 kset容器,如果没找到就从其父kobj找,一直持续下去,直到父kobj不存在
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;
}
//在本例中是devices_kset容器, 后面将列出devices_kset的定义
kset = top_kobj->kset;
uevent_ops = kset->uevent_ops;
//回调 uevent_ops->filter()例程,本例中是dev_uevent_filter()例程,先检查是否uevent suppress
//如果uevent suppress,则直接返回
/* skip the event, if uevent_suppress is set*/
if (kobj->uevent_suppress) {
pr_debug("kobject: '%s' (%p): %s: uevent_suppress "
"caused the event to drop!\n", kobject_name(kobj), kobj, __func__);
return 0;
}
/* skip the event, if the filter returns zero. */
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 0;
}
//回调 uevent_ops-> name (),本例中是dev_uevent_name()例程,获取bus或class的名字
/* 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来获得
/* 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) {
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;
mutex_lock(&uevent_sock_mutex);
//增加event序列号的值,并输出到环境变量的buffer。该序列号可以从/sys/kernel/uevent_seqnum属性文件读取,
//uevent_seqnum属性文件及/sys/kernel/目录是怎样产生的呢?
/* we will send an event, so request a new sequence number */
retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)++uevent_seqnum);
if (retval) {
mutex_unlock(&uevent_sock_mutex);
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
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, 1))
continue;
/* 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_filtered(uevent_sock, skb, 0, 1, GFP_KERNEL, kobj_bcast_filter, kobj);
/* ENOBUFS should be handled in userspace */
if (retval == -ENOBUFS || retval == -ESRCH)
retval = 0;
} else
retval = -ENOMEM;
}
#endif
mutex_unlock(&uevent_sock_mutex);
//对于嵌入式系统来说,busybox采用的是mdev,在系统启动脚本rcS 中会使用
//echo/sbin/mdev > /proc/sys/kernel/hotplug命令,而这个 hotplug文件通过一定的方法映射到了
//uevent_helper[]数组,所以uevent_helper[] = “/sbin/mdev” 。所以对于采用busybox的嵌入式系统来说会执行里面的代码,
//而pc机不会。也就是说内核会call用户空间的/sbin/mdev这个应用程序来做动作
/* 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;
//调用用户空间应用程序, 将事件通知到用户空间, UMH_WAIT_EXEC表明等待应用程序处理完
retval = call_usermodehelper(argv[0], argv, env->envp, UMH_WAIT_EXEC); //调用argv[0]指定的用户空间的应用程序
}
exit:
kfree(devpath);
kfree(env);
return retval;
}
EXPORT_SYMBOL_GPL(kobject_uevent_env);
//core_initcall(ksysfs_init);
static int __init ksysfs_init(void)
//获得一个kobj,无parent,故生成/sys/kernel目录
+-- kernel_kobj = kobject_create_and_add("kernel", NULL);
//在/sys/kernel目录下生成一些属性文件(包含在属性组里)
+-- sysfs_create_group(kernel_kobj, &kernel_attr_group); //kernel_attr_group...
//生成/sys/kernel/notes二进制属性文件,可用来读取二进制 kernel.notes section
+-- if (notes_size > 0) {
notes_attr.size = notes_size;
error = sysfs_create_bin_file(kernel_kobj, & notes_attr);
if (error)
goto group_exit;
}
static struct attribute_group kernel_attr_group = {
.attrs = kernel_attrs,
};
static struct attribute * kernel_attrs[] = {
&fscaps_attr.attr,
#if defined(CONFIG_HOTPLUG)
&uevent_seqnum_attr.attr, //用于获取event序列号
&uevent_helper_attr.attr, //uevent_helper_attr... 用于存取用户提供的程序
#endif
#ifdef CONFIG_PROFILING
&profiling_attr.attr,
#endif
#ifdef CONFIG_KEXEC
&kexec_loaded_attr.attr,
&kexec_crash_loaded_attr.attr,
&kexec_crash_size_attr.attr,
&vmcoreinfo_attr.attr,
#endif
NULL
};
#define KERNEL_ATTR_RW(_name) \
static struct kobj_attribute _name##_attr = \
__ATTR(_name, 0644, _name##_show, _name##_store)
/* uevent helper program, used during early boot */
static ssize_t uevent_helper_show(struct kobject *kobj, struct kobj_attribute *attr, char *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[count] = '\0';
if (count && uevent_helper[count-1] == '\n')
uevent_helper[count-1] = '\0';
return count;
}
KERNEL_ATTR_RW(uevent_helper);
//devices_kset->uevent_ops的初始化
static int __init kernel_init(void * unused)
+-- do_basic_setup();
+-- driver_init();
+-- devtmpfs_init();
+-- devices_init();
+-- devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
+-- kset = kset_create(name, uevent_ops, parent_kobj);
+-- kset = kzalloc(sizeof(*kset), GFP_KERNEL);
+-- kobject_set_name(&kset->kobj, name);
/*
* kset->uevent_ops被初始化为uevent_ops, uevent_ops为kset_create()
* 传入的参数实际上就是device_uevent_ops
* static const struct kset_uevent_ops device_uevent_ops = {
* .filter = dev_uevent_filter,
* .name = dev_uevent_name,
* .uevent = dev_uevent,
* };
*/
+-- kset->uevent_ops = uevent_ops;
+-- kset->kobj.parent = parent_kobj;
+-- kset->kobj.ktype = &kset_ktype;
+-- kset->kobj.kset = NULL;
+-- error = kset_register(kset);
+-- dev_kobj = kobject_create_and_add("dev", NULL);
+-- sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj);
+-- sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj);
+-- buses_init();
+-- classes_init();
+-- firmware_init();
+-- hypervisor_init();
+-- platform_bus_init();
+-- cpu_dev_init();
+-- memory_dev_init();
//1. 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);
//bus或class如果都没设置,那么也不会发送uevent事件
if (dev->bus)
return 1;
if (dev->class)
return 1;
}
return 0;
}
static struct kobj_type device_ktype = {
.release = device_release,
.sysfs_ops = &dev_sysfs_ops,
.namespace = device_namespace,
};
//2.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)
return dev->bus->name; //如果设置了bus,则返回bus的name
if (dev->class)
return dev->class->name; //如果没有设置bus而设置了class ,则返回 class 的name
return NULL;
}
//3. dev_uevent
//用于输出一定的环境变量
static int dev_uevent(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env)
{
struct device *dev = kobj_to_dev(kobj);
int retval = 0;
/* add device node properties if present */
//本例中rtc0有devt,所以会输出下面的环境变量
if (MAJOR(dev->devt)) {
const char *tmp;
const char *name;
umode_t mode = 0;
add_uevent_var(env, "MAJOR=%u", MAJOR(dev->devt));
add_uevent_var(env, "MINOR=%u", MINOR(dev->devt));
name = device_get_devnode(dev, &mode, &tmp);
if (name) {
add_uevent_var(env, "DEVNAME=%s", name);
kfree(tmp);
if (mode)
add_uevent_var(env, "DEVMODE=%#o", mode & 0777);
}
}
if (dev->type && dev->type->name)
add_uevent_var(env, "DEVTYPE=%s", dev->type->name);
if (dev->driver)
add_uevent_var(env, "DRIVER=%s", dev->driver->name);
/* Add common DT information about the device */
of_device_uevent(dev, env);
/* have the bus specific function add its stuff */
//若果设置了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);
}
/* have the class specific function add its stuff */
//本例中设置了 class ,走class->dev_uevent()
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);
}
/* have the device type specific function add its stuff */
//如果设置了dev->type, 走dev->type->uevent()
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;
}
//要实现hotplug机制,需要有用户空间的程序配合才行。对于pc机的linux系统,采用的是udevd服务程序,
//其通过监听NETLINK_KOBJECT_UEVENT获得内核发出的uevent事件和环境变量,然后再查找匹配的udev rules,
//根据找到的rules做动作,udev的具体实现原理可参照网上的一些文章
//在每个注册的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加载驱动程序,等等。从而实现自动生成设备节点、加载驱动程序等等这些热拔插机制。
//udev -- systemd (url = git://anongit.freedesktop.org/systemd/systemd) systemd/src/udev/*
//mdev -- busybox (url = git://busybox.net/busybox.git)
//vold -- android (url = http://android.googlesource.com/platform/system/vold.git)