https://www.cnblogs.com/arnoldlu/p/10868354.html

busybox启动流程简单解析:从init到shell login

关键词:kernel_init()、init、inittab、wait/waitpid、fork/vfork、setsid()、execvp/execlp、dup2等等。

由于遇到一系列定制,从init开始加载不同服务,对服务异常等需要特殊处理。

如何在恰当的时机加载恰当的服务?如何对不同异常进行特殊处理?

这就有必要分析内核是如何加载init进程的?init进程是按照何种顺序启动各种服务的?init是如何管理这些服务的?系统开机后各种进程都是在哪里创立的?

带着这些问题来分析一下kernel->init、init进程本身、inittab配置文件、rcS、/etc/profile等等。

1. 从kernel到init

在内核启动的最后阶段start_kernel()->reset_init()创建第一个进程,即pid=0的idle进程,运行在内核态,也是唯一一个没有通过fork()或者kernel_thread()创建的进程。

这个进程最终进入start_kernel()->reset_init()->cpu_startup_entry()->cpu_idle_loop()。

在进程0中生成两个进程:一个是所有用户空间进程的祖先的init进程,一个是所有内核线程祖先的kthreadd。

static noinline void __ref rest_init(void)
{
...kernel_thread(kernel_init, NULL, CLONE_FS);numa_default_policy();pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
...cpu_startup_entry(CPUHP_ONLINE);
}static int __ref kernel_init(void *unused)
{...if (ramdisk_execute_command) {--------------------------------可以在command line通过"rdinit=/sbin/init"来指定,如果指定则启动ramdisk。ret = run_init_process(ramdisk_execute_command);if (!ret)return 0;pr_err("Failed to execute %s (error %d)\n",ramdisk_execute_command, ret);}if (execute_command) {----------------------------------------在command line中通过"init=/sbin/init"来指定,包括启动参数argv_init[]。ret = run_init_process(execute_command);if (!ret)return 0;panic("Requested init %s failed (error %d).",execute_command, ret);}if (!try_to_run_init_process("/sbin/init") ||-----------------如果没有指定rdinit和init,那么依次尝试下面几个固定路径init程序。!try_to_run_init_process("/etc/init") ||!try_to_run_init_process("/bin/init") ||!try_to_run_init_process("/bin/sh"))return 0;
...
}int kthreadd(void *unused)
{struct task_struct *tsk = current;/* Setup a clean context for our children to inherit. */set_task_comm(tsk, "kthreadd");--------------------------------修改内核线程名为kthreadd。ignore_signals(tsk);set_cpus_allowed_ptr(tsk, cpu_all_mask);set_mems_allowed(node_states[N_MEMORY]);current->flags |= PF_NOFREEZE;cgroup_init_kthreadd();for (;;) {set_current_state(TASK_INTERRUPTIBLE);if (list_empty(&kthread_create_list))schedule();__set_current_state(TASK_RUNNING);spin_lock(&kthread_create_lock);while (!list_empty(&kthread_create_list)) {----------------内核线程的创建是由kthreadd遍历kthread_create_list列表,然后取出成员,通过create_kthread()创建内核线程。struct kthread_create_info *create;create = list_entry(kthread_create_list.next,struct kthread_create_info, list);list_del_init(&create->list);spin_unlock(&kthread_create_lock);create_kthread(create);spin_lock(&kthread_create_lock);}spin_unlock(&kthread_create_lock);}return 0;
}

经过上面的分析可以知道pid-0是所有进程/线程的祖先,init负责所有用户空间进程创建,kthreadd是所有内核线程的祖先。

简单看一个系统的进程执行snap如下:

  PID USER      PR  NI    VIRT    RES %CPU %MEM     TIME+ S COMMAND1 root      20   0    2.3m   1.7m  0.0  0.2   0:01.75 S init------------------------------所有用户空间进程的祖先。135 root      20   0    2.3m   1.9m  0.0  0.3   0:00.02 S  `- syslogd138 root      20   0    2.3m   1.8m  0.0  0.2   0:00.03 S  `- klogd143 root      20   0    4.1m   3.1m  0.0  0.4   0:00.00 S  `- sshd155 root      20   0    2.3m   1.6m  0.0  0.2   0:00.04 S  `- autologin156 root      20   0    2.3m   1.9m  0.0  0.3   0:00.10 S      `- sh161 root      20   0    2.3m   1.6m  0.0  0.2   0:00.28 S          `- monito+629 root      20   0    2.2m   1.3m  0.5  0.2   0:00.01 S              `- sl+623 root      20   0    2.7m   1.7m  2.1  0.2   0:00.14 R          `- top2 root      20   0    0.0m   0.0m  0.0  0.0   0:00.00 S kthreadd---------------------------所有内核线程的祖先。3 root      20   0    0.0m   0.0m  0.0  0.0   0:00.27 S  `- ksoftirqd/04 root      20   0    0.0m   0.0m  0.0  0.0   0:00.04 S  `- kworker/0:05 root       0 -20    0.0m   0.0m  0.0  0.0   0:00.00 S  `- kworker/0:0H6 root      20   0    0.0m   0.0m  0.0  0.0   0:00.04 S  `- kworker/u2:0

