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 COMMAND
    1 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  `- syslogd
  138 root      20   0    2.3m   1.8m  0.0  0.2   0:00.03 S  `- klogd
  143 root      20   0    4.1m   3.1m  0.0  0.4   0:00.00 S  `- sshd
  155 root      20   0    2.3m   1.6m  0.0  0.2   0:00.04 S  `- autologin
  156 root      20   0    2.3m   1.9m  0.0  0.3   0:00.10 S      `- sh
  161 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          `- top
    2 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/0
    4 root      20   0    0.0m   0.0m  0.0  0.0   0:00.04 S  `- kworker/0:0
    5 root       0 -20    0.0m   0.0m  0.0  0.0   0:00.00 S  `- kworker/0:0H
    6 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_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[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_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 >= 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_OPENQRY
    if (ioctl(STDIN_FILENO, VT_OPENQRY, &vtno) != 0) {
        if (!s || strcmp(s, "linux") == 0)
            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[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();
    else
        pid = 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_INITTAB
        if (sig == SIGHUP)------------------------重新執行/etc/inittab中的選項。
            reload_inittab();
#endif
        if (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_REMOVED
    for (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);
        }
    }
#endif
    nextp = &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_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(-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; } #endif IF_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_ALIAS
    INIT_G_alias();
#endif
    INIT_G_cmdtable();

#if PROFILE
    monitor(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 linux
     getuid() == geteuid() && getgid() == getegid() &&
#endif
     iflag
    ) {
        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_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(1);
    }
#if PROFILE
    monitor(0);
#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 0 ]; 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等做詳細分析。

 

從以上流程分析,大概可以看出從init到進入shell,相關的配置文件為/etc/inittab、/etc/init.d/rcS、/etc/init.d/Sxx,以及shell使用的/etc/profile、$HOME/.profile、ENV等。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM