详解kobject_uevent_env上报机制

示例

比如插拔sim卡会打印如下信息

[90133.351745] report_usim_event: usim uevent [USIM_NAME=usim0 USIM_EVENT=plugout] is sent
[90133.380554] USIM_NAME=usim0, USIM_EVENT=plugout
[90133.380645] serial_atcmd at+cfun=5

[90176.496063] report_usim_event: usim uevent [USIM_NAME=usim0 USIM_EVENT=plugin] is sent
[90176.524383] USIM_NAME=usim0, USIM_EVENT=plugin
[90176.533599] CUR CFUN = 5
[90176.533691] serial_atcmd at+cfun=0
[90181.583862] serial_atcmd at+cfun=1

这是热插拔触后,触发的事件给应用层,然后执行的脚本,跟上面的信息是对上的

#!/bin/sh

#usim0_state=`cat /sys/class/usim_event/usim0/state`
usim_name=`echo $USIM_NAME`
usim_event=`echo $USIM_EVENT`

if [ -d /sys/devices/virtual/usim_event/usim0 -o -d /sys/devices/virtual/usim_event/usim1 ]; then
        echo "USIM_NAME=$usim_name, USIM_EVENT=$usim_event" > /dev/kmsg
        if [ "$usim_event" == "plugin" ]; then
                cur_cfun=`serial_atcmd "AT+CFUN?" | grep CFUN | awk -F " " '{print $2}'`
                echo "CUR CFUN =" $cur_cfun > /dev/kmsg
                if [ "$cur_cfun" != "0" ]; then
                        echo "serial_atcmd at+cfun=0" > /dev/kmsg
                        serial_atcmd at+cfun=0
                        sleep 5
                fi
                echo "serial_atcmd at+cfun=1" > /dev/kmsg
                serial_atcmd at+cfun=1
                ubus send "usim.event" '{"status":1}'
        elif [ "$usim_event" == "plugout" ]; then
                echo "serial_atcmd at+cfun=5" > /dev/kmsg
                serial_atcmd at+cfun=5
                ubus call network.interface.wan0 remove
                ubus call network.interface.wan60 remove
                ubus send "usim.event" '{"status":0}'
        fi
elif [ -d /sys/devices/virtual/usim_event/usimtray ]; then
        echo "USIM_NAME=$usim_name, USIM_EVENT=$usim_event" > /dev/kmsg
fi

上报事件的函数如下,这个可以是中断脚来检测,或者上层通过attr属性主动调用触发

static void report_usim_event(struct usim_event_device *uedev, int state)
{
	char name_buf[50];
	char *env[3];
	snprintf(name_buf, sizeof(name_buf), "USIM_NAME=%s", uedev->name);

	env[0] = name_buf;
	if (strcmp("usimtray", uedev->name) == 0)
		env[1] = state ? "USIM_EVENT=trayPlugin" :
			"USIM_EVENT=trayPlugout";
	else
		env[1] = state ? "USIM_EVENT=plugin" : "USIM_EVENT=plugout";
	env[2] = NULL;

	kobject_uevent_env(&uedev->dev->kobj, KOBJ_CHANGE, env);
	usim_event_debug(GE_DEBUG_INFO, "%s: usim uevent [%s %s] is sent\n",
			 __func__, env[0], env[1]);
}

.config

uevent相关配置如下

CONFIG_NET=y
CONFIG_UEVENT_HELPER=y
CONFIG_UEVENT_HELPER_PATH=""

uevent把事件上报给用户空间的两种途径:根据上述配置来选择用那种
1.通过kmod模块,直接调用用户空间的可执行程序或脚本。
2..通过netlink通信机制,将事件从内核空间传递到用户空间。

定义了CONFIG_UEVENT_HELPER和CONFIG_UEVENT_HELPER_PATH不为空,就会掉用户空间的程序

