linux内核启动过程会执行用户空间的init进程

描述

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

NFS

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

NFS

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

NFS

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

NFS

linux内核运行后期的结果如下:

NFS

可见,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命令:打印系统的上一个和当前运行级别:

NFS

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





审核编辑:刘清

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分