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 , 內容節選如下:
在這裏插入圖片描述

這裏主要處理的地方在 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 函數可以使條件都滿足。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章