2. init(of busybox)分析

init_main()也即busybox中的init进程入口。init上承kernel,下起用户空间进程,配置了整个用户空间工作环境。

首先初始化串口、环境变量等;解析/etc/inittab文件;初始化信号处理函数;然后依次执行SYSINIT、WAIT、ONCE选项;最后在while(1)中监控RESPAWN|ASKFIRST选项。

int init_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int init_main(int argc UNUSED_PARAM, char **argv)
{if (argv[1] && strcmp(argv[1], "-q") == 0) {return kill(1, SIGHUP);}
...die_func = sleep_much;console_init();set_sane_term();xchdir("/");setsid();/* Make sure environs is set to something sane */----------------------设置环境变量,SHELL指向/bin/sh。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_QUIETmessage(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'))) {new_init_action(RESPAWN, bb_default_login_shell, "");} else {parse_inittab();---------------------------------------------------解析/etc/inittab文件,下面按照SYSINIT->WAIT->ONCE->RESPAWN|ASKFIRST顺序执行inittab内容。}
...if (ENABLE_FEATURE_INIT_MODIFY_CMDLINE) {strncpy(argv[0], "init", strlen(argv[0]));while (*++argv)nuke_str(*argv);}if (!DEBUG_INIT) {-----------------------------------------------------初始化信号处理。struct sigaction sa;memset(&sa, 0, sizeof(sa));sigfillset(&sa.sa_mask);sigdelset(&sa.sa_mask, SIGCONT);sa.sa_handler = stop_handler;sigaction_set(SIGTSTP, &sa); /* pause */sigaction_set(SIGSTOP, &sa); /* pause */bb_signals_recursive_norestart(0+ (1 << SIGINT)  /* Ctrl-Alt-Del */+ (1 << SIGQUIT) /* re-exec another init */
#ifdef SIGPWR+ (1 << SIGPWR)  /* halt */
#endif+ (1 << SIGUSR1) /* halt */+ (1 << SIGTERM) /* reboot */+ (1 << SIGUSR2) /* poweroff */
#if ENABLE_FEATURE_USE_INITTAB+ (1 << SIGHUP)  /* reread /etc/inittab */
#endif, record_signo);}/* Now run everything that needs to be run *//* First run the sysinit command */run_actions(SYSINIT);---------------------------------------------------首先运行SYSINIT,其次是WAIT和ONCE,这里也体现了/etc/inittab中不同优先级。check_delayed_sigs();---------------------------------------------------检查是否收到SIGHUP、SIGINT、SIGQUIT、SIGPWR、SIGTERM等信号,并进行处理。/* Next run anything that wants to block */run_actions(WAIT);check_delayed_sigs();/* Next run anything to be run only once */run_actions(ONCE);while (1) {int maybe_WNOHANG;maybe_WNOHANG = check_delayed_sigs();--------------------------------返回1表示有信号被check_delayed_sigs()检测到;0表示没有信号。run_actions(RESPAWN | ASKFIRST);-------------------------------------这里也是RESPAWN|ASKFIRST能起作用的地方,在init中循环处理。进入run_action()一看究竟。maybe_WNOHANG |= check_delayed_sigs();sleep(1);maybe_WNOHANG |= check_delayed_sigs();if (maybe_WNOHANG)maybe_WNOHANG = WNOHANG;while (1) {pid_t wpid;struct init_action *a;wpid = waitpid(-1, NULL, maybe_WNOHANG);-------------------------- -1表示等待任一子进程。若成功则返回状态改变的子进程ID,若出错则返回-1,若指定了WNOHANG选项且pid指定的子进程状态没有发生改变则返回0。if (wpid <= 0)break;a = mark_terminated(wpid);----------------------------------------将进程的init_action->pid改成0.if (a) {message(L_LOG, "process '%s' (pid %d) exited. ""Scheduling for restart.",a->command, wpid);}maybe_WNOHANG = WNOHANG;}} /* while (1) */
}

2.1 console设置

console_init()获取console文件相关环境变量,然后打开并将STDIN_FILENO和STDOUT_FILENO重定向到console。最后设置终端配置。

static void console_init(void)
{
#ifdef VT_OPENQRYint vtno;
#endifchar *s;s = getenv("CONSOLE");if (!s)s = getenv("console");
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)if (!s)s = (char*)"/dev/console";
#endifif (s) {int fd = open(s, O_RDWR | O_NONBLOCK | O_NOCTTY);if (fd >= 0) {dup2(fd, STDIN_FILENO);dup2(fd, STDOUT_FILENO);xmove_fd(fd, STDERR_FILENO);}dbg_message(L_LOG, "console='%s'", s);} else {bb_sanitize_stdio();}s = getenv("TERM");
#ifdef VT_OPENQRYif (ioctl(STDIN_FILENO, VT_OPENQRY, &vtno) != 0) {if (!s || strcmp(s, "linux") == 0)putenv((char*)"TERM=vt102");
# if !ENABLE_FEATURE_INIT_SYSLOGlog_console = NULL;
# endif} else
#endifif (!s)putenv((char*)"TERM=" CONFIG_INIT_TERMINAL_TYPE);
}

2.2 inittab解析

parse_inittab()用于解析/etc/inittab文件,并将解析结果通过new_init_action()插入到init_action_list链表中。

static void parse_inittab(void)
{char *token[4];parser_t *parser = config_open2("/etc/inittab", fopen_for_read);-------------打开/etc/inittab文件,句柄在parser->fd中。
...while (config_read(parser, token, 4, 0, "#:",PARSE_NORMAL & ~(PARSE_TRIM | PARSE_COLLAPSE))) {----------------分隔符是“#”或者“:”,解析的结果放在token[]中。按照optional_tty:ignored_runlevel:action:command顺序排布。/* 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]);----------------------------token[2]对应action类型,通过actions转化成数值,通过左移对应位数后即是new_init_action()是别的类型。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));------------------token[0]对应tty设备序号。}new_init_action(1 << action, token[3], tty);-----------------------------token[3]是应用的路径。if (tty[0])free(tty);continue;bad_entry:message(L_LOG | L_CONSOLE, "Bad inittab entry at line %d",parser->lineno);}config_close(parser);
}static void new_init_action(uint8_t action_type, const char *command, const char *cons)
{struct init_action *a, **nextp;nextp = &init_action_list;while ((a = *nextp) != NULL) {-----------------------------------------------遍历init_action_list,目的是避免重复action。如果发现已有action,则删除,然后重新加入init_action_list中。if (strcmp(a->command, command) == 0&& strcmp(a->terminal, cons) == 0) {/* Remove from list */*nextp = a->next;/* Find the end of the list */while (*nextp != NULL)nextp = &(*nextp)->next;------------------------------------------直到尾部a->next = NULL;goto append;}nextp = &a->next;---------------------------------------------------------直到尾部}a = xzalloc(sizeof(*a) + strlen(command));------------------------------------重新申请action,并重新复制。/* Append to the end of the list */append:*nextp = a;a->action_type = action_type;strcpy(a->command, command);safe_strncpy(a->terminal, cons, sizeof(a->terminal));dbg_message(L_LOG | L_CONSOLE, "command='%s' action=%x tty='%s'\n",a->command, a->action_type, a->terminal);
}

2.3 各种类型action

/etc/inittab中不同action类型有着先后顺序:SYSINIT > WAIT > ONCE > RESPAWN | ASKFIRST。

#define SYSINIT     0x01-----------------最先开始启动,并且执行完毕后才会进入WAIT。
#define WAIT        0x02-----------------在SYSINIT之后启动,并且执行完毕后才会启动ONCE。
#define ONCE        0x04-----------------在WAIT之后启动,但是后面的并不需要等待执行完毕。
#define RESPAWN     0x08-----------------在ONCE之后启动,退出后会重新启动。
#define ASKFIRST    0x10-----------------类似RESPAWN,但是需要<Enter>确认。
#define CTRLALTDEL  0x20-----------------收到SIGINIT后执行,并且执行完毕后开始执行RESPAWN和ASKFIRST。
#define SHUTDOWN    0x40-----------------在kill所有进程之后启动SHUTDOWN。这是为RESTART或者底层halt/reboot/poweroff做准备。
#define RESTART     0x80-----------------收到SIGQUIT后执行RESTART。

run_actions()运行统一action类型的所有命令。但是对于RESPAWN|ASKFIRST特殊处理。

static void run_actions(int action_type)
{struct init_action *a;for (a = init_action_list; a; a = a->next) {if (!(a->action_type & action_type))------------------------------------根据action_type进行过滤。continue;if (a->action_type & (SYSINIT | WAIT | ONCE | CTRLALTDEL | SHUTDOWN)) {-对于SYSINIT | WAIT | ONCE | CTRLALTDEL | SHUTDOWN类型action,都是无条件运行。pid_t pid = run(a);if (a->action_type & (SYSINIT | WAIT | CTRLALTDEL | SHUTDOWN))------这里的waitfor()是等待进程执行结束,说明SYSINIT | WAIT | CTRLALTDEL | SHUTDOWN几种类型的action是不允许并行的,即使同一类型action。waitfor(pid);}if (a->action_type & (RESPAWN | ASKFIRST)) {if (a->pid == 0)----------------------------------------------------pid为0是一个特殊标记,这样避免造成重复运行。不为0表示对应命令已经运行中。a->pid = run(a);}}
}static pid_t run(const struct init_action *a)
{pid_t pid;sigprocmask_allsigs(SIG_BLOCK);if (BB_MMU && (a->action_type & ASKFIRST))pid = fork();elsepid = vfork();--------------------------------------------------fork()下面父进程和子进程执行同样代码,但是可以通过pid进行区分。fork()调用一次返回两次:pid小于0表示错误;pid=0表示子进程;pid大于0位父进程中返回的子进程pid。if (pid < 0)message(L_LOG | L_CONSOLE, "can't fork");if (pid) {----------------------------------------------------------pid不为0,说明是在父进程环境中,返还pid给调用者。sigprocmask_allsigs(SIG_UNBLOCK);return pid; /* Parent or error */}/* Child */----------------------------------------------------------执行到这里说明是出于子进程中,因为pid>0。/* Reset signal handlers that were set by the parent process */reset_sighandlers_and_unblock_sigs();--------------------------------对init中设置的各种signal进行复位。setsid();------------------------------------------------------------if (!open_stdio_to_tty(a->terminal))_exit(EXIT_FAILURE);if (BB_MMU && (a->action_type & ASKFIRST)) {-------------------------对于ASKFIRST类型action,需要等待输入<Enter>。static const char press_enter[] ALIGN1 =
#ifdef CUSTOMIZED_BANNER
#include CUSTOMIZED_BANNER
#endif"\nPlease press Enter to activate this console. ";char c;dbg_message(L_LOG, "waiting for enter to start '%s'""(pid %d, tty '%s')\n",a->command, getpid(), a->terminal);full_write(STDOUT_FILENO, press_enter, sizeof(press_enter) - 1);while (safe_read(STDIN_FILENO, &c, 1) == 1 && c != '\n')continue;}
...message(L_LOG, "starting pid %u, tty '%s': '%s'",(int)getpid(), a->terminal, a->command);
init_exec(a->command);-----------------------------------------------执行对应命令。/* We're still here?  Some error happened. */_exit(-1);
}static void init_exec(const char *command)
{/* +8 allows to write VLA sizes below more efficiently: */unsigned command_size = strlen(command) + 8;/* strlen(command) + strlen("exec ")+1: */char buf[command_size];/* strlen(command) / 2 + 4: */char *cmd[command_size / 2];int dash;dash = (command[0] == '-' /* maybe? && command[1] == '/' */);command += dash;
...if (ENABLE_FEATURE_INIT_SCTTY && dash) {/* _Attempt_ to make stdin a controlling tty. */ioctl(STDIN_FILENO, TIOCSCTTY, 0 /*only try, don't steal*/);}/* Here command never contains the dash, cmd[0] might */BB_EXECVP(command, cmd);---------------------------------------------fork()创建子进程,execvp()把当前今晨替换为一个新锦成,且新锦成与元进程有相同的pid。fork()和execvp()联用将进程创建和应用加载分离。message(L_LOG | L_CONSOLE, "can't run '%s': %s", command, strerror(errno));/* returns if execvp fails */
}#define BB_EXECVP(prog,cmd)     execvp(prog,cmd)
#define BB_EXECLP(prog,cmd,...) execlp(prog,cmd,__VA_ARGS__)

2.4 异常信号处理

check_delayed_sigs()对接收到的各种异常信号进行处理,包括SIGHUP、SIGINT、SIGQUIT、SIGPWR、SIGTERM等。

static int check_delayed_sigs(void)
{int sigs_seen = 0;while (1) {smallint sig = bb_got_signal;if (!sig)return sigs_seen;bb_got_signal = 0;sigs_seen = 1;
#if ENABLE_FEATURE_USE_INITTABif (sig == SIGHUP)------------------------重新执行/etc/inittab中的选项。reload_inittab();
#endifif (sig == SIGINT)run_actions(CTRLALTDEL);--------------执行CTRLALTDEL选项。if (sig == SIGQUIT) {exec_restart_action();}if ((1 << sig) & (0
#ifdef SIGPWR+ (1 << SIGPWR)
#endif+ (1 << SIGUSR1)+ (1 << SIGUSR2)+ (1 << SIGTERM))) {halt_reboot_pwoff(sig);}}
}
static void reload_inittab(void)
{struct init_action *a, **nextp;message(L_LOG, "reloading /etc/inittab");for (a = init_action_list; a; a = a->next)a->action_type = 0;-------------------------------将init_action_list链表上所有选项清除。parse_inittab();#if ENABLE_FEATURE_KILL_REMOVEDfor (a = init_action_list; a; a = a->next)if (a->action_type == 0 && a->pid != 0)-----------对pid不为0,action_type为0的进程发送SIGTERM信号。kill(a->pid, SIGTERM);if (CONFIG_FEATURE_KILL_DELAY) {----------------------对于定义了CONFIG_FEATURE_KILL_DELAY,延迟然后发送SIGKILL信号。/* NB: parent will wait in NOMMU case */if ((BB_MMU ? fork() : vfork()) == 0) { /* child */sleep(CONFIG_FEATURE_KILL_DELAY);for (a = init_action_list; a; a = a->next)if (a->action_type == 0 && a->pid != 0)kill(a->pid, SIGKILL);_exit(EXIT_SUCCESS);}}
#endifnextp = &init_action_list;while ((a = *nextp) != NULL) {if ((a->action_type & ~SYSINIT) == 0 && a->pid == 0) {---忽略SYSINIT类型action,并且对pid为0的特殊情况交给init去处理。*nextp = a->next;free(a);} else {nextp = &a->next;}}
}

SIGQUIT信号调用exec_restart_action()来执行restart操作。

/* Handler for QUIT - exec "restart" action,* else (no such action defined) do nothing */
static void exec_restart_action(void)
{struct init_action *a;for (a = init_action_list; a; a = a->next) {if (!(a->action_type & RESTART))-----------------------只执行RESTART类型action,如果没有定义RESTART类型action则不会执行以下操作。continue;reset_sighandlers_and_unblock_sigs();run_shutdown_and_kill_processes();---------------------执行SHUTDOWN类型action,并且kill所有除init之外的进程。#ifdef RB_ENABLE_CADreboot(RB_ENABLE_CAD); /* misnomer */------------------CAD的意思是Ctrl-Alt_del,这里表示按下Ctrl-Alt-Del立即重启。
#endifif (open_stdio_to_tty(a->terminal)) {dbg_message(L_CONSOLE, "Trying to re-exec %s", a->command);init_exec(a->command);------------------------------执行RESTART类型action。}/* Open or exec failed */pause_and_low_level_reboot(RB_HALT_SYSTEM);-------------重启一个子进程执行RB_HALT_SYSTEM类型重启。/* not reached */}
}static void run_shutdown_and_kill_processes(void)
{run_actions(SHUTDOWN);--------------------------------------首先执行SHUTDOWN类型action。message(L_CONSOLE | L_LOG, "The system is going down NOW!");/* Send signals to every process _except_ pid 1 */kill(-1, SIGTERM);----------------------------------------然后分别对init进程之外的所有进程发送SIGTERM和SIGKILL信号。message(L_CONSOLE, "Sent SIG%s to all processes", "TERM");sync();sleep(1);kill(-1, SIGKILL);message(L_CONSOLE, "Sent SIG%s to all processes", "KILL");sync();/*sleep(1); - callers take care about making a pause */
}static void pause_and_low_level_reboot(unsigned magic)
{pid_t pid;/* Allow time for last message to reach serial console, etc */sleep(1);pid = vfork();if (pid == 0) { /* child */------------------------------创建一个子进程执行reboot命令。reboot(magic);_exit(EXIT_SUCCESS);}while (1)sleep(1);
}

不同信号对应不同重启操作,SIGTERM对应RB_AUTOBOOT;SIGUSR2对应RB_POWER_OFF;其余对应RB_HALT_SYSTEM。

/* The SIGPWR/SIGUSR[12]/SIGTERM handler */
static void halt_reboot_pwoff(int sig)
{const char *m;unsigned rb;reset_sighandlers_and_unblock_sigs();run_shutdown_and_kill_processes();m = "halt";rb = RB_HALT_SYSTEM;if (sig == SIGTERM) {m = "reboot";rb = RB_AUTOBOOT;} else if (sig == SIGUSR2) {m = "poweroff";rb = RB_POWER_OFF;}message(L_CONSOLE, "Requesting system %s", m);pause_and_low_level_reboot(rb);/* not reached */
}

上面这些操作对应的reboot系统调用,不同的magic表示不同的做操,具体看看内核中都做了哪些动作。

看看libc的sys/reboot.h中的定义:

/* Perform a hard reset now.  */
#define RB_AUTOBOOT    0x01234567
/* Halt the system.  */
#define RB_HALT_SYSTEM    0xcdef0123
/* Enable reboot using Ctrl-Alt-Delete keystroke.  */
#define RB_ENABLE_CAD    0x89abcdef
/* Disable reboot using Ctrl-Alt-Delete keystroke.  */
#define RB_DISABLE_CAD    0
/* Stop system and switch power off if possible.  */
#define RB_POWER_OFF    0x4321fedc
/* Suspend system using software suspend.  */
#define RB_SW_SUSPEND    0xd000fce2
/* Reboot system into new kernel.  */
#define RB_KEXEC    0x45584543

内核中命令定义如下:

#define    LINUX_REBOOT_CMD_RESTART    0x01234567------------------------重启系统,使用kernel_restart()。
#define    LINUX_REBOOT_CMD_HALT        0xCDEF0123-----------------------挂起系统,使用kernel_halt()。
#define    LINUX_REBOOT_CMD_CAD_ON        0x89ABCDEF---------------------内核变量C_A_D置位,如果为1则ctrl_alt_del()中将调用deffered_cad()函数。里面执行kernel_restart()。
#define    LINUX_REBOOT_CMD_CAD_OFF    0x00000000
#define    LINUX_REBOOT_CMD_POWER_OFF    0x4321FEDC----------------------关闭系统,移除所有供电。调用kernel_power_off()。
#define    LINUX_REBOOT_CMD_RESTART2    0xA1B2C3D4-----------------------从用户空间传入字符串,然后重启系统调用kernel_restart()。
#define    LINUX_REBOOT_CMD_SW_SUSPEND    0xD000FCE2---------------------进入休眠,调用hibernate()。
#define    LINUX_REBOOT_CMD_KEXEC        0x45584543----------------------暂停当前系统,重启一个新内核。

3. /etc/inittab解析

inittab文件中一行表示一个action。

每一行有4个组成部分,分别是:id、runlevels、action、process。

id表示process使用的tty设备;runlevels在busybox中不支持;action是sysinit、wait、once、respawn、askfirst中的一种;process是命令及其参数。

# /etc/inittab
#
# Copyright (C) 2001 Erik Andersen <andersen@codepoet.org>
#
# Note: BusyBox init doesn't support runlevels.  The runlevels field is
# completely ignored by BusyBox init. If you want runlevels, use
# sysvinit.
#
# Format for each entry: <id>:<runlevels>:<action>:<process>
#
# id        == tty to run on, or empty for /dev/console
# runlevels == ignored
# action    == one of sysinit, respawn, askfirst, wait, and once
# process   == program to run# Startup the system
::sysinit:/bin/mount -t proc proc /proc
::sysinit:/bin/mount -o remount,rw /
::sysinit:/bin/mkdir -p /dev/pts
::sysinit:/bin/mkdir -p /dev/shm
::sysinit:/bin/mount -a
::sysinit:/bin/hostname -F /etc/hostname
# now run any rc scripts
::sysinit:/etc/init.d/rcS----------------------------------------------------------------sysinit的最后一个是调用rcS。# Put a getty on the serial port
console::respawn:/sbin/getty -L -n -l /etc/autologin console 0 vt100 # GENERIC_SERIAL----login启动的console。# Stuff to do for the 3-finger salute
#::ctrlaltdel:/sbin/reboot# Stuff to do before rebooting
::shutdown:/etc/init.d/rcK---------------------------------------------------------------SHUTDOWN最先执行rcK。
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r

下面看看一个rcS示例,结合上面init进程树。

init通过/etc/inittab调用/etc/init.d/rcS,调用了S01logging和S50sshd,创建了syslogd、klogd、sshd几个进程。

#!/bin/sh# To start mdev
echo "Starting mdev..."
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
mount -t debugfs none /sys/kernel/debug# To enable watchdog
#watchdog -t 14 -T 44 /dev/watchdog# To start network
printf "Starting network: "
/sbin/ifup -a
[ $? = 0 ] && echo "OK" || echo "FAIL"#To start syslog
/etc/init.d/S01logging start#
# To start sshd
#
/etc/init.d/S50sshd start
...

init还创建了login进程,getty打开tty设备,然后调用/bin/autologin。

/bin/autologin中调用/bin/login,通过-f跳过验证。

#!/bin/sh/bin/login -f root----------------------选项-f表示不对root用户验证。

从开机到login的路径为,init -> /etc/inittab -> /sbin/getty -> /etc/autologin -> /bin/login。

4. login进程

login进程主要工作是处理用户验证,验证通过后设置新用户环境,并启动shell。

如果没有设置ENABLE_LOGIN_SESSION_AS_CHILD的情况下,shell进程会替代loging进程。

用户就得到一个新的shell环境,进行各种业务处理。

int login_main(int argc UNUSED_PARAM, char **argv)
{enum {LOGIN_OPT_f = (1<<0),LOGIN_OPT_h = (1<<1),LOGIN_OPT_p = (1<<2),};char *fromhost;
...openlog(applet_name, LOG_PID | LOG_CONS, LOG_AUTH);while (1) {/* flush away any type-ahead (as getty does) */tcflush(0, TCIFLUSH);if (!username[0])get_username_or_die(username, sizeof(username));#if ENABLE_PAM...
#else /* not PAM */pw = getpwnam(username);if (!pw) {strcpy(username, "UNKNOWN");goto fake_it;}if (pw->pw_passwd[0] == '!' || pw->pw_passwd[0] == '*')goto auth_failed;if (opt & LOGIN_OPT_f)break; /* -f USER: success without asking passwd */if (pw->pw_uid == 0 && !is_tty_secure(short_tty))goto auth_failed;/* Don't check the password if password entry is empty (!) */if (!pw->pw_passwd[0])break;fake_it:if (ask_and_check_password(pw) > 0)break;
#endif /* ENABLE_PAM */...} /* while (1) */alarm(0);if (pw->pw_uid != 0)die_if_nologin();#if ENABLE_LOGIN_SESSION_AS_CHILD-----------------------------------------------------没有定义此宏的情况下,新建的shell进程就会替代当前/bin/login进程。child_pid = vfork();if (child_pid != 0) {if (child_pid < 0)bb_perror_msg("vfork");else {if (safe_waitpid(child_pid, NULL, 0) == -1)bb_perror_msg("waitpid");update_utmp_DEAD_PROCESS(child_pid);}IF_PAM(login_pam_end(pamh);)return 0;}
#endifIF_SELINUX(initselinux(username, full_tty, &user_sid);)fchown(0, pw->pw_uid, pw->pw_gid);-------------------------------------------------将当前用户切换到登录用户id和用户组id。fchmod(0, 0600);update_utmp(getpid(), USER_PROCESS, short_tty, username, run_by_root ? opt_host : NULL);/* We trust environment only if we run by root */if (ENABLE_LOGIN_SCRIPTS && run_by_root)run_login_script(pw, full_tty);change_identity(pw);setup_environment(pw->pw_shell,(!(opt & LOGIN_OPT_p) * SETUP_ENV_CLEARENV) + SETUP_ENV_CHANGEENV,pw);
...if (access(".hushlogin", F_OK) != 0)motd();if (pw->pw_uid == 0)syslog(LOG_INFO, "root login%s", fromhost);if (ENABLE_FEATURE_CLEAN_UP)free(fromhost);IF_SELINUX(set_current_security_context(user_sid);)signal(SIGINT, SIG_DFL);/* Exec login shell with no additional parameters */run_shell(pw->pw_shell, 1, NULL);--------------------------------------------------运行shell程序,比如这里指定/bin/sh。
}

run_shell()根据shell指定的路径,additional_args附加参数到shell。

然后调用execv()来替换当前进程。

void FAST_FUNC run_shell(const char *shell, int loginshell, const char **additional_args)
{const char **args;args = additional_args;while (args && *args)args++;args = xmalloc(sizeof(char*) * (2 + (args - additional_args)));if (!shell || !shell[0])shell = DEFAULT_SHELL;------------------------------------------------------------shell参数可以通过pw->pw_shell指定,否则使用默认的DEFAULT_SHELL,指向/bin/sh。args[0] = bb_get_last_path_component_nostrip(shell);if (loginshell)args[0] = xasprintf("-%s", args[0]);args[1] = NULL;if (additional_args) {int cnt = 1;for (;;)if ((args[cnt++] = *additional_args++) == NULL)break;}
...execv(shell, (char **) args);bb_perror_msg_and_die("can't execute '%s'", shell);
}

5. ash shell

具体shell使用哪一种实现,是根据.config中的"Shells"设置。

结合上面的shell指向/bin/sh,所以最终使用的实现是ash。

CONFIG_SH_IS_ASH=y
# CONFIG_SH_IS_HUSH is not set
# CONFIG_SH_IS_NONE is not set
# CONFIG_BASH_IS_ASH is not set
# CONFIG_BASH_IS_HUSH is not set
CONFIG_BASH_IS_NONE=y
CONFIG_ASH=y

下面看看ash shell的处理流程,主要有初始化各种全局数据、解析参数,解析/etc/profile、/HOME/.profile并执行其中命令。

int ash_main(int argc UNUSED_PARAM, char **argv)
{volatile smallint state;struct jmploc jmploc;struct stackmark smark;/* Initialize global data */INIT_G_misc();---------------------------------------------------------------全局变量设置。INIT_G_memstack();INIT_G_var();
#if ENABLE_ASH_ALIASINIT_G_alias();
#endifINIT_G_cmdtable();#if PROFILEmonitor(4, etext, profile_buf, sizeof(profile_buf), 50);
#endif
...if (argv[0] && argv[0][0] == '-')--------------------------------------------如果argv[0]以‘-’开头,则表示在login上下文中。isloginsh = 1;if (isloginsh) {-------------------------------------------------------------如果当前状态时在login中,那么需要解析/etc/profile、$HOME/.profile、或者ENV变量,并执行其中内容。const char *hp;state = 1;read_profile("/etc/profile");--------------------------------------------解析/etc/profile,并执行其中的命令。state1:state = 2;hp = lookupvar("HOME");if (hp)read_profile("$HOME/.profile");}state2:state = 3;if (
#ifndef linuxgetuid() == geteuid() && getgid() == getegid() &&
#endififlag) {const char *shinit = lookupvar("ENV");if (shinit != NULL && *shinit != '\0')read_profile(shinit);}popstackmark(&smark);state3:state = 4;if (minusc) {evalstring(minusc, 0);}if (sflag || minusc == NULL) {
#if MAX_HISTORY > 0 && ENABLE_FEATURE_EDITING_SAVEHISTORYif (iflag) {const char *hp = lookupvar("HISTFILE");if (!hp) {hp = lookupvar("HOME");if (hp) {INT_OFF;hp = concat_path_file(hp, ".ash_history");setvar0("HISTFILE", hp);free((char*)hp);INT_ON;hp = lookupvar("HISTFILE");}}if (hp)line_input_state->hist_file = hp;
# if ENABLE_FEATURE_SH_HISTFILESIZEhp = lookupvar("HISTFILESIZE");line_input_state->max_history = size_from_HISTFILESIZE(hp);
# endif}
#endifstate4: /* XXX ??? - why isn't this before the "if" statement */cmdloop(1);}
#if PROFILEmonitor(0);
#endif
#ifdef GPROF{extern void _mcleanup(void);_mcleanup();}
#endifTRACE(("End of main reached\n"));exitshell();
}

再来看看上面提到的/etc/profile:

export PATH=/bin:/sbin:/usr/bin:/usr/sbinif [ "$PS1" ]; thenif [ "`id -u`" -eq 0 ]; thenexport PS1='# 'elseexport PS1='$ 'fi
fiexport PAGER='/bin/more '
export EDITOR='/bin/vi'# Source configuration files from /etc/profile.d
for i in /etc/profile.d/*.sh ; do--------------------------------遍历/etc/profile.d目录下的所有*.sh文件,并且执行。if [ -r "$i" ]; then. $ifiunset i
done

至此大概对从init到/etc/inittab,在从/etc/inittab启动各种服务,直至进入shell的流程有了大概的了解。

这里没有对ash shell、login等做详细分析。

联系方式:arnoldlu@qq.com

分类: Linux相关学习总结

busybox启动流程简单解析:从init到shell login相关推荐

  1. SSM项目的启动流程深入解析

    1 环境说明 本文的内容基于Tomcat9.0.10.Spring 4.3 2 Tomcat加载应用的顺序 在我们正式介绍SSM项目是怎么启动之前,我们要先来简单介绍一下Tomcat.很多人在介绍To ...

  2. App 启动流程全解析

    概述 Activity 启动过程分为两种,一种是根Activity的启动过程,另一种是普通Activity的启动过程.这里介绍是是根Activity的启动过程,也可以理解为应用程序启动过程. Laun ...

  3. Atmel SAMA5D3 U-Boot 启动流程简单分析

    处理器              ATSAMA5D3x 硬件平台          SAMA5D3x-EK u-boot 版本     u-boot-2012.10 先阅读链接脚本 arch/arm/ ...

  4. laravel启动过程简单解析

    laravel的启动过程 如果没有使用过类似Yii之类的框架,直接去看laravel,会有点一脸迷糊的感觉,起码我是这样的.laravel的启动过程,也是laravel的核心,对这个过程有一个了解,有 ...

  5. linux系统编码启动,Linux启动流程介绍

    一. 内核镜像Izmage被bootloader装载到内存中.zImage的入口代码是自引导程序.包含一些初始化代码.第一条指令在head.S文件中.解压内核,然后调用call_kernel启动vml ...

  6. linux基础命令介绍十三:启动流程

    固件(firmware)是指设备最底层的,让设备得以运行的程序代码.简单理解就是:固定在硬件上的软件.计算机中的许多设备都拥有固件(如硬盘.鼠标.光驱.U盘等),在计算机启动过程中,最先读取的就是位于 ...

  7. uboot启动流程概述_Alibaba Cloud Linux 2 LTS OS 启动优化实践

    Alibaba Cloud Linux 2 (原Aliyun Linux 2)是阿里云操作系统团队基于社区版 4.19 LTS 内核打造的一款针对云产品优化的下一代 Linux 操作系统发行版,不仅提 ...

  8. I.MX6 Linux Qt 启动流程跟踪

    /*************************************************************************** I.MX6 Linux Qt 启动流程跟踪* ...

  9. Android系统的心脏-Zygote进程启动流程分析

    简介: Android中,Zygote是整个Android系统的核心进程,是Android系统的心脏.所有的Android应用程序,包括Android框架层所在的进程system_server,都是由 ...

最新文章

  1. HSC86H SUMTOR 混合步进伺服电机驱动器
  2. MySQL返回多行错误怎么处理_结果包含多个行错误mysql
  3. Sublime Text 解决 Unable to download XXX 问题
  4. python中decode和encode的区别
  5. java partialfunction,scala中方法和函数的区别
  6. 【机器学习】Andrew Ng——04多变量线性回归
  7. 16 寸MacBook Pro比14 寸风扇更强大,更耐用
  8. 在主函数中输入10个等长的字符串。用另一函数对他们排序
  9. 性能测试oracle瓶颈定位,性能测试难点之瓶颈分析
  10. Python库下载第三方镜像(清华/豆瓣)
  11. 计算机专业毕业论文参考文献(精选115个最新)
  12. VS实现格式化代码及代码缩进
  13. 景区分时实名预约系统
  14. win10 戴尔电脑 禁用触摸板
  15. matlab逆变器原理,MATLAB中的单相全桥逆变器电路建模与仿真
  16. 从 WWDC17 看苹果图形技术的革新
  17. vue 移入显示_vue鼠标移入显示点赞图标,移出隐藏点赞图标,现在我想点击点赞图标,鼠标移出不会隐藏图标,怎么做?...
  18. StringBuilder和输入输出
  19. Python 哈希函数
  20. 中国软件企业自主创新

热门文章

  1. 智能秤四/八电极测脂模块,实现人体脂肪成分计量
  2. Python 数据分析实战案例:京东用户行为分析
  3. Mysql binlog数据恢复(使用mysqlbinlog_flashback逆向生成SQL语句)
  4. Happens-Before原则的通俗理解
  5. 创领3G 多普达首款TD手机S700将上市
  6. java中什么叫懒加载_java懒加载的原理
  7. JARVIS(贾维斯)来了,科技改变生活
  8. 《汇编语言》- 读书笔记 - 第1章-基础知识
  9. windows系统内存结构概述(重要概念释疑)
  10. 【Devc++】狼人杀小游戏