Linux 内核启动流程分析

分析版本为linux-2.6.22.6

UBOOT:启动内核 。

内核:1. 挂接根文件系统。 2. 启动根文件系统上的应用程序 。

内核启动流程:init_post

/* This is a non __init function. Force it to be noinline otherwise gcc
 * makes it inline to init() and it becomes part of init.text section
 */
static int noinline init_post(void)
{
    free_initmem();
    unlock_kernel();
    mark_rodata_ro();
    system_state = SYSTEM_RUNNING;
    numa_default_policy();

    if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
        printk(KERN_WARNING "Warning: unable to open an initial console.\n");

    (void) sys_dup(0);
    (void) sys_dup(0);

    if (ramdisk_execute_command) {
        run_init_process(ramdisk_execute_command);
        printk(KERN_WARNING "Failed to execute %s\n",
                ramdisk_execute_command);
    }

    /*
     * We try each of these until one succeeds.
     *
     * The Bourne shell can be used instead of init if we are
     * trying to recover a really broken machine.
     */
    if (execute_command) {
        run_init_process(execute_command);
        printk(KERN_WARNING "Failed to execute %s.  Attempting "
                    "defaults...\n", execute_command);
    }
    run_init_process("/sbin/init");
    run_init_process("/etc/init");
    run_init_process("/bin/init");
    run_init_process("/bin/sh");

    panic("No init found.  Try passing init= option to kernel.");
}
1 先打开一个设备。打开 /dev/console 设备:

