1、内核调用
内核启动以后调用init_post函数
/* This is a non __init function. Force it to be noinline otherwise gcc
* makes it inline to init() and it becomes part of init.text section
*/
static noinline int init_post(void)
__releases(kernel_lock)
{
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();
free_initmem();
mark_rodata_ro();
system_state = SYSTEM_RUNNING;
numa_default_policy();
current->signal->flags |= SIGNAL_UNKILLABLE;
if (ramdisk_execute_command) {
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING "Failed to execute %s\n",
ramdisk_execute_command);
}
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) {
run_init_process(execute_command);
printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults...\n", execute_command);
}
printk("init.main L%d\n", __LINE__);
run_init_process("/etc/preinit");
printk("init.main L%d\n", __LINE__);
run_init_process("/sbin/init");
printk("init.main L%d\n", __LINE__);
run_init_process("/etc/init");
printk("init.main L%d\n", __LINE__);
run_init_process("/bin/init");
printk("init.main L%d\n", __LINE__);
// run_init_process("/bin/ash");
panic("No init found. Try passing init= option to kernel. "
"See Linux Documentation/init.txt for guidance.");
}
execute_command是通过command line参数传递过来的。因为我们command line中并没有指定init参数,所以会依次尝试调用/etc/preinit、/sbin/init、/etc/init、/bin/init。因为启动脚本/etc/preinit是存在的,所以实际上就是调用/etc/preinit启动了pid为1的1号进程。后面的/sbin/init、/etc/init、/bin/init实际上调用不到。
2、/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.1.1
fs_failsafe_broadcast=192.168.1.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
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"为空,所以会执行/sbin/init,/sbin/init实际上就是/sbin/procd的一个链接
root@OpenWrt:~# ls -l /sbin/init
lrwxrwxrwx 1 root root 11 Dec 1 08:12 /sbin/init -> /sbin/procd
3、procd进程
/*
* Copyright (C) 2013 Felix Fietkau
* Copyright (C) 2013 John Crispin
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include
#include
#include
#include
#include
#include
#include "procd.h"
#include "hotplug.h"
#include "watchdog.h"
static int usage(const char *prog)
{
ERROR("Usage: %s [options]\n"
"Options:\n"
" -s : Path to ubus socket\n"
" -d: Enable debug messages\n"
"\n", prog);
return 1;
}
static int main_procd_init(int argc, char **argv)
{
procd_signal_preinit();
procd_early();
debug_init();
watchdog_init(1);
system("/sbin/kmodloader /etc/modules-boot.d/");
uloop_init();
hotplug("/etc/hotplug-preinit.json");
procd_preinit();
uloop_run();
return 0;
}
int main(int argc, char **argv)
{
int ch;
if (!strcmp(basename(*argv), "init"))
return main_procd_init(argc, argv);
while ((ch = getopt(argc, argv, "ds:")) != -1) {
switch (ch) {
case 's':
ubus_socket = optarg;
break;
case 'd':
debug++;
break;
default:
return usage(argv[0]);
}
}
uloop_init();
procd_signal();
trigger_init();
if (getpid() != 1)
procd_connect_ubus();
else
procd_state_next();
uloop_run();
return 0;
}
从代码可以看出,调用/sbin/init,最后实际上是调用main_procd_init函数,main_procd_init又调用procd_preinit。
4、procd_preinit函数
void procd_preinit(void)
{
char *argv[] = { "/bin/sh", "/etc/preinit", NULL };
LOG("- preinit -\n");
setenv("PREINIT", "1", 1);
preinit.cb = spawn_procd;
preinit.pid = fork();
if (!preinit.pid) {
execvp(argv[0], argv);
ERROR("Failed to start preinit\n");
exit(-1);
}
if (preinit.pid <= 0) {
ERROR("Failed to start new preinit instance\n");
return;
}
uloop_process_add(&preinit);
DEBUG(2, "Launched preinit instance, pid=%d\n", (int) preinit.pid);
}
可以看到procd_preinit函数中fork了一个子进程,子进程中再次调用/etc/preinit脚本,不同的是在再次执行该脚本之前设置了环境变量PREINIT,所以再次执行preinit脚本的时候,不会再调用/sbin/init,而是会执行一系列boot_hook_init。执行完以后,子进程就退出了。由于子进程通过uloop_process_add被加入父进程/sbin/init的监控当中,所以当子进程退出时,父进程会收到信号,从而执行挂载的回调函数spawn_procd。
5、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;
if (!stat("/tmp/sysupgrade", &s))
while (true)
sleep(1);
unsetenv("INITRAMFS");
unsetenv("PREINIT");
DEBUG(1, "Exec to real procd now\n");
if (wdt_fd)
setenv("WDTFD", wdt_fd, 1);
execvp(argv[0], argv);
}
父进程/sbin/init执行了spawn_procd函数,通过execvp调用/sbin/procd,execvp调用后不会返回,1号进程/sbin/init也变成了/sbin/procd。1号进程运行的/sbin/procd,不会再进入main_procd_init,最终是会调用procd_state_next函数。至此procd进程才正式运行起来,接管了后续其他进程的启动。