Hotplug
the hotplug implements of OpenWRT
Install / Use
/learn @wywincl/HotplugREADME
OpenWRT Hotplug原理分析
本次研究基于OpenWRT 14_07 trunk。其他版本有部分差异,请阅读时注意。
<a name='TOC'>目录表</a>
<a name='profiles'>Hotplug原理</a>
Hotplug即热插拔,在新版本OpenWRT上,hotplug,coldplug与watchdog等被集成到全新的Procd系统中。
Procd是OpenWRT下新的预初始化,初始化,热插拔和事件系统。在openwrt 中, procd 作为 init 进程会处理许多事情, 其中就包括 hotplug。procd本身并不知道如何处理hotplug事件,也没有必要知道,因为它只实现机制,而不实现策略。事件的处理是由配置文件决定的,这些配置文件即所谓的rules.。老版本下独立的hotplug2在r36987被移除了。所以下面我们要介绍的就是新版本下Hotplug的机制。
要了解Hotplug运行的整个过程,首先得了解procd系统的工作流程。才能从全局了解hotplug是如何工作的。在这里我们重点介绍与hotplug相关的procd启动过程。
Procd启动过程分析
preinit()函数
void
preinit(void)
{
char *init[] = { "/bin/sh", "/etc/preinit", NULL };
char *plug[] = { "/sbin/procd", "-h", "/etc/hotplug-preinit.json", NULL };
LOG("- preinit -\n");
plugd_proc.cb = plugd_proc_cb;
plugd_proc.pid = fork();
if (!plugd_proc.pid) {
execvp(plug[0], plug);
ERROR("Failed to start plugd\n");
exit(-1);
}
if (plugd_proc.pid <= 0) {
ERROR("Failed to start new plugd instance\n");
return;
}
uloop_process_add(&plugd_proc);
setenv("PREINIT", "1", 1);
preinit_proc.cb = spawn_procd;
preinit_proc.pid = fork();
if (!preinit_proc.pid) {
execvp(init[0], init);
ERROR("Failed to start preinit\n");
exit(-1);
}
if (preinit_proc.pid <= 0) {
ERROR("Failed to start new preinit instance\n");
return;
}
uloop_process_add(&preinit_proc);
DEBUG(4, "Launched preinit instance, pid=%d\n", (int) preinit_proc.pid);
}
-
创建子进程执行
/etc/preinit脚本,此时PREINIT环境变量被设置为1,主进程同时使用uloop_process_add()把/etc/preinit子进程加入uloop进行监控,当/etc/preinit执行结束时回调plugd_proc_cb()函数把监控/etc/preinit进程对应对象中pid属性设置为0,表示/etc/preinit已执行完成。 -
创建子进程执行
/sbin/procd -h /etc/hotplug-preinit.json,主进程同时使用uloop_process_add()把/sbin/procd子进程加入uloop进行监控,当/sbin/procd进程结束时回调spawn_procd()函数。 -
spawn_procd()函数繁衍后继真正使用的/sbin/procd进程,从/tmp/debuglevel读出debug级别并设置到环境变量DBGLVL中,把watchdog fd设置到环境变量WDTFD中,最后调用execvp()繁衍/sbin/procd进程。
procd进程
在这里我们主要分析procd的五个状态,分别为 STATE_EARLY、STATE_INIT、STATE_RUNNING、STATE_SHUTDOWN、STATE_HALT,这5个状态将按顺序变化,当前状态保存在全局变量state中,可通过procd_state_next()函数使用状态发生变化。
static void state_enter(void)
{
char ubus_cmd[] = "/sbin/ubusd";
switch (state) {
case STATE_EARLY:
LOG("- early -\n");
watchdog_init(0);
hotplug("/etc/hotplug.json");
procd_coldplug();
break;
case STATE_INIT:
// try to reopen incase the wdt was not available before coldplug
watchdog_init(0);
LOG("- ubus -\n");
procd_connect_ubus();
LOG("- init -\n");
service_init();
service_start_early("ubus", ubus_cmd);
procd_inittab();
procd_inittab_run("respawn");
procd_inittab_run("askconsole");
procd_inittab_run("askfirst");
procd_inittab_run("sysinit");
break;
case STATE_RUNNING:
LOG("- init complete -\n");
break;
case STATE_SHUTDOWN:
LOG("- shutdown -\n");
procd_inittab_run("shutdown");
sync();
break;
case STATE_HALT:
LOG("- reboot -\n");
reboot(reboot_event);
break;
default:
ERROR("Unhandled state %d\n", state);
return;
};
}
-
#####STATE_EARLY状态 - init前准备工作
- 初始化
watchdog - 根据"
/etc/hotplug.json"规则监听hotplug procd_coldplug()函数处理,把/dev挂载到tmpfs中,fork udevtrigger进程产生冷插拔事件,以便让hotplug监听进行处理udevstrigger进程处理完成后回调procd_state_next()函数把状态从STATE_EARLY转变为STATE_INIT
- 初始化
-
#####STATE_INIT状态 - 初始化工作
- 连接
ubusd,此时实际上ubusd并不存在,所以procd_connect_ubus函数使用了定时器进行重连,而uloop_run()需在初始化工作完成后才真正运行。当成功连接上ubusd后,将注册service main_object对象,system_object对象、watch_event对象(procd_connect_ubus()函数), - 初始化
services(服务)和validators(服务验证器)全局AVL tree - 把
ubusd服务加入services管理对象中(service_start_early) - 根据
/etc/inittab内容把cmd、handler对应关系加入全局链表actions中 - 顺序加载
respawn、askconsole、askfirst、sysinit命令 sysinit命令把/etc/rc.d/目录下所有启动脚本执行完成后将回调rcdone()函数把状态从STATE_INIT转变为STATE_RUNNING
- 连接
-
#####STATE_RUNNING状态
- 进入
STATE_RUNNING状态后procd运行uloop_run()主循环
- 进入
Hotplug原理图
Hotplug原理的整个流程如下所示:
-----------------------
| procd daemon |
| (hotplug.json) |
-----------------------
netlink| socket user space
-------------------------------------------------
| kernel space
-----------------------
| (uevent [json]) |
| kernel |
-----------------------
主要过程分为以下两个部分:
- 内核发出uevent事件
内核使用uevent事件通知用户空间,uevent首先在内核中调用netlink_kernel_create()函数创建一个socket套接字,该函数原型在netlink.h中定义。这是一种特殊类型的socket ,专门用于内核空间与用户空间的异步通信。
kobject_uevent()产生uevent事件(/lib/kobject_uevent.c),事件的部分信息通过环境变量传递,如$ACTION, $DEVPATH, $SUBSYSTEM等,产生的uevent先由netlink_broadcast_filtered()发出,最后调用uevent_helper[]所指定的程序来处理。
在linux中,uevent_helper[]里默认指定”/sbin/hotplug”,但可以通过/sys/kernel/uevent_helper(kernel/ksysfs.c)或/proc/kernel/uevent_helper(kernel/sysctl.c)来修改成指定的程序。
在新OpenWRT中,并不使用user_helper[]指定程序来处理uevent(/sbin/hotplug不存在,在以前版本中存在),而是通过PF_NETLINK套接字来获取来自内核空间的uevent。
2. 用户空间监听uevent
在proc/plug/hotplug.c中,创建一个PF_NETLINK套接字来监听内核netlink_broadcast_filtered()发出的uevent。收到uevent之后,在根据/etc/hotplug.json里的描述,定位到对应的执行函数来处理。
通常情况下,/etc/hotplug.json会调用/sbin/hotplug-call来处理uevent,它根据uevent的$SUBSYSTEM变量来分别调用/etc/hotplug.d下不同目录中的脚本。
/sbin/hotplug-call脚本如下所示,这里面的$1表示hotplug-call的第一个参数:
root@OpenWrt:/sbin# cat hotplug-call
#!/bin/sh
# Copyright (C) 2006-2010 OpenWrt.org
export HOTPLUG_TYPE="$1"
. /lib/functions.sh
PATH=/bin:/sbin:/usr/bin:/usr/sbin
LOGNAME=root
USER=root
export PATH LOGNAME USER
export DEVICENAME="${DEVPATH##*/}"
[ \! -z "$1" -a -d /etc/hotplug.d/$1 ] && {
for script in $(ls /etc/hotplug.d/$1/* 2>&-); do (
[ -f $script ] && . $script
); done
}
下表是hotplug.json的具体内容,重点关注蓝色字段。
root@OpenWrt:/etc# cat hotplug.json
[
[ "case", "ACTION", {
"add": [
[ "if",
[ "and",
[ "has", "MAJOR" ],
[ "has", "MINOR" ],
],
[
[ "if",
[ "or",
[ "eq", "DEVNAME",
[ "null", "full", "ptmx", "zero" ],
],
[ "regex", "DEVNAME",
[ "^gpio", "^hvc" ],
],
],
[
[ "makedev", "/dev/%DEVNAME%", "0666" ],
[ "return" ],
]
],
[ "if",
[ "or",
[ "eq", "DEVNAME", "mapper/control" ],
[ "regex", "DEVPATH", "^ppp" ],
],
[
[ "makedev", "/dev/%DEVNAME%", "0600" ],
[ "return" ],
],
],
[ "if",
[ "has", "DEVNAME" ],
[ "makedev", "/dev/%DEVNAME%", "0644" ],
],
],
],
[ "if",
[ "has", "FIRMWARE" ],
[
[ "exec", "/sbin/hotplug-call", "%SUBSYSTEM%" ],
[ "load-firmware", "/lib/firmware" ],
[ "return" ]
]
],
],
"remove" : [
[ "if",
[ "and",
[ "has", "DEVNAME" ],
[ "has", "MAJOR" ],
[ "has", "MINOR" ],
],
[ "rm", "/dev/%DEVNAME%" ]
]
]
} ],
[ "if",
[ "eq", "SUBSYSTEM", "platform" ],
[ "exec", "/sbin/hotplug-call", "%SUBSYSTEM%" ]
],
[ "if",
[ "and",
[ "has", "BUTTON" ],
[ "eq", "SUBSYSTEM", "button" ],
],
[ "exec", "/etc/rc.button/%BUTTON%" ]
],
[ "if",
[ "eq", "SUBSYSTEM",
[ "net", "input", "usb", "ieee1394", "block", "atm", "zaptel", "tty", "button" ]
],
[ "exec", "/sbin/hotplug-call", "%SUBSYSTEM%" ]
],
[ "if",
[ "and",
[ "eq", "SUBSYSTEM", "usb-serial" ],
[ "regex", "DEVNAME",
[ "^ttyUSB", "^ttyACM" ]
],
],
[
Security Score
Audited on Aug 11, 2025
