linux内核启动过程的后期,在kernel_init()函数代表的init线程中,会尝试执行用户空间的init进程:
从上述代码可见,会尝试执行/sbin/、/etc、/bin三个目录中的init。从《busybox源码分析笔记(一)》一文可以知道,在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_ttyaction: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�""wait�""once�""respawn�""askfirst�" "ctrlaltdel�""shutdown�""restart�"; 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。
审核编辑:刘清
全部0条评论
快来发表一下你的评论吧 !