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 ;
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 ;
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 ;
...
} 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 ;
}
经过上面的分析可以知道pid-0是所有进程/线程的祖先,init负责所有用户空间进程创建,kthreadd是所有内核线程的祖先。
简单看一个系统的进程执行snap如下:
PID USER PR NI VIRT RES %CPU %MEM TIME+ S COMMAND
root 2.3m 1.7m 0.0 0.2 :01.75 S init------------------------------所有用户空间进程的祖先。
root 2.3m 1.9m 0.0 0.3 :00.02 S `- syslogd
root 2.3m 1.8m 0.0 0.2 :00.03 S `- klogd
root 4.1m 3.1m 0.0 0.4 :00.00 S `- sshd
root 2.3m 1.6m 0.0 0.2 :00.04 S `- autologin
root 2.3m 1.9m 0.0 0.3 :00.10 S `- sh
root 2.3m 1.6m 0.0 0.2 :00.28 S `- monito+
root 2.2m 1.3m 0.5 0.2 :00.01 S `- sl+
root 2.7m 1.7m 2.1 0.2 :00.14 R `- top
root 0.0m 0.0m 0.0 0.0 :00.00 S kthreadd---------------------------所有内核线程的祖先。
root 0.0m 0.0m 0.0 0.0 :00.27 S `- ksoftirqd/
root 0.0m 0.0m 0.0 0.0 :00.04 S `- kworker/:
root - 0.0m 0.0m 0.0 0.0 :00.00 S `- kworker/:0H
root 0.0m 0.0m 0.0 0.0 :00.04 S `- kworker/u2:
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[] && strcmp(argv[], "-q") == ) {
return kill(, 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[])
xsetenv("RUNLEVEL", argv[]); #if !ENABLE_FEATURE_INIT_QUIET
message(L_CONSOLE | L_LOG, "init started: %s", bb_banner);
#endif /* Check if we are supposed to be in single user mode */
if (argv[]
&& (strcmp(argv[], "single") == || strcmp(argv[], "-s") == || LONE_CHAR(argv[], ''))
) {
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[], "init", strlen(argv[]));
while (*++argv)
nuke_str(*argv);
} if (!DEBUG_INIT) {-----------------------------------------------------初始化信号处理。
struct sigaction sa; memset(&sa, , 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(
+ ( << SIGINT) /* Ctrl-Alt-Del */
+ ( << SIGQUIT) /* re-exec another init */
#ifdef SIGPWR
+ ( << SIGPWR) /* halt */
#endif
+ ( << SIGUSR1) /* halt */
+ ( << SIGTERM) /* reboot */
+ ( << SIGUSR2) /* poweroff */
#if ENABLE_FEATURE_USE_INITTAB
+ ( << 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 () {
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();
maybe_WNOHANG |= check_delayed_sigs(); if (maybe_WNOHANG)
maybe_WNOHANG = WNOHANG;
while () {
pid_t wpid;
struct init_action *a; wpid = waitpid(-, NULL, maybe_WNOHANG);-------------------------- -1表示等待任一子进程。若成功则返回状态改变的子进程ID,若出错则返回-1,若指定了WNOHANG选项且pid指定的子进程状态没有发生改变则返回0。
if (wpid <= )
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_OPENQRY
int vtno;
#endif
char *s; s = getenv("CONSOLE");
if (!s)
s = getenv("console");
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
if (!s)
s = (char*)"/dev/console";
#endif
if (s) {
int fd = open(s, O_RDWR | O_NONBLOCK | O_NOCTTY);
if (fd >= ) {
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_OPENQRY
if (ioctl(STDIN_FILENO, VT_OPENQRY, &vtno) != ) {
if (!s || strcmp(s, "linux") == )
putenv((char*)"TERM=vt102");
# if !ENABLE_FEATURE_INIT_SYSLOG
log_console = NULL;
# endif
} else
#endif
if (!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[];
parser_t *parser = config_open2("/etc/inittab", fopen_for_read);-------------打开/etc/inittab文件,句柄在parser->fd中。
...
while (config_read(parser, token, , , "#:",
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[]; if (!token[]) /* less than 4 tokens */
goto bad_entry;
action = index_in_strings(actions, token[]);----------------------------token[2]对应action类型,通过actions转化成数值,通过左移对应位数后即是new_init_action()是别的类型。
if (action < || !token[][]) /* token[3]: command */
goto bad_entry;
/* turn .*TTY -> /dev/TTY */
if (tty[]) {
tty = concat_path_file("/dev/", skip_dev_pfx(tty));------------------token[0]对应tty设备序号。
}
new_init_action( << action, token[], tty);-----------------------------token[3]是应用的路径。
if (tty[])
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) ==
&& strcmp(a->terminal, cons) ==
) {
/* 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 == )----------------------------------------------------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();
else
pid = vfork();--------------------------------------------------fork()下面父进程和子进程执行同样代码,但是可以通过pid进行区分。fork()调用一次返回两次:pid小于0表示错误;pid=0表示子进程;pid大于0位父进程中返回的子进程pid。
if (pid < )
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) - );
while (safe_read(STDIN_FILENO, &c, ) == && 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(-);
} static void init_exec(const char *command)
{
/* +8 allows to write VLA sizes below more efficiently: */
unsigned command_size = strlen(command) + ;
/* strlen(command) + strlen("exec ")+1: */
char buf[command_size];
/* strlen(command) / 2 + 4: */
char *cmd[command_size / ];
int dash; dash = (command[] == '-' /* maybe? && command[1] == '/' */);
command += dash;
...
if (ENABLE_FEATURE_INIT_SCTTY && dash) {
/* _Attempt_ to make stdin a controlling tty. */
ioctl(STDIN_FILENO, TIOCSCTTY, /*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 = ; while () {
smallint sig = bb_got_signal; if (!sig)
return sigs_seen;
bb_got_signal = ;
sigs_seen = ;
#if ENABLE_FEATURE_USE_INITTAB
if (sig == SIGHUP)------------------------重新执行/etc/inittab中的选项。
reload_inittab();
#endif
if (sig == SIGINT)
run_actions(CTRLALTDEL);--------------执行CTRLALTDEL选项。
if (sig == SIGQUIT) {
exec_restart_action();
}
if (( << sig) & (
#ifdef SIGPWR
+ ( << SIGPWR)
#endif
+ ( << SIGUSR1)
+ ( << SIGUSR2)
+ ( << 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 = ;-------------------------------将init_action_list链表上所有选项清除。
parse_inittab(); #if ENABLE_FEATURE_KILL_REMOVED
for (a = init_action_list; a; a = a->next)
if (a->action_type == && a->pid != )-----------对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()) == ) { /* child */
sleep(CONFIG_FEATURE_KILL_DELAY);
for (a = init_action_list; a; a = a->next)
if (a->action_type == && a->pid != )
kill(a->pid, SIGKILL);
_exit(EXIT_SUCCESS);
}
}
#endif
nextp = &init_action_list;
while ((a = *nextp) != NULL) {
if ((a->action_type & ~SYSINIT) == && a->pid == ) {---忽略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_CAD
reboot(RB_ENABLE_CAD); /* misnomer */------------------CAD的意思是Ctrl-Alt_del,这里表示按下Ctrl-Alt-Del立即重启。
#endif if (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(-, SIGTERM);----------------------------------------然后分别对init进程之外的所有进程发送SIGTERM和SIGKILL信号。
message(L_CONSOLE, "Sent SIG%s to all processes", "TERM");
sync();
sleep(); kill(-, 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(); pid = vfork();
if (pid == ) { /* child */------------------------------创建一个子进程执行reboot命令。
reboot(magic);
_exit(EXIT_SUCCESS);
}
while ()
sleep();
}
不同信号对应不同重启操作,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) 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 vt100 # GENERIC_SERIAL----login启动的console。 # Stuff to do for the -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 -T /dev/watchdog # To start network
printf "Starting network: "
/sbin/ifup -a
[ $? = ] && 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 = (<<),
LOGIN_OPT_h = (<<),
LOGIN_OPT_p = (<<),
};
char *fromhost;
...
openlog(applet_name, LOG_PID | LOG_CONS, LOG_AUTH); while () {
/* flush away any type-ahead (as getty does) */
tcflush(, TCIFLUSH); if (!username[])
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[] == '!' || pw->pw_passwd[] == '*')
goto auth_failed; if (opt & LOGIN_OPT_f)
break; /* -f USER: success without asking passwd */ if (pw->pw_uid == && !is_tty_secure(short_tty))
goto auth_failed; /* Don't check the password if password entry is empty (!) */
if (!pw->pw_passwd[])
break;
fake_it:
if (ask_and_check_password(pw) > )
break;
#endif /* ENABLE_PAM */...
} /* while (1) */ alarm(); if (pw->pw_uid != )
die_if_nologin(); #if ENABLE_LOGIN_SESSION_AS_CHILD-----------------------------------------------------没有定义此宏的情况下,新建的shell进程就会替代当前/bin/login进程。
child_pid = vfork();
if (child_pid != ) {
if (child_pid < )
bb_perror_msg("vfork");
else {
if (safe_waitpid(child_pid, NULL, ) == -)
bb_perror_msg("waitpid");
update_utmp_DEAD_PROCESS(child_pid);
}
IF_PAM(login_pam_end(pamh);)
return ;
}
#endif IF_SELINUX(initselinux(username, full_tty, &user_sid);) fchown(, pw->pw_uid, pw->pw_gid);-------------------------------------------------将当前用户切换到登录用户id和用户组id。
fchmod(, ); 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) != )
motd(); if (pw->pw_uid == )
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, , 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*) * ( + (args - additional_args))); if (!shell || !shell[])
shell = DEFAULT_SHELL;------------------------------------------------------------shell参数可以通过pw->pw_shell指定,否则使用默认的DEFAULT_SHELL,指向/bin/sh。 args[] = bb_get_last_path_component_nostrip(shell);
if (loginshell)
args[] = xasprintf("-%s", args[]);
args[] = NULL;
if (additional_args) {
int cnt = ;
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_ALIAS
INIT_G_alias();
#endif
INIT_G_cmdtable(); #if PROFILE
monitor(, etext, profile_buf, sizeof(profile_buf), );
#endif
...
if (argv[] && argv[][] == '-')--------------------------------------------如果argv[0]以‘-’开头,则表示在login上下文中。
isloginsh = ;
if (isloginsh) {-------------------------------------------------------------如果当前状态时在login中,那么需要解析/etc/profile、$HOME/.profile、或者ENV变量,并执行其中内容。
const char *hp; state = ;
read_profile("/etc/profile");--------------------------------------------解析/etc/profile,并执行其中的命令。
state1:
state = ;
hp = lookupvar("HOME");
if (hp)
read_profile("$HOME/.profile");
}
state2:
state = ;
if (
#ifndef linux
getuid() == geteuid() && getgid() == getegid() &&
#endif
iflag
) {
const char *shinit = lookupvar("ENV");
if (shinit != NULL && *shinit != '\0')
read_profile(shinit);
}
popstackmark(&smark);
state3:
state = ;
if (minusc) {
evalstring(minusc, );
} if (sflag || minusc == NULL) {
#if MAX_HISTORY > 0 && ENABLE_FEATURE_EDITING_SAVEHISTORY
if (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_HISTFILESIZE
hp = lookupvar("HISTFILESIZE");
line_input_state->max_history = size_from_HISTFILESIZE(hp);
# endif
}
#endif
state4: /* XXX ??? - why isn't this before the "if" statement */
cmdloop();
}
#if PROFILE
monitor();
#endif
#ifdef GPROF
{
extern void _mcleanup(void);
_mcleanup();
}
#endif
TRACE(("End of main reached\n"));
exitshell();
}
再来看看上面提到的/etc/profile:
export PATH=/bin:/sbin:/usr/bin:/usr/sbin if [ "$PS1" ]; then
if [ "`id -u`" -eq ]; then
export PS1='# '
else
export PS1='$ '
fi
fi export 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
. $i
fi
unset i
done
至此大概对从init到/etc/inittab,在从/etc/inittab启动各种服务,直至进入shell的流程有了大概的了解。
这里没有对ash shell、login等做详细分析。
busybox启动流程简单解析:从init到shell login的更多相关文章
- Linux启动流程和服务管理(init和systemd)
目录 一:Linux启动流程 init和Systemd的区别 二:Linux服务管理(service,systemctl) 一:Linux启动流程 Rhel6启动过程: Rhel7启动过程: GRUB ...
- linux根文件系统制作,busybox启动流程分析
分析 busybox-1.1.6 启动流程,并 制作一个小的根文件系统 源码百度云链接:https://pan.baidu.com/s/1tJhwctqj4VB4IpuKCA9m1g 提取码 :l10 ...
- zookeeper启动流程简单梳理
等着測试童鞋完工,顺便里了下zookeeper的启动流程 zk3.4.6 启动脚本里面 nohup "$JAVA" "-Dzookeeper.log.dir=${ZOO_ ...
- 高通msm8994启动流程简单介绍
处理器信息 8994包括例如以下子系统: 子系统 处理器 含义 APSS 4*Cortex-A53 应用子系统 APSS 4*Cortex-A57 应用子系统 LPASS QDSP6 v5.5A(He ...
- SpringBoot启动流程原理解析(二)
在上一章我们分析了SpingBoot启动流程中实例化SpingApplication的过程. return new SpringApplication(primarySources).run(args ...
- laravel启动过程简单解析
:first-child{margin-top:0!important}img.plugin{box-shadow:0 1px 3px rgba(0,0,0,.1);border-radius:3px ...
- Springboot启动流程简单分析
springboot启动的类为SpringApplication,执行构造函数初始化属性值后进入run方法: 然后返回ConfigurableApplicationContext(spring应用). ...
- EurekaClient自动装配及启动流程解析
在上篇文章中,我们简单介绍了EurekaServer自动装配及启动流程解析,本篇文章则继续研究EurekaClient的相关代码 老规矩,先看spring.factories文件,其中引入了一个配置类 ...
- VIEWCONTROLLER的启动流程
转载自:http://sunnyyoung.net/post/ios/2015-04-22-viewcontrollerde-qi-dong-liu-cheng-yu-jie-xi VIEWCONTR ...
随机推荐
- Java程序员月薪三万,需要技术达到什么水平?
最近跟朋友在一起聚会的时候,提了一个问题,说 Java 程序员如何能月薪达到二万,技术水平需要达到什么程度?人回答说这只能是大企业或者互联网企业工程师才能拿到.也许是的,小公司或者非互联网企业拿二万的 ...
- Python用pip安装第三方库时换源下载
pip默认是从Python官网下载第三方库,从国外下载当然不如从国内下载来得快 豆瓣:https://pypi.doubanio.com/simple 还有其它源,阿里云等等,一个就够用了 用pip安 ...
- vue render函数解析
一.render 函数的作用: 写一些vue.js的template太繁琐,利用render,可以使用js来生成模板,更加灵活和简便. 二.使用render前提: 官网也说了.在深入渲染函数之前推荐阅 ...
- Mac Pro 2017款自带php与用brew重装PHP后的地址
mac pro 2017款自带PHP与apache位置: [apache]apache配置文件 :/etc/apache2/httpd.confDocumentRoot : /Library/WebS ...
- Linux系统学习 十九、VSFTP服务—虚拟用户访问—为每个虚拟用户建立自己的配置文件,单独定义权限
为每个虚拟用户建立自己的配置文件,单独定义权限 可以给每个虚拟用户单独建立目录,并建立自己的配置文件.这样方便单独配置权限,并可以单独指定上传目录 1.修改配置文件 vi /etc/vsftpd/vs ...
- Centos8 配置静态IP
安装centos 8之后,重启启动网络时,会出现以下报错 报错信息如下: Failed to start network.service: Unit network.service not found ...
- R期望
斐波那契数列--九九乘法表 # 1. 打印斐波那契数列 kl<-c(1,1) for (i in 1:8){ kl[i+2]<-kl[i]+kl[i+1] } kl # 10. 打印九九乘 ...
- Notepad++ 异常崩溃 未保存的new *文件列表没了怎么办?
今天就遇到这种问题了,把之前写的临时代码拷贝到Notepad++,不知道啥时候脑袋一抽风强迫症犯了就把所有临时代码给未保存关闭了,然后懊恼不已,百度了一下解决办法,一下就搜到了. Notepad++是 ...
- LeetCode 3: 无重复字符的最长子串 Longest Substring Without Repeating Characters
题目: 给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度. Given a string, find the length of the longest substring withou ...
- jQuery-点击返回顶部
在页面上,有时需要点击某个图标钮实现返回顶部的效果. 实现方式如下,给图标按钮增加名叫goTop-hook的类. // 点击返回顶部 $(window).scroll(function() { if ...