openwrt 中的设备节点自动创建

通常在linux 系统中,自动创建设备节点,需要udev 或 mdev 。
但是在openwrt 中并没有 udev 和 mdev ,但它仍然可以自动创建设备节点,那么它的设备节点是怎么自动创建的呢?

在这之前需要了解,内核是怎么样告诉用户进程有新设备添加的。
有一篇文章介绍的很全面:
sysfs、udev 和 它们背后的 Linux 统一设备模型 - 博客 - binsite

摘要如下:

在这里我们不关心 udev 的具体实现,只关心一个问题:当设备插入 / 拔出时,udevd 为何会收到一个相应的 uevent ?这个 uevent 是谁发出的?


我们在 kobject 系列函数中发现了 kobject_uevent 函数,其负责向 user space 发送 uevent 。

这里跟踪一下 kobject_uevent 的调用过程
内核有新设备添加是会调用 device_add 函数,device_add 里面就会 调用kobject_uevent
代码节选如下:

int device_add(struct device *dev)
{
	//省略
	kobject_uevent(&dev->kobj, KOBJ_ADD);
	//省略
}

那么kobject_uevent 里面具体是怎么向 user space 发送 uevent 的呢。
跟踪代码可知当内核配置了CONFIG_NET=y 时,最终调用 netlink_broadcast_filtered 向应用层发送消息。

kobject_uevent(&dev->kobj, KOBJ_ADD);
  return kobject_uevent_env(kobj, action, NULL);
  //节选关键代码
    #if defined(CONFIG_NET)
	/* send netlink message */
	list_for_each_entry(ue_sk, &uevent_sock_list, list) {   // 遍历uevent_sock_list  链表
		struct sock *uevent_sock = ue_sk->sk;
			retval = netlink_broadcast_filtered(uevent_sock, skb,
							    0, 1, GFP_KERNEL,
							    kobj_bcast_filter,
							    kobj);			
	}
    #endif

搜索 uevent_sock_list 可知该链表在 uevent_net_init 函数中对其添加。
uevent_net_init 函数中创建了一个 unit 为 NETLINK_KOBJECT_UEVENT 的 socket

static int uevent_net_init(struct net *net)
{
	//省略
	ue_sk->sk = netlink_kernel_create(net, NETLINK_KOBJECT_UEVENT, &cfg);
	//省略
	mutex_lock(&uevent_sock_mutex);
	list_add_tail(&ue_sk->list, &uevent_sock_list);
	mutex_unlock(&uevent_sock_mutex);
	return 0;
}
netlink_kernel_create
  return __netlink_kernel_create(net, unit, THIS_MODULE, cfg);
    if (sock_create_lite(PF_NETLINK, SOCK_DGRAM, unit, &sock))
		return NULL;

由此可知 ,应用层只需创建一个 family 为 AF_NETLINK , protocol 为 NETLINK_KOBJECT_UEVENT 的 socket ,然后监听并接收该socket 的消息即可。

接下来,找到应用层中处理 NETLINK_KOBJECT_UEVENT 的代码。

grep NETLINK_KOBJECT_UEVENT  . -rn
./hostapd-wpad-mini/hostapd-2018-04-09-fa617ee6/tests/hwsim/netlink.py:105:NETLINK_KOBJECT_UEVENT       = 15
./busybox-1.28.3/util-linux/uevent.c:61:        fd = xsocket(AF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
./procd-2018-03-28-dfb68f85/plug/hotplug.c:596: if ((hotplug_fd.fd = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT)) == -1) {
./procd-2018-03-28-dfb68f85/.pc/001-debug.patch/plug/hotplug.c:593:     if ((hotplug_fd.fd = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT)) == -1) {
./netifd-2018-07-30-a0a1e52e/system-linux.c:252:        if (!create_raw_event_socket(&hotplug_event, NETLINK_KOBJECT_UEVENT, 1,

找到在 procd 和 netfid 中有接收 NETLINK_KOBJECT_UEVENT 的代码,推测应该是在 procd 中,procd 是 opewrt 启动后的第一个进程。

来看一下procd 中处理NETLINK_KOBJECT_UEVENT 的代码。

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

	rule_file = strdup(rules);   // rules = "/etc/hotplug.json"
	nls.nl_family = AF_NETLINK;
	nls.nl_pid = getpid();
	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);
}

这里主要有两个关键的地方,rule_file 和 jctx,
其中rule_file = /etc/hotplug.json , 内容节选如下:
openwrt 中的设备节点自动创建_第1张图片

这里主要处理的地方在 jctx,jctx的定义

static struct json_script_ctx jctx = {
	.handle_var = rule_handle_var,
	.handle_error = rule_handle_error,
	.handle_command = rule_handle_command,
	.handle_file = rule_handle_file,
};

重点关注 rule_handle_command 函数,此函数根据参数name 从 handlers 中找到对应的 handler 函数,并执行。

static void rule_handle_command(struct json_script_ctx *ctx, const char *name,
				struct blob_attr *data, struct blob_attr *vars)
{
	//省略
	for (i = 0; i < ARRAY_SIZE(handlers); i++)
		if (!strcmp(handlers[i].name, name)) {
			if (handlers[i].atomic)
				handlers[i].handler(vars, data);
			else
				queue_add(&handlers[i], vars, data);
			break;
		}

	//省略
}

handlers 是一个结构体数组,定义如下:

static struct cmd_handler {
	char *name;
	int atomic;
	void (*handler)(struct blob_attr *msg, struct blob_attr *data);
	void (*start)(struct blob_attr *msg, struct blob_attr *data);
	void (*complete)(struct blob_attr *msg, struct blob_attr *data, int ret);
} handlers[] = {
	[HANDLER_MKDEV] = {
		.name = "makedev",
		.atomic = 1,
		.handler = handle_makedev,
	},
	//...
	//...
};

handle_makedev 中 调用 mknod 创建设备节点

static void handle_makedev(struct blob_attr *msg, struct blob_attr *data)
{
	//...	
		mknod(blobmsg_get_string(tb[0]),
				m | strtoul(blobmsg_data(tb[1]), NULL, 8),
				makedev(atoi(major), atoi(minor)));
	//...
}

梳理总结:
设备节点自动创建的过程

-> 内核调用 kobject_uevent(&dev->kobj, KOBJ_ADD)
-> 通过 netlink 发送 NETLINK_KOBJECT_UEVENT   
-> 应用层(openwrt 中是 procd)监听 NETLINK_KOBJECT_UEVENT
-> 响应 NETLINK_KOBJECT_UEVENT 消息,根据  /etc/hotplug.json 执行对应操作,
   对于KOBJ_ADD 其实是ACTION=add , 对应就会执行 makedev ,然后再到 handle_makedev 函数
   最终 mknod 函数创建设备节点
    

主要知识关键词 netlink ,对于netlink 我目前并不了解,后续了解一下 netlink

深入研究:为什么一定要调用 device_create 函数才会自动创建设备节点?
device_create - > device_add -> kobject_uevent
在代码中 调用 kobject_uevent 之前有很多条件判断,这些条件只要有一个不满足都会提前返回, device_create 函数可以使条件都满足。

你可能感兴趣的:(OpenWrt)