```c
sys_open((const char __user *) "/dev/console", O_RDWR, 0)   //打开的第 0 个文件,指向 dev/console
    (void) sys_dup(0);  //复制第0个文件,得到第 1 个文件
    (void) sys_dup(0);  //复制第0个文件,得到第 2 个文件

//(这三个文件代表标准输入,标准输出,标准错误)

2 用 run_init_process 启动第一个应用程序:

if (execute_command) {
        run_init_process(execute_command);
        printk(KERN_WARNING "Failed to execute %s.  Attempting "
                    "defaults...\n", execute_command);
    }
    run_init_process("/sbin/init");
    run_init_process("/etc/init");
    run_init_process("/bin/init");
    run_init_process("/bin/sh");

一般来说第一个应用程序运行后,就不会从系统返回了。
这个应用程序:
(1)可以是 UBOOT 传进来的命令行参数,如: init=linuxrc
(2)也可以是 sbin/init, 要是 sbin/init 不成功,则还有 /etc/init 或 bin/init, bin/sh

例如bootargs里指定的inint bootargs=noinitrd root=/dev/mtcblock3 init=/linuxrc console=ttySAC0

init进程分析

init程序要做的事:

  1. 读取配置文件
  2. 解析配置文件
  3. 执行(用户程序)

分析busybox init.c 源码

int init_main(int argc, char **argv)
{
    struct init_action *a;
    pid_t wpid;

    die_sleep = 30 * 24*60*60; /* if xmalloc will ever die... */

    if (argc > 1 && !strcmp(argv[1], "-q")) {
        return kill(1, SIGHUP);
    }
#if !ENABLE_DEBUG_INIT
    /* Expect to be invoked as init with PID=1 or be invoked as linuxrc */
    if (getpid() != 1
     && (!ENABLE_FEATURE_INITRD || !strstr(applet_name, "linuxrc"))
    ) {
        bb_show_usage();
    }
    /* Set up sig handlers  -- be sure to
     * clear all of these in run() */
    signal(SIGHUP, exec_signal);      //这些是信号处理函数。里面有 ctrl+alt+del 信号
    signal(SIGQUIT, exec_signal);
    signal(SIGUSR1, shutdown_signal);
    signal(SIGUSR2, shutdown_signal);
    signal(SIGINT, ctrlaltdel_signal);
    signal(SIGTERM, shutdown_signal);
    signal(SIGCONT, cont_handler);
    signal(SIGSTOP, stop_handler);
    signal(SIGTSTP, stop_handler);

    /* Turn off rebooting via CTL-ALT-DEL -- we get a
     * SIGINT on CAD so we can shut things down gracefully... */
    init_reboot(RB_DISABLE_CAD);
#endif


    /* Figure out where the default console should be */
    console_init();    //控制台初始化
    set_sane_term();
    chdir("/");
    setsid();
    {
        const char *const *e;
        /* Make sure environs is set to something sane */
        for (e = environment; *e; e++)
            putenv((char *) *e);
    }

    if (argc > 1) setenv("RUNLEVEL", argv[1], 1);

    /* Hello world */
    message(MAYBE_CONSOLE | L_LOG, "init started: %s", bb_banner);

    /* Make sure there is enough memory to do something useful. */
    if (ENABLE_SWAPONOFF) {
        struct sysinfo info;

        if (!sysinfo(&info) &&
            (info.mem_unit ? : 1) * (long long)info.totalram < 1024*1024)
        {
            message(L_CONSOLE, "Low memory, forcing swapon");
            /* swapon -a requires /proc typically */
            new_init_action(SYSINIT, "mount -t proc proc /proc", "");
            /* Try to turn on swap */
            new_init_action(SYSINIT, "swapon -a", "");
            run_actions(SYSINIT);   /* wait and removing */
        }
    }

    /* Check if we are supposed to be in single user mode */
    if (argc > 1   //linux 内核启动时,执行了run_init_process("/sbin/init");没有给参数。所以argc = 1,就执行else分支,即执行parse_inittab()
     && (!strcmp(argv[1], "single") || !strcmp(argv[1], "-s") || LONE_CHAR(argv[1], '1'))
    ) {
        /* Start a shell on console */
        new_init_action(RESPAWN, bb_default_login_shell, "");
    } else {
        /* Not in single user mode -- see what inittab says */

        /* NOTE that if CONFIG_FEATURE_USE_INITTAB is NOT defined,
         * then parse_inittab() simply adds in some default
         * actions(i.e., runs INIT_SCRIPT and then starts a pair
         * of "askfirst" shells */
        parse_inittab();
    }

#if ENABLE_SELINUX
    if (getenv("SELINUX_INIT") == NULL) {
        int enforce = 0;

        putenv((char*)"SELINUX_INIT=YES");
        if (selinux_init_load_policy(&enforce) == 0) {
            BB_EXECVP(argv[0], argv);
        } else if (enforce > 0) {
            /* SELinux in enforcing mode but load_policy failed */
            /* At this point, we probably can't open /dev/console, so log() won't work */
            message(L_CONSOLE, "Cannot load SELinux Policy. "
                "Machine is in enforcing mode. Halting now.");
            exit(1);
        }
    }
#endif /* CONFIG_SELINUX */

    /* Make the command line just say "init"  -- thats all, nothing else */
    fixup_argv(argv);

    /* Now run everything that needs to be run */

    /* First run the sysinit command */
    run_actions(SYSINIT);

    /* Next run anything that wants to block */
    run_actions(WAIT);

    /* Next run anything to be run only once */
    run_actions(ONCE);

#if ENABLE_FEATURE_USE_INITTAB
    /* Redefine SIGHUP to reread /etc/inittab */
    signal(SIGHUP, reload_signal);
#else
    signal(SIGHUP, SIG_IGN);
#endif /* FEATURE_USE_INITTAB */

    /* Now run the looping stuff for the rest of forever */
    while (1) {
        /* run the respawn stuff */
        run_actions(RESPAWN);

        /* run the askfirst stuff */
        run_actions(ASKFIRST);

        /* Don't consume all CPU time -- sleep a bit */
        sleep(1);

        /* Wait for a child process to exit */
        wpid = wait(NULL);
        while (wpid > 0) {
            /* Find out who died and clean up their corpse */
            for (a = init_action_list; a; a = a->next) {
                if (a->pid == wpid) {
                    /* Set the pid to 0 so that the process gets
                     * restarted by run_actions() */
                    a->pid = 0;
                    message(L_LOG, "process '%s' (pid %d) exited. "
                            "Scheduling it for restart.",
                            a->command, wpid);
                }
            }
            /* see if anyone else is waiting to be reaped */
            wpid = waitpid(-1, NULL, WNOHANG);
        }
    }
}

init_main一开始时,会设置“信号量”:

/* Set up sig handlers  -- be sure to
     * clear all of these in run() */
    signal(SIGHUP, exec_signal);
    signal(SIGQUIT, exec_signal);
    signal(SIGUSR1, shutdown_signal);
    signal(SIGUSR2, shutdown_signal);
    signal(SIGINT, ctrlaltdel_signal);//按下 ctrlaltdel 时,内核会给 init 进程发一个信号 SIGINT。init 收到 SIGINT 信号时,就会执行“ctrlaltdel_signal”信号处理函数
    signal(SIGTERM, shutdown_signal);
    signal(SIGCONT, cont_handler);
    signal(SIGSTOP, stop_handler);
    signal(SIGTSTP, stop_handler);

static void ctrlaltdel_signal(int sig ATTRIBUTE_UNUSED)
{
    run_actions(CTRLALTDEL); //表明会去执行 CTRLALTDEL 这一类的应用程序。
}

parse_inittab() 解析 inittab 文件

/* NOTE that if CONFIG_FEATURE_USE_INITTAB is NOT defined,
 * then parse_inittab() simply adds in some default
 * actions(i.e., runs INIT_SCRIPT and then starts a pair
 * of "askfirst" shells).  If CONFIG_FEATURE_USE_INITTAB
 * _is_ defined, but /etc/inittab is missing, this
 * results in the same set of default behaviors.
 */
static void parse_inittab(void)
{
#if ENABLE_FEATURE_USE_INITTAB
    FILE *file;
    char buf[INIT_BUFFS_SIZE], lineAsRead[INIT_BUFFS_SIZE];
    char tmpConsole[CONSOLE_NAME_SIZE];
    char *id, *runlev, *action, *command, *eol;
    const struct init_action_type *a = actions;

    file = fopen(INITTAB, "r");
    if (file == NULL) {  //如果没有配置文件,那么这里会有一系列的默认配置项
        /* No inittab file -- set up some default behavior */
#endif
        /* Reboot on Ctrl-Alt-Del */
        new_init_action(CTRLALTDEL, "reboot", "");
        /* Umount all filesystems on halt/reboot */
        new_init_action(SHUTDOWN, "umount -a -r", "");
        /* Swapoff on halt/reboot */
        if (ENABLE_SWAPONOFF) new_init_action(SHUTDOWN, "swapoff -a", "");
        /* Prepare to restart init when a HUP is received */
        new_init_action(RESTART, "init", "");
        /* Askfirst shell on tty1-4 */
        new_init_action(ASKFIRST, bb_default_login_shell, "");
        new_init_action(ASKFIRST, bb_default_login_shell, VC_2);
        new_init_action(ASKFIRST, bb_default_login_shell, VC_3);
        new_init_action(ASKFIRST, bb_default_login_shell, VC_4);
        /* sysinit */
        new_init_action(SYSINIT, INIT_SCRIPT, "");

        return;
#if ENABLE_FEATURE_USE_INITTAB
    }
    while (fgets(buf, INIT_BUFFS_SIZE, file) != NULL) {//如果 INITTAB 可以打开,则放到一个缓冲区去解析
        /* Skip leading spaces */
        for (id = buf; *id == ' ' || *id == '\t'; id++);

        /* Skip the line if it's a comment */
        if (*id == '#' || *id == '\n') //如果遇到 # 号,则跳过去忽略不执行。
            continue;

        /* Trim the trailing \n */
        //XXX: chomp() ?
        eol = strrchr(id, '\n');
        if (eol != NULL)
            *eol = '\0';

        /* Keep a copy around for posterity's sake (and error msgs) */
        strcpy(lineAsRead, buf);

        /* Separate the ID field from the runlevels */
        runlev = strchr(id, ':');
        if (runlev == NULL || *(runlev + 1) == '\0') {
            message(L_LOG | L_CONSOLE, "Bad inittab entry: %s", lineAsRead);
            continue;
        } else {
            *runlev = '\0';
            ++runlev;
        }

        /* Separate the runlevels from the action */
        action = strchr(runlev, ':');
        if (action == NULL || *(action + 1) == '\0') {
            message(L_LOG | L_CONSOLE, "Bad inittab entry: %s", lineAsRead);
            continue;
        } else {
            *action = '\0';
            ++action;
        }

        /* Separate the action from the command */
        command = strchr(action, ':');
        if (command == NULL || *(command + 1) == '\0') {
            message(L_LOG | L_CONSOLE, "Bad inittab entry: %s", lineAsRead);
            continue;
        } else {
            *command = '\0';
            ++command;
        }

        /* Ok, now process it */
        for (a = actions; a->name != 0; a++) {
            if (strcmp(a->name, action) == 0) {
                if (*id != '\0') {
                    if (strncmp(id, "/dev/", 5) == 0)//id 加一个 /dev 前缀
                        id += 5;
                    strcpy(tmpConsole, "/dev/");
                    safe_strncpy(tmpConsole + 5, id,
                        sizeof(tmpConsole) - 5);
                    id = tmpConsole;
                }
                new_init_action(a->action, command, id);//解析完后,最终执行new_init_action
                break;
            }
        }
        if (a->name == 0) {
            /* Choke on an unknown action */
            message(L_LOG | L_CONSOLE, "Bad inittab entry: %s", lineAsRead);
        }
    }
    fclose(file);
#endif /* FEATURE_USE_INITTAB */
}
file = fopen(INITTAB, "r");
#define INITTAB      "/etc/inittab" /* inittab file location */

/etc/inittab 就是配置文件。

主要过程为:parse_ininttab 解析inittab, 填充数据到 init_action 结构体中,结构体保存id, 执行时机,对应的应用程序,将init_action结构体放到链表init_action_list中。

inittab 格式的说明。

inittab格式: :::
**id 项**:会加一个 /dev 前缀成为/dev/id, 用作终端(终端:stdin-printf,stdout-scanf,stderr-err)。id 可以省略。
**runlevels 项**:完全可以忽略掉。
**action 项**:指示程序何时执行。
**process 项**:是应用程序或脚本。

# : Valid actions include: sysinit, respawn, askfirst, wait, once,
#                                  restart, ctrlaltdel, and shutdown.

new_init_action 这个函数
①.创建结构体 init_action,填充:执行时机,对应执行程序,终端
②.将结构体放入链表 init_action_list

函数原型:

static void new_init_action(int action, const char *command, const char *cons)
 参一 action :应用程序执行的时机。
 参二 *command :应用程序或脚本----“"-/bin/sh"”
 参三 *cons :这是 id 应用终端---“"/dev/tty2"”
static void new_init_action(int action, const char *command, const char *cons)
{
    struct init_action *new_action, *a, *last;

    if (strcmp(cons, bb_dev_null) == 0 && (action & ASKFIRST))
        return;

    /* Append to the end of the list */
    for (a = last = init_action_list; a; a = a->next) {
        /* don't enter action if it's already in the list,
         * but do overwrite existing actions */ //若原来链表中已经有个结构和传进来的 action,*command,*cons 一样就覆盖它。
        if ((strcmp(a->command, command) == 0)
         && (strcmp(a->terminal, cons) == 0)
        ) {
            a->action = action;
            return;
        }
        last = a;
    }

    //若没有,则分配一块内存,相当于创建一个 init_action 结构。将传进来的action,command,cons 写进链表
    new_action = xzalloc(sizeof(struct init_action));//分配一块内存
    if (last) {
        last->next = new_action;
    } else {
        init_action_list = new_action;
    }
    strcpy(new_action->command, command); //这里分别对应传进来的command, action和cons
    new_action->action = action;
    strcpy(new_action->terminal, cons);
    messageD(L_LOG | L_CONSOLE, "command='%s' action=%d tty='%s'\n",
        new_action->command, new_action->action, new_action->terminal);
}

里面有一个链表: init_action_list 和结构体init_action:

struct init_action {
    struct init_action *next;
    int action;  //执行时机
    pid_t pid;   //进程号 
    char command[INIT_BUFFS_SIZE];  //对应执行程序 (-/bin/sh)
    char terminal[CONSOLE_NAME_SIZE];  //终端(/dev/tty2)
};

run_actions(SYSINIT)首先执行的是 SYSINIT 这一类。(接着执行“run_actions(WAIT)”--"run_actions(ONCE)")。

分析run_actions

/* Run all commands of a particular type */
static void run_actions(int action)
{
    struct init_action *a, *tmp;

    for (a = init_action_list; a; a = tmp) {
        tmp = a->next;
        if (a->action == action) {
            /* a->terminal of "" means "init's console" */
            if (a->terminal[0] && access(a->terminal, R_OK | W_OK)) {
                delete_init_action(a);
            } else if (a->action & (SYSINIT | WAIT | CTRLALTDEL | SHUTDOWN | RESTART)) {
                waitfor(a, 0); // 执行应用程序,等待它执行完毕
                delete_init_action(a); // 将这个init_action结构从init_action_list链表中删除掉。所以用SYSINIT定义的应用程序,执行完一次就扔掉了。后面的几类与SYSINIT是一样的,只是比在init_main中SYSINIT后执行。
            } else if (a->action & ONCE) { //对于“ONCE”这类程序,init进程不会等待它执行完毕(无waitfor函数下调用的waitpid函数)。
                run(a); // 创建process子进程,就是inittab结构中的 process中指定的应用程序(如-/bin/sh)
                delete_init_action(a);
            } else if (a->action & (RESPAWN | ASKFIRST)) {
                /* Only run stuff with pid==0.  If they have
                 * a pid, that means it is still running */
                if (a->pid == 0) {
                    a->pid = run(a);
                }
                /*
                对于“RESPAWN” “ASKFIRST”这类程序, 开始a->pid是等于0的,若run(a)它, 则给你赋个PID, 即a->pid = run(a)。init_mainwpid = wait(NULL); 是等待子进程退出。只要任意一个子进程退出时,这个子进程的 pid 就又设置为 0。然后回到 while(1)开始处重新运行,但并不是重新运行所有的子进程,而是运行那个已经退出来的子进程,因为只有a->pid == 0的程序才执行。*/
            }
        }
    }
}

你可能感兴趣的:(Linux 内核启动流程分析)