openwrt启动流程(一)
Linux在start_kernel函数执行的最后会调用kernel_init函数来启动用户空间的一号进程,之前都是我们熟悉的init进程,但在openwrt里面会执行/etc/preinit脚本

其中标记红框的代码为给kernel打的patch
下面来看/etc/preinit脚本
[ -z "$PREINIT" ] && exec /sbin/init
使用exec,表明init替换进程上下文,pid保持不变,应该为1
PREINIT变量当前没有值,所以执行init,下面来看init.c函数
early(); cmdline(); watchdog_init(1);
首先做一些文件系统挂载,环境变量设置,watchdog初试化等操作
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); }
然后fork一个子进程执行kmodloader,下面来看kmodloader.c函数
if (scan_loaded_modules()) return -1; if (scan_module_folders()) return -1;
从/proc/modules里面遍历所有已经安装的模块,包括模块名称和大小等信息,插入到avl树中,同时将节点状态设置为LOADED
将/lib/modules/x.x.x路径保存到二维指针module_folders,遍历/lib/modules/x.x.x/*.ko,查看是否在avl树中,如果不在,添加到avl树中,同时将节点状态设置为SCANNED
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(); }
遍历/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
static void spawn_procd(struct uloop_process *proc, int ret) { char *wdt_fd = watchdog_fd(); char *argv[] = { "/sbin/procd", NULL}; struct stat s; char dbg[2]; if (plugd_proc.pid > 0) kill(plugd_proc.pid, SIGKILL); if (!stat("/tmp/sysupgrade", &s)) while (true) sleep(1); unsetenv("INITRAMFS"); unsetenv("PREINIT"); DEBUG(2, "Exec to real procd now\n"); if (wdt_fd) setenv("WDTFD", wdt_fd, 1); check_dbglvl(); if (debug > 0) { snprintf(dbg, 2, "%d", debug); setenv("DBGLVL", dbg, 1); } execvp(argv[0], argv); }
首先kill掉刚才创建的子进程procd
if (plugd_proc.pid > 0) kill(plugd_proc.pid, SIGKILL);
最后调用procd,来看procd.c
int main(int argc, char **argv) { int ch; char *dbglvl = getenv("DBGLVL"); ulog_open(ULOG_KMSG, LOG_DAEMON, "procd"); if (dbglvl) { debug = atoi(dbglvl); unsetenv("DBGLVL"); } while ((ch = getopt(argc, argv, "d:s:h:")) != -1) { switch (ch) { case 'h': return hotplug_run(optarg); case 's': ubus_socket = optarg; break; case 'd': debug = atoi(optarg); break; default: return usage(argv[0]); } } setsid(); uloop_init(); procd_signal(); trigger_init(); if (getpid() != 1) { procd_connect_ubus(); } else { procd_state_next(); } uloop_run(); uloop_done(); return 0; }
因pid为1,进入procd_state_next()
{ 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_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_init(); 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"); 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); if (reboot_event == RB_POWER_OFF) LOG("- power down -\n"); else LOG("- reboot -\n"); /* Allow time for last message to reach serial console, etc */ sleep(1); /* We have to fork here, since the kernel calls do_exit(EXIT_SUCCESS) * in linux/kernel/sys.c, which can cause the machine to panic when * the init process exits... */ if (!vfork( )) { /* child */ reboot(reboot_event); _exit(EXIT_SUCCESS); } while (1) sleep(1); break; default: ERROR("Unhandled state %d\n", state); return; }; }
顺序跳转到每个state执行,来看procd_inittab_run("sysinit"),其他以后再分析
static void runrc(struct init_action *a) { if (!a->argv[1] || !a->argv[2]) { ERROR("valid format is rcS <S|K> <param>\n"); return; } rcS(a->argv[1], a->argv[2], rcdone); }
第一个参数为S或者K,第二个参数为boot
int rcS(char *pattern, char *param, void (*q_empty)(struct runqueue *)) { runqueue_init(&q); q.empty_cb = q_empty; q.max_running_tasks = 1; return _rc(&q, "/etc/rc.d", pattern, "*", param); }
然后依次执行/etc/rc.d中S开头的脚本,参数为boot
浙公网安备 33010602011771号