通常在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 函数可以使条件都满足。