/**
 * kobject_uevent_env - send an uevent with environmental data
 *
 * @kobj: struct kobject that the action is happening to
 * @action: action that is happening
 * @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;

	/*
	 * Mark "remove" event done regardless of result, for some subsystems
	 * do not want to re-trigger "remove" event via automatic cleanup.
	 */
	if (action == KOBJ_REMOVE)
		kobj->state_remove_uevent_sent = 1;

	pr_debug("kobject: '%s' (%p): %s\n",
		 kobject_name(kobj), kobj, __func__);

	/* search the kset we belong to */
	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*/
	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;
		}

	/* 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;
	}

	/* environment buffer */
	env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
	if (!env)
		return -ENOMEM;

	/* complete object path */
	devpath = kobject_get_path(kobj, GFP_KERNEL);
	if (!devpath) {
		retval = -ENOENT;
		goto exit;
	}

	/* default keys */
	retval = add_uevent_var(env, "ACTION=%s", action_string);
	if (retval)
		goto exit;
	retval = add_uevent_var(env, "DEVPATH=%s", devpath);
	if (retval)
		goto exit;
	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;
		}
	}

	/* 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;
		}
	}

	switch (action) {
	case KOBJ_ADD:
		/*
		 * Mark "add" event so we can make sure we deliver "remove"
		 * event 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.
		 */
		kobj->state_add_uevent_sent = 1;
		break;

	case KOBJ_UNBIND:
		zap_modalias_env(env);
		break;

	default:
		break;
	}

	mutex_lock(&uevent_sock_mutex);
	/* we will send an event, so request a new sequence number */
	retval = add_uevent_var(env, "SEQNUM=%llu", ++uevent_seqnum);
	if (retval) {
		mutex_unlock(&uevent_sock_mutex);
		goto exit;
	}
	retval = kobject_uevent_net_broadcast(kobj, env, action_string,
					      devpath);
	mutex_unlock(&uevent_sock_mutex);

#ifdef CONFIG_UEVENT_HELPER
	/* call uevent_helper, usually only enabled during early boot */
	if (uevent_helper[0] && !kobj_usermode_filter(kobj)) {
		struct subprocess_info *info;

		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 = init_uevent_argv(env, subsystem);
		if (retval)
			goto exit;

		retval = -ENOMEM;
		info = call_usermodehelper_setup(env->argv[0], env->argv,
						 env->envp, GFP_KERNEL,
						 NULL, cleanup_uevent_env, env);
		if (info) {
			retval = call_usermodehelper_exec(info, UMH_NO_WAIT);
			env = NULL;	/* freed by cleanup_uevent_env */
		}
	}
#endif

exit:
	kfree(devpath);
	kfree(env);
	return retval;
}

定义了CONFIG_NET就会走netlink

static int kobject_uevent_net_broadcast(struct kobject *kobj,
					struct kobj_uevent_env *env,
					const char *action_string,
					const char *devpath)
{
	int ret = 0;

#ifdef CONFIG_NET
	const struct kobj_ns_type_operations *ops;
	const struct net *net = NULL;

	ops = kobj_ns_ops(kobj);
	if (!ops && kobj->kset) {
		struct kobject *ksobj = &kobj->kset->kobj;

		if (ksobj->parent != NULL)
			ops = kobj_ns_ops(ksobj->parent);
	}

	/* kobjects currently only carry network namespace tags and they
	 * are the only tag relevant here since we want to decide which
	 * network namespaces to broadcast the uevent into.
	 */
	if (ops && ops->netlink_ns && kobj->ktype->namespace)
		if (ops->type == KOBJ_NS_TYPE_NET)
			net = kobj->ktype->namespace(kobj);

	if (!net)
		ret = uevent_net_broadcast_untagged(env, action_string,
						    devpath);
	else
		ret = uevent_net_broadcast_tagged(net->uevent_sock->sk, env,
						  action_string, devpath);
#endif

	return ret;
}

netlink

netlink就是socket的一种表现方式,在内核中通过NETLINK_KOBJECT_UEVENT参数,用netlink_kernel_create来创建一个netlink的socket

/lib/kobject_uevent.c
static int uevent_net_init(struct net *net)
{
	struct uevent_sock *ue_sk;
	struct netlink_kernel_cfg cfg = {
		.groups	= 1,
		.input = uevent_net_rcv,
		.flags	= NL_CFG_F_NONROOT_RECV
	};

	ue_sk = kzalloc(sizeof(*ue_sk), GFP_KERNEL);
	if (!ue_sk)
		return -ENOMEM;

	ue_sk->sk = netlink_kernel_create(net, NETLINK_KOBJECT_UEVENT, &cfg);
	if (!ue_sk->sk) {
		pr_err("kobject_uevent: unable to create netlink socket!\n");
		kfree(ue_sk);
		return -ENODEV;
	}

	net->uevent_sock = ue_sk;

	/* Restrict uevents to initial user namespace. */
	if (sock_net(ue_sk->sk)->user_ns == &init_user_ns) {
		mutex_lock(&uevent_sock_mutex);
		list_add_tail(&ue_sk->list, &uevent_sock_list);
		mutex_unlock(&uevent_sock_mutex);
	}

	return 0;
}

