zoukankan      html  css  js  c++  java
  • 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)
    ",
                   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""wait""once""respawn""askfirst"
                "ctrlaltdel""shutdown""restart";
            int action;
            char *tty = token[0];
    
            if (!token[3]) /* less than 4 tokens */
                goto bad_entry;
            action = index_in_strings(actions, token[2]);----------------------------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'
    ",
            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
                "
    Please press Enter to activate this console. ";
            char c;
            dbg_message(L_LOG, "waiting for enter to start '%s'"
                        "(pid %d, tty '%s')
    ",
                    a->command, getpid(), a->terminal);
            full_write(STDOUT_FILENO, press_enter, sizeof(press_enter) - 1);
            while (safe_read(STDIN_FILENO, &c, 1) == 1 && c != '
    ')
                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 != '')
                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
    "));
        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等。

  • 相关阅读:
    xml读写
    scrollWidth,clientWidth与offsetWidth的区别
    DIV+CSS设计时浏览器兼容性
    访问IIS客户端出现server application error解决方案
    网站局域网内不能访问解决方法之一
    xml学习笔记(一)
    文本编辑器FCKeditor
    业务部门需要IT人员为其提供什么
    程序员与VBA之怪现状
    你的代码完成了吗?(之一)——可用性和易用性
  • 原文地址:https://www.cnblogs.com/arnoldlu/p/10868354.html
Copyright © 2011-2022 走看看