OpenWrt 内部消息总线 ubus 是支撑 该系统核心,系统软件模块之间关系图如下:
此图仅是把部分软件模块标识出来,其中还有很多软件模块如:uhttpd、rpcd、mwan3、hotplug、coldplug等等模块,都是依托openWRT的系统ubus总线来构建。此框图非常重要、能够快速建立系统软件组件之间关系。
热插拔与冷插拔入口
调用 openWRT的state_enter() 状态机函数,此函数中,在系统early阶段初始 hotplug()、procd_coldplug()函数。注册RPC服务响应程序。源码如下:
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"); //热插拔函数初始化,hotplug.json文件内容非常关键
procd_coldplug(); //冷插拔函数初始化
break;
case STATE_UBUS:
// try to reopen incase the wdt was not available before coldplug
watchdog_init(0);
set_stdio("console");
LOG("- ubus -\n");
procd_connect_ubus();
service_start_early("ubus", ubus_cmd);
break;
case STATE_INIT:
LOG("- init -\n");
procd_inittab();
procd_inittab_run("respawn");
procd_inittab_run("askconsole");
procd_inittab_run("askfirst");
procd_inittab_run("sysinit");
// switch to syslog log channel
ulog_open(ULOG_SYSLOG, LOG_DAEMON, "procd");
break;
case STATE_RUNNING:
LOG("- init complete -\n");
procd_inittab_run("respawnlate");
procd_inittab_run("askconsolelate");
break;
case STATE_SHUTDOWN:
/* Redirect output to the console for the users' benefit */
set_console();
LOG("- shutdown -\n");
procd_inittab_run("shutdown");
sync();
break;
case STATE_HALT:
// To prevent killed processes from interrupting the sleep
signal(SIGCHLD, SIG_IGN);
LOG("- SIGTERM processes -\n");
kill(-1, SIGTERM);
sync();
sleep(1);
LOG("- SIGKILL processes -\n");
kill(-1, SIGKILL);
sync();
sleep(1);
#ifndef DISABLE_INIT
perform_halt();
#else
exit(EXIT_SUCCESS);
#endif
break;
default:
ERROR("Unhandled state %d\n", state);
return;
};
}
一、热插拔源码走读
void hotplug(char *rules)
{
struct sockaddr_nl nls = {};
int nlbufsize = 512 * 1024;
rule_file = strdup(rules);
nls.nl_family = AF_NETLINK;
nls.nl_pid = getpid();
nls.nl_groups = -1;
// 建立 NETLINK_KOBJECT_UEVENT 连接
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); //把 hotplug_fd 添加到 ubus 链表中
}
文件 /etc/hotplug.json 内容
[
[ "case", "ACTION", {
"add": [
[ "if",
[ "and",
[ "has", "MAJOR" ],
[ "has", "MINOR" ]
],
[
[ "if",
[ "eq", "DEVNAME",
[ "null", "full", "ptmx", "zero", "tty", "net", "random", "urandom" ]
],
[
[ "makedev", "/dev/%DEVNAME%", "0666" ],
[ "return" ]
]
],
[ "if",
[ "regex", "DEVNAME", "^snd" ],
[ "makedev", "/dev/%DEVNAME%", "0660", "audio" ]
],
[ "if",
[ "regex", "DEVNAME", "^tty" ],
[ "makedev", "/dev/%DEVNAME%", "0660", "dialout" ]
],
[ "if",
[ "has", "DEVNAME" ],
[ "makedev", "/dev/%DEVNAME%", "0600" ]
]
]
],
[ "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",
[ "and", //键盘事件
[ "has", "BUTTON" ],
[ "eq", "SUBSYSTEM", "button" ]
],
[ "button", "/etc/rc.button/%BUTTON%" ] // 键盘事件执行的脚本位置
],
[ "if",
[ "and", // usb串口事件
[ "eq", "SUBSYSTEM", "usb-serial" ],
[ "regex", "DEVNAME",
[ "^ttyUSB", "^ttyACM" ]
]
],
[ "exec", "/sbin/hotplug-call", "tty" ],
[ "if",
[ "isdir", "/etc/hotplug.d/%SUBSYSTEM%" ],
[ "exec", "/sbin/hotplug-call", "%SUBSYSTEM%" ]
]
]
]
热插拔回调函数
static void hotplug_handler(struct uloop_fd *u, unsigned int ev)
{
int i = 0;
static char buf[4096];
int len = recv(u->fd, buf, sizeof(buf) - 1, MSG_DONTWAIT);
void *index;
if (len < 1)
return;
buf[len] = '\0';
blob_buf_init(&b, 0);
index = blobmsg_open_table(&b, NULL);
while (i < len) {
int l = strlen(buf + i) + 1;
char *e = strstr(&buf[i], "=");
if (e) {
*e = '\0';
blobmsg_add_string(&b, &buf[i], &e[1]);
}
i += l;
}
blobmsg_close_table(&b, index);
hotplug_handler_debug(b.head);
json_script_run(&jctx, rule_file, blob_data(b.head)); //执行 /etc/hotplug.json 执行 json 脚本程序。
}
热插拔回调函数中,调用json_script_run()函数,此函数调用如下函数
static void __json_script_run(struct json_call *call, struct json_script_file *file,
struct blob_attr *context)
{
struct json_script_ctx *ctx = call->ctx;
if (file->seq == call->seq) {
if (context)
ctx->handle_error(ctx, "Recursive include", context);
return;
}
file->seq = call->seq;
while (file) { // 遍历 json 文件中所有的脚本文件
json_process_cmd(call, file->data); // 执行脚本中命令
file = file->next;
}
}
调用如下函数,此函数
static int __json_process_cmd(struct json_call *call, struct blob_attr *cur)
{
struct json_script_ctx *ctx = call->ctx;
const char *name;
bool found;
int ret;
if (blobmsg_type(cur) != BLOBMSG_TYPE_ARRAY ||
blobmsg_type(blobmsg_data(cur)) != BLOBMSG_TYPE_STRING) {
ctx->handle_error(ctx, "Unexpected element type", cur);
return -1;
}
ret = __json_process_type(call, cur, cmd, ARRAY_SIZE(cmd), &found);
if (found)
return ret;
name = blobmsg_data(blobmsg_data(cur));
ret = cmd_process_strings(call, cur);
if (ret)
return ret;
ctx->handle_command(ctx, name, blob_data(ctx->buf.head), call->vars);
return 0;
}
二、冷插拔源码走读
void procd_coldplug(void)
{
char *argv[] = { "udevtrigger", NULL };
unsigned int oldumask = umask(0);
if (!is_container()) {
umount2("/dev/pts", MNT_DETACH);
umount2("/dev/", MNT_DETACH);
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755,size=512K");
mkdir("/dev/pts", 0755);
mount("devpts", "/dev/pts", "devpts", MS_NOEXEC | MS_NOSUID, 0);
}
ignore(symlink("/tmp/shm", "/dev/shm"));
umask(oldumask);
udevtrigger.cb = udevtrigger_complete;
udevtrigger.pid = fork();
if (!udevtrigger.pid) {
execvp(argv[0], argv);
ERROR("Failed to start coldplug: %m\n");
exit(EXIT_FAILURE);
}
if (udevtrigger.pid <= 0) {
ERROR("Failed to start new coldplug instance: %m\n");
return;
}
uloop_process_add(&udevtrigger);
DEBUG(4, "Launched coldplug instance, pid=%d\n", (int) udevtrigger.pid);
}
此部分代码需要进一步解读,可参考《详解 OpenWRT RESET按键、键盘响应逻辑》