linux内核启动过程的后期,在kernel_init()
函数代表的init线程中,会尝试执行用户空间的init进程:

从上述代码可见,会尝试执行/sbin/
、/etc
、/bin
三个目录中的init。从《》一文可以知道,在busybox编译构建完成并安装后,会生成对应的目录(注:/etc目录不存在)。在/sbin
目录中,则会存在一个init
链接:

查看其属性,其本质则是链接到了../bin/busybox
:

综上所述,证明linux内核启动后期,运行的第一个用户空间程序是init
,在busybox源码中,init程序则由位于/init
目录中的init.c
编译构建而成,程序入口是:init_main(),小生在该函数中添加一行标识代码:
linux内核运行后期的结果如下:

可见,linux内核后期加载的就是busybox下的init程序。
init_main分析
贴上该函数的完整代码,下文将分段描述:
int init_main(int argc UNUSED_PARAM, char **argv)
{
struct sigaction sa;
INIT_G();
/* Some users send poweroff signals to init VERY early.
* To handle this, mask signals early.
*/
/* sigemptyset(&G.delayed_sigset); - done by INIT_G() */
sigaddset(&G.delayed_sigset, SIGINT); /* Ctrl-Alt-Del */
sigaddset(&G.delayed_sigset, SIGQUIT); /* re-exec another init */
#ifdef SIGPWR
sigaddset(&G.delayed_sigset, SIGPWR); /* halt */
#endif
sigaddset(&G.delayed_sigset, SIGUSR1); /* halt */
sigaddset(&G.delayed_sigset, SIGTERM); /* reboot */
sigaddset(&G.delayed_sigset, SIGUSR2); /* poweroff */
#if ENABLE_FEATURE_USE_INITTAB
sigaddset(&G.delayed_sigset, SIGHUP); /* reread /etc/inittab */
#endif
sigaddset(&G.delayed_sigset, SIGCHLD); /* make sigtimedwait() exit on SIGCHLD */
sigprocmask(SIG_BLOCK, &G.delayed_sigset, NULL);
#if DEBUG_SEGV_HANDLER
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = handle_sigsegv;
sa.sa_flags = SA_SIGINFO;
sigaction_set(SIGSEGV, &sa);
sigaction_set(SIGILL, &sa);
sigaction_set(SIGFPE, &sa);
sigaction_set(SIGBUS, &sa);
#endif
if (argv[1] && strcmp(argv[1], "-q") == 0) {
return kill(1, SIGHUP);
}
#if !DEBUG_INIT
/* Expect to be invoked as init with PID=1 or be invoked as linuxrc */
if (getpid() != 1
&& (!ENABLE_LINUXRC || applet_name[0] != 'l') /* not linuxrc? */
) {
bb_simple_error_msg_and_die("must be run as PID 1");
}
# ifdef RB_DISABLE_CAD
/* Turn off rebooting via CTL-ALT-DEL - we get a
* SIGINT on CAD so we can shut things down gracefully... */
reboot(RB_DISABLE_CAD); /* misnomer */
# endif
#endif
/* If, say, xmalloc would ever die, we don't want to oops kernel
* by exiting.
* NB: we set die_func *after* PID 1 check and bb_show_usage.
* Otherwise, for example, "init u" ("please rexec yourself"
* command for sysvinit) will show help text (which isn't too bad),
* *and sleep forever* (which is bad!)
*/
die_func = sleep_much;
/* Figure out where the default console should be */
console_init();
set_sane_term();
xchdir("/");
setsid();
/* Make sure environs is set to something sane */
putenv((char *) "HOME=/");
putenv((char *) bb_PATH_root_path);
putenv((char *) "SHELL=/bin/sh");
putenv((char *) "USER=root"); /* needed? why? */
if (argv[1])
xsetenv("RUNLEVEL", argv[1]);
#if !ENABLE_FEATURE_INIT_QUIET
/* Hello world */
message(L_CONSOLE | L_LOG, "init started: %s", bb_banner);
#endif
/* Check if we are supposed to be in single user mode */
if (argv[1]
&& (strcmp(argv[1], "single") == 0 || strcmp(argv[1], "-s") == 0 || LONE_CHAR(argv[1], '1'))
) {
/* ??? shouldn't we set RUNLEVEL="b" here? */
/* Start a shell on console */
new_init_action(RESPAWN, bb_default_login_shell, "");
} else {
/* Not in single user mode - see what inittab says */
/* NOTE that if CONFIG_FEATURE_USE_INITTAB is NOT defined,
* then parse_inittab() simply adds in some default
* actions (i.e., INIT_SCRIPT and a pair
* of "askfirst" shells) */
parse_inittab();
}
#if ENABLE_SELINUX
if (getenv("SELINUX_INIT") == NULL) {
int enforce = 0;
putenv((char*)"SELINUX_INIT=YES");
if (selinux_init_load_policy(&enforce) == 0) {
BB_EXECVP(argv[0], argv);
} else if (enforce > 0) {
/* SELinux in enforcing mode but load_policy failed */
message(L_CONSOLE, "can't load SELinux Policy. "
"Machine is in enforcing mode. Halting now.");
return EXIT_FAILURE;
}
}
#endif
#if ENABLE_FEATURE_INIT_MODIFY_CMDLINE
/* Make the command line just say "init" - that's all, nothing else */
strncpy(argv[0], "init", strlen(argv[0]));
/* Wipe argv[1]-argv[N] so they don't clutter the ps listing */
while (*++argv)
nuke_str(*argv);
#endif
/* Set up STOP signal handlers */
/* Stop handler must allow only SIGCONT inside itself */
memset(&sa, 0, sizeof(sa));
sigfillset(&sa.sa_mask);
sigdelset(&sa.sa_mask, SIGCONT);
sa.sa_handler = stop_handler;
sa.sa_flags = SA_RESTART;
sigaction_set(SIGTSTP, &sa); /* pause */
/* Does not work as intended, at least in 2.6.20.
* SIGSTOP is simply ignored by init
* (NB: behavior might differ under strace):
*/
sigaction_set(SIGSTOP, &sa); /* pause */
/* Now run everything that needs to be run */
/* First run the sysinit command */
run_actions(SYSINIT);
check_delayed_sigs(&G.zero_ts);
/* Next run anything that wants to block */
run_actions(WAIT);
check_delayed_sigs(&G.zero_ts);
/* Next run anything to be run only once */
run_actions(ONCE);
/* Now run the looping stuff for the rest of forever */
while (1) {
/* (Re)run the respawn/askfirst stuff */
run_actions(RESPAWN | ASKFIRST);
/* Wait for any signal (typically it's SIGCHLD) */
check_delayed_sigs(NULL); /* NULL timespec makes it wait */
/* Wait for any child process(es) to exit */
while (1) {
pid_t wpid;
struct init_action *a;
wpid = waitpid(-1, NULL, WNOHANG);
if (wpid <= 0)
break;
a = mark_terminated(wpid);
if (a) {
message(L_LOG, "process '%s' (pid %u) exited. "
"Scheduling for restart.",
a->command, (unsigned)wpid);
}
}
/* Don't consume all CPU time - sleep a bit */
sleep1();
} /* while (1) */
}
跳过条件宏定义下的编译分支,主要分析其通用的代码部分:
(1)首先使用sigaddset()将信号添加到信号集合,添加的信号有:Ctrl-Alt-Del、SIGQUIT、SIGPWR、 SIGUSR1、SIGTERM、SIGUSR2、SIGUSR2。
(2)然后找出系统默认的控制台,并将路径切换到/,接着重新创建一个新的会话:
console_init();
set_sane_term();
xchdir("/");
setsid();
- (3)设置默认的环境变量:
putenv((char *) "HOME=/");
putenv((char *) bb_PATH_root_path);
putenv((char *) "SHELL=/bin/sh");
putenv((char *) "USER=root"); /* needed? why? */
- (4)如果是单用户模式,则会调用
new_init_action(RESPAWN, bb_default_login_shell, "");
在控制台启动一个shell;否则,则会调用parse_inittab()
函数。
parse_inittab()函数定义如下:
static void parse_inittab(void)
{
#if ENABLE_FEATURE_USE_INITTAB
char *token[4];
parser_t *parser = config_open2("/etc/inittab", fopen_for_read);
if (parser == NULL)
#endif
{
/* No inittab file - set up some default behavior */
/* Sysinit */
new_init_action(SYSINIT, INIT_SCRIPT, "");
/* Askfirst shell on tty1-4 */
new_init_action(ASKFIRST, bb_default_login_shell, "");
//TODO: VC_1 instead of ""? "" is console -> ctty problems -> angry users
new_init_action(ASKFIRST, bb_default_login_shell, VC_2);
new_init_action(ASKFIRST, bb_default_login_shell, VC_3);
new_init_action(ASKFIRST, bb_default_login_shell, VC_4);
/* Reboot on Ctrl-Alt-Del */
new_init_action(CTRLALTDEL, "reboot", "");
/* Umount all filesystems on halt/reboot */
new_init_action(SHUTDOWN, "umount -a -r", "");
/* Swapoff on halt/reboot */
new_init_action(SHUTDOWN, "swapoff -a", "");
/* Restart init when a QUIT is received */
new_init_action(RESTART, "init", "");
return;
}
#if ENABLE_FEATURE_USE_INITTAB
/* optional_tty:ignored_runlevel:action:command
* Delims are not to be collapsed and need exactly 4 tokens
*/
while (config_read(parser, token, 4, 0, "#:",
PARSE_NORMAL & ~(PARSE_TRIM | PARSE_COLLAPSE))) {
/* order must correspond to SYSINIT..RESTART constants */
static const char actions[] ALIGN1 =
"sysinit\0""wait\0""once\0""respawn\0""askfirst\0"
"ctrlaltdel\0""shutdown\0""restart\0";
int action;
char *tty = token[0];
if (!token[3]) /* less than 4 tokens */
goto bad_entry;
action = index_in_strings(actions, token[2]);
if (action < 0 || !token[3][0]) /* token[3]: command */
goto bad_entry;
/* turn .*TTY -> /dev/TTY */
if (tty[0]) {
tty = concat_path_file("/dev/", skip_dev_pfx(tty));
}
new_init_action(1 << action, token[3], tty);
if (tty[0])
free(tty);
continue;
bad_entry:
message(L_LOG | L_CONSOLE, "Bad inittab entry at line %d",
parser->lineno);
}
config_close(parser);
#endif
}
如果定义了CONFIG_FEATURE_USE_INITTAB,则会使用/etc/inittab
文件中的action;如果CONFIG_FEATURE_USE_INITTAB没有定义,parse_inittab()则会简单添加一些默认actions(例如,运行INIT_SCRIPT,然后启动一个“askfirst”shell)。如果ENABLE_FEATURE_USE_INITTAB已定义,但是/etc/inittab文件缺失也会使用相同的默认行为集合。
- (5)设置STOP信号处理程序:
memset(&sa, 0, sizeof(sa));
sigfillset(&sa.sa_mask);
sigdelset(&sa.sa_mask, SIGCONT);
sa.sa_handler = stop_handler;
sa.sa_flags = SA_RESTART;
sigaction_set(SIGTSTP, &sa); /* pause */
sigaction_set(SIGSTOP, &sa); /* pause */
- (6)接下来运行想要运行的命令,依次运行:
SYSINIT
、WAIT
、ONCE
:
run_actions(SYSINIT);
check_delayed_sigs(&G.zero_ts);
run_actions(WAIT);
check_delayed_sigs(&G.zero_ts);
run_actions(ONCE);
- (7)在最后,则是一个while(1)的死循环,用于处理我们在命令行下输入的命令:
while (1) {
/* 重新运行respawn/askfisrt */
run_actions(RESPAWN | ASKFIRST);
/* 等待信号 */
check_delayed_sigs(NULL); /* NULL timespec makes it wait */
/* 等待所有的子进程退出 */
while (1) {
pid_t wpid;
struct init_action *a;
wpid = waitpid(-1, NULL, WNOHANG);
if (wpid <= 0)
break;
a = mark_terminated(wpid);
if (a) {
message(L_LOG, "process '%s' (pid %u) exited. "
"Scheduling for restart.",
a->command, (unsigned)wpid);
}
}
/* 短暂让出CPU,不要消耗所有的CPU时间*/
sleep1();
} /* while (1) */
补充
BusyBox的init
不支持运行级别。runlevels
字段在BusyBox init中将会被完全忽略。
所以如果想要使用运行级别的系统,需使用sysvinit
作为启动进程。
一、7个运行级别
(1)运行级别0:系统停机状态,系统默认运行级别不能设为0,否则不能正常启动。其实就是关机。
(2)运行级别1:单用户工作状态,root权限,用于系统维护,禁止远程登陆。在忘记root密码时一般用这个运行级别,进去修改root密码。
(3)运行级别2:多用户状态(没有NFS),没有网络连接。
(4)运行级别3:完全的多用户状态(有NFS),登陆后进入控制台命令行模式。linux很常见的运行级别
(5)运行级别4:系统未使用,保留。
(6)运行级别5:X11控制台,登陆后进入图形GUI模式。就是图形模式。
(7)运行级别6:系统正常关闭并重启,默认运行级别不能设为6,否则不能正常启动。
二、查看运行级别
1、runlevel命令:打印系统的上一个和当前运行级别:

N:“N”表示自系统启动后运行级别尚未更改。从上图可见,小生的Ubuntu系统的运行级别为5。