static void uevent_net_exit(struct net *net)
{
	struct uevent_sock *ue_sk = net->uevent_sock;

	if (sock_net(ue_sk->sk)->user_ns == &init_user_ns) {
		mutex_lock(&uevent_sock_mutex);
		list_del(&ue_sk->list);
		mutex_unlock(&uevent_sock_mutex);
	}

	netlink_kernel_release(ue_sk->sk);
	kfree(ue_sk);
}

static struct pernet_operations uevent_net_ops = {
	.init	= uevent_net_init,
	.exit	= uevent_net_exit,
};

static int __init kobject_uevent_init(void)
{
	return register_pernet_subsys(&uevent_net_ops);
}


postcore_initcall(kobject_uevent_init);

在用户层,通过一个进程来创建一个netlink,这个应用程序可以是mdev,udev或者openwrt的procd,这三种方式都是基于netlink来管理热插拔的,比如procd通过NETLINK_KOBJECT_UEVENT参数来通过socket系统调用创建一个fd来与内核的netlink交互数据,内核上报uevent被这个进程接受后,然后调用/etc/hotplug.d/的各脚本来执行热插拔操作

void hotplug(char *rules)
{
        struct sockaddr_nl nls = {};
        int nlbufsize = 512 * 1024;

        rule_file = strdup(rules);
        nls.nl_family = AF_NETLINK;
        nls.nl_pid = 0;
        nls.nl_groups = -1;

        if ((hotplug_fd.fd = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT)) == -1) {
                ERROR("Failed to open hotplug socket: %m\n");
                exit(1);
        }
        if (bind(hotplug_fd.fd, (void *)&nls, sizeof(struct sockaddr_nl))) {
                ERROR("Failed to bind hotplug socket: %m\n");
                exit(1);
        }

        if (setsockopt(hotplug_fd.fd, SOL_SOCKET, SO_RCVBUFFORCE, &nlbufsize, sizeof(nlbufsize)))
                ERROR("Failed to resize receive buffer: %m\n");

        json_script_init(&jctx);
        queue_proc.cb = queue_proc_cb;
        uloop_fd_add(&hotplug_fd, ULOOP_READ);
}

 当device_add添加设备时会调用kobject_uevent(&dev->kobj, KOBJ_ADD);来上报事件给procd的netlink,然后根据是否有主从设备号等信息,来创建设备节点文件

static void handle_makedev(struct blob_attr *msg, struct blob_attr *data)
{
        unsigned int oldumask = umask(0);
        static struct blobmsg_policy mkdev_policy[3] = {
                { .type = BLOBMSG_TYPE_STRING },
                { .type = BLOBMSG_TYPE_STRING },
                { .type = BLOBMSG_TYPE_STRING },
        };
        struct blob_attr *tb[3];
        char *minor = hotplug_msg_find_var(msg, "MINOR");
        char *major = hotplug_msg_find_var(msg, "MAJOR");
        char *subsystem = hotplug_msg_find_var(msg, "SUBSYSTEM");

        blobmsg_parse_array(mkdev_policy, 3, tb, blobmsg_data(data), blobmsg_data_len(data));
        if (tb[0] && tb[1] && minor && major && subsystem) {
                mode_t m = S_IFCHR;
                char *d = strdup(blobmsg_get_string(tb[0]));

                d = dirname(d);
                mkdir_p(d, 0755);
                free(d);

                if (!strcmp(subsystem, "block"))
                        m = S_IFBLK;
                mknod(blobmsg_get_string(tb[0]),
                                m | strtoul(blobmsg_data(tb[1]), NULL, 8),
                                makedev(atoi(major), atoi(minor)));
                if (tb[2])
                        chgrp_target(tb[2], tb[0]);
        }
        umask(oldumask);
}

或者收到sim的切换事件后,调用最上面的位于/etc/hotplug.d里的sim热插拔脚本,来执行对应的行为

你可能感兴趣的:(Linux内核之驱动,服务器,linux,运维)