通常在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 函數可以使條件都滿足。