【Busybox】Busybox源码分析-02 | init程序

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)接下来运行想要运行的命令,依次运行:SYSINITWAITONCE
	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作为启动进程。

(1)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,否则不能正常启动。

(2)查看运行级别

1、runlevel命令:打印系统的上一个和当前运行级别:

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

猜你喜欢

转载自blog.csdn.net/iriczhao/article/details/127341833