OpenWrt系统启动脚本调用流程如下:
Linux在start_kernel
函数执行的最后会调用kernel_init
函数来启动用户空间的一号进程,之前都是我们熟悉的init进程,但在OpenWRT里面会执行/etc/preinit
脚本:
其中红框标注的代码为OpenWRT给kernel打的patch
/etc/preinit
脚本如下:
#!/bin/sh
# Copyright (C) 2006 OpenWrt.org
# Copyright (C) 2010 Vertical Communications
[ -z "$PREINIT" ] && exec /sbin/init
export PATH=/bin:/sbin:/usr/bin:/usr/sbin
pi_ifname=
pi_ip=192.168.1.1
pi_broadcast=192.168.1.255
pi_netmask=255.255.255.0
fs_failsafe_ifname=
fs_failsafe_ip=192.168.2.20
fs_failsafe_broadcast=192.168.2.255
fs_failsafe_netmask=255.255.255.0
fs_failsafe_wait_timeout=2
pi_suppress_stderr="y"
pi_init_suppress_stderr="y"
pi_init_path="/bin:/sbin:/usr/bin:/usr/sbin"
pi_init_cmd="/sbin/init"
. /lib/functions.sh
. /lib/functions/preinit.sh
. /lib/functions/system.sh
boot_hook_init preinit_essential
boot_hook_init preinit_main
boot_hook_init failsafe
boot_hook_init initramfs
boot_hook_init preinit_mount_root
for pi_source_file in /lib/preinit/*; do
. $pi_source_file
done
boot_run_hook preinit_essential
pi_mount_skip_next=false
pi_jffs2_mount_success=false
pi_failsafe_net_message=false
boot_run_hook preinit_main
PREINIT
变量当前没有值,所以执行init
;使用exec
,表明init
替换进程上下文,pid保持不变,应该为1;
下面来看init.c
函数:
int main(int argc, char **argv)
{
pid_t pid;
sigaction(SIGTERM, &sa_shutdown, NULL);
sigaction(SIGUSR1, &sa_shutdown, NULL);
sigaction(SIGUSR2, &sa_shutdown, NULL);
early();//-------->early.c
cmdline();
watchdog_init(1); //------->../watchdog.c
pid = fork();
if (!pid) {
char *kmod[] = { "/sbin/kmodloader", "/etc/modules-boot.d/", NULL };
if (debug < 3) {
int fd = open("/dev/null", O_RDWR);
if (fd > -1) {
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
if (fd > STDERR_FILENO)
close(fd);
}
}
execvp(kmod[0], kmod);
ERROR("Failed to start kmodloader\n");
exit(-1);
}
if (pid <= 0)
ERROR("Failed to start kmodloader instance\n");
uloop_init();
preinit(); //-------------->watchdog.c
uloop_run();
return 0;
}
首先做一些文件系统挂载,环境变量设置,watchdog初试化等操作,然后fork一个子进程执行kmodloader,下面来看kmodloader.c
函数:
if (scan_loaded_modules())
return -1;
if (scan_module_folders())
return -1;
while (getline(&mod, &mod_len, fp) > 0) {
char *nl = strchr(mod, '\n');
struct module *m;
char *opts;
if (nl)
*nl = '\0';
opts = strchr(mod, ' ');
if (opts)
*opts++ = '\0';
m = find_module(get_module_name(mod));
if (!m || (m->state == LOADED))
continue;
if (opts)
m->opts = strdup(opts);
m->state = PROBE;
if (basename(gl.gl_pathv[j])[0] - '0' <= 9)
load_modprobe();
}
从/proc/modules
里面遍历所有已经安装的模块,包括模块名称和大小等信息,插入到avl树中,同时将节点状态设置为LOADED
;将/lib/modules/x.x.x
路径保存到二维指针module_folders
,遍历/lib/modules/x.x.x/*.ko
,查看是否在avl树中,如果不在,添加到avl树中,同时将节点状态设置为SCANNED
;遍历/etc/modules-boot.d/
下所有文件并打开,判断当前模块是否在avl树中,如果存在且节点状态不为LOADED
,则将此节点状态设置为PROBE
,如果文件名第一个字符是0~9之间,则装载此模块,同时将此节点状态设置为LOADED
;
回到init.c
,来看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);
}
fork两个子进程分别执行procd
和/etc/preinit
,来看procd.c
,因携带参数-h /etc/hotplug-preinit.json
,所以进入hotplug_run
:
void hotplug(char *rules)
{
struct sockaddr_nl nls;
int nlbufsize = 512 * 1024;
rule_file = strdup(rules);
memset(&nls,0,sizeof(struct sockaddr_nl));
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: %s\n", strerror(errno));
exit(1);
}
if (bind(hotplug_fd.fd, (void *)&nls, sizeof(struct sockaddr_nl))) {
ERROR("Failed to bind hotplug socket: %s\n", strerror(errno));
exit(1);
}
if (setsockopt(hotplug_fd.fd, SOL_SOCKET, SO_RCVBUFFORCE, &nlbufsize, sizeof(nlbufsize)))
ERROR("Failed to resize receive buffer: %s\n", strerror(errno));
json_script_init(&jctx);
queue_proc.cb = queue_proc_cb;
uloop_fd_add(&hotplug_fd, ULOOP_READ);
}
这里是建立netlink监听内核的uevent事件,具体的实现之后再单独分析;回到/etc/preinit
:
. /lib/functions.sh
. /lib/functions/preinit.sh
. /lib/functions/system.sh
在当前shell环境下调用三个脚本,里面包含一些函数定义,
boot_hook_init preinit_essential
boot_hook_init preinit_main
boot_hook_init failsafe
boot_hook_init initramfs
boot_hook_init preinit_mount_root
初试化hook链:
for pi_source_file in /lib/preinit/*; do
. $pi_source_file
done
依次执行/lib/preinit/下面的脚本,都是将函数调用添加到hook链上面:
boot_run_hook preinit_essential
执行preinit_essential注册的hook链的所有函数:
boot_run_hook preinit_main
执行preinit_main
注册的hook链的所有函数;/etc/preinit
执行完毕后,init进程调用spawn_procd
,procd内容较多,此处暂不讨论了,可参考第一个链接的末尾部分;