Linux内核-文件系统启动流程

文件系统启动流程

内核打开文件系统流程大致分析

这里以linux3.4.2内核为例,大致看一下流程是如何进行的

打开控制台

//init/main.c kernel_init()
/* Open the /dev/console on the rootfs, this should never fail */
    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);

在这里打开了控制台作为标准输入,并且复制了标准输出和标准错误

打开系统默认配置文件

有命令参数

    //init_post()
    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. "
          "See Linux Documentation/init.txt for guidance.");

init_post函数中打开系统配置文件,首先看是否有可以执行的命令,如果有可以执行的命令如ramdisk_execute_command或是execute_command就去调用run_init_process函数执行

static void run_init_process(const char *init_filename)
{
    argv_init[0] = init_filename;
    kernel_execve(init_filename, argv_init, envp_init);
}

run_init_process函数底层调用的kernel_execve,也就是如果这两个命令其中一个可以执行那么就直接切过去执行而不会执行后面的代码

无命令参数

    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. "
          "See Linux Documentation/init.txt for guidance.");

如果没有前面两个命令参数,那么就去执行默认路径下的启动配置文件,只要有一个存在就会跳转过去执行,如果都没有那么就系统崩溃报错

在我自己设备上可以看到:

idwjb@dshanpi-a1:~$ ls -l /sbin/init 
lrwxrwxrwx 1 root root 22  7月  2  2025 /sbin/init -> ../lib/systemd/systemd
kidwjb@dshanpi-a1:~$ ls -l /etc/init
ls: cannot access '/etc/init': No such file or directory
kidwjb@dshanpi-a1:~$ ls -l /bin/init
ls: cannot access '/bin/init': No such file or directory
kidwjb@dshanpi-a1:~$ ls -l /bin/sh
lrwxrwxrwx 1 root root 4  3月 31  2024 /bin/sh -> dash

/sbin/init指向的是/lib/systemd/systemd,通过查看pid为1的实际进程:

kidwjb@dshanpi-a1:~$ ps -p 1 -o pid,comm,args
    PID COMMAND         COMMAND
      1 systemd         /sbin/init splash
kidwjb@dshanpi-a1:~$ 

可以看到是/sbin/init,当然一般嵌入式设备上使用更多的是busybox文件系统

# ls /sbin/init -l
lrwxrwxrwx 1 0 root  14 Jun 30 2026 /sbin/init -> ../bin/busybox

busybox文件系统初始化流程

busybox是一个集成了一百多个常用linux命令的精简工具集,它本身是一个二进制程序,但通过符号链接模拟了多种常用命令。许多嵌入式系统的rootfs(最小根文件系统)直接以busybox为基础构建,再补充必要的配置文件(如/etc/inittab)和库文件

busybox初始化开始是init_main函数

int init_main(int argc, char **argv)

在里面最开始是去设置一些信号处理函数,然后进行一些基础配置

在后面有一个对传入的参数进行解析

    /* Check if we are supposed to be in single user mode */
    if (argc > 1
     && (!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();
    }

由于从上面内核打开文件系统流程可以看到内核运行busybox时候并没有传递参数进去,所以直接执行parse_inittab

获取解析参数parse_inittab

打开inittab

在函数开始就去打开inittab文件

    #define INITTAB "/etc/inittab"

    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
    }

1.如果有/etc/inittab文件就可以直接打开内容,大致如下:

# cat /etc/inittab
::sysinit:/etc/init.d/rcS
::respawn:/sbin/inetd -f -e /etc/inetd.conf
::respawn:-/bin/sh
::restart:/sbin/init

这里面有些关键字可以在examples/inittab查看:

Format for each entry: <id>:<runlevels>:<action>:<process>

# <action>: Valid actions include: sysinit, respawn, askfirst, wait, once,
#                                  restart, ctrlaltdel, and shutdown.
# <process>: Specifies the process to be executed and it's command line.
  • id用于在busybox初始化时候去辨别控制tty,一般是”/dev/”,当然也可以不填写,就代表忽略这个字段
  • runlevels:完全忽略
  • action:包含上面那几种类型
  • process:可执行程序

每个条目都是这个写法,比如上面的::sysinit:/etc/init.d/rcS,就是id为空,runlevels为空,actionsysinit,process/etc/init.d/rcS

2.如果没有/etc/inittab文件

那么就调用new_init_action去创建新的action

static struct init_action *init_action_list = NULL;
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 */
        if ((strcmp(a->command, command) == 0)
         && (strcmp(a->terminal, cons) == 0)
        ) {
            a->action = action;
            return;
        }
        last = a;
    }

    new_action = xzalloc(sizeof(struct init_action));
    if (last) {
        last->next = new_action;
    } else {
        init_action_list = new_action;
    }
    strcpy(new_action->command, command);
    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);
}

busybox维护一个init_action_list全局链表,存放对应的命令。new_init_action函数就是把传递进入的action存放进入全局链表中

init_action定义如下:

struct init_action {
    struct init_action *next;
    int action;
    pid_t pid;
    char command[INIT_BUFFS_SIZE];
    char terminal[CONSOLE_NAME_SIZE];
};

参数传入与解析方法

  1. 用户自定义/etc/inittab配置文件,在init_main函数中进行文件的读取,并且根据文件的每一项参数,创建init_action结构体节点,并且把inittab中的所有配置项解析的init_action节点形成一个init_action_list
  2. 如果用户没有定义/etc/inittab配置文件,busybox会默认进行多个配置项节点的建立并且形成init_action_list链表

执行命令参数

在上面获取完成参数后就进入了执行参数的步骤

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);
            } else if (a->action & ONCE) {
                run(a);
                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);
                }
            }
        }
    }
}

在函数里面遍历链表,只要是传入action值的,就分为几种情况执行:

  • 如果a->terminal[0]非空并且a->terminal可读可写,那么直接删除这个命令
  • 如果action值是SYSINIT | WAIT | CTRLALTDEL | SHUTDOWN | RESTART其中一个, 就调用waitfor函数,在函数里面会去执行命令并且等待命令执行完毕然后删除并退出
static int waitfor(const struct init_action *a, pid_t pid)
{
    int runpid;
    int status, wpid;

    runpid = (NULL == a)? pid : run(a);
    while (1) {
        wpid = waitpid(runpid, &status, 0);
        if (wpid == runpid)
            break;
        if (wpid == -1 && errno == ECHILD) {
            /* we missed its termination */
            break;
        }
        /* FIXME other errors should maybe trigger an error, but allow
         * the program to continue */
    }
    return wpid;
}
  • 如果是ONCE那么就直接执行一次并且不阻塞等待命令执行完成,直接删除并退出
  • 如果是RESPAWN | ASKFIRST这种只运行在pid为0的进程上的,那么如果当前是0进程则运行他们,并且获取得到子进程pid存储到a->pid

第一阶段

在第一阶段运行的是系统启动初始化的一些命令

    /* 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);

比如上面的::sysinit:/etc/init.d/rcS::restart:/sbin/init

第二阶段

第二阶段是一个while(1)的循环

/* 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);
        }
    }

先运行RESPAWNASKFIRST,然后就进入等待子进程退出,如果没有子进程退出就重复,去执行命令,然后等待子进程退出。实现的就是shell

最小文件系统需要什么

  1. /dev/console
  2. init_main函数—>也就是需要一个文件系统,比如busybox
  3. /etc/init.d/rcS系统启动配置脚本
  4. 因为要运行shell,所以需要shell命令支持的函数—>busybox提供
# ls -l /bin
lrwxrwxrwx  1 0   root    7 jun 30 2026 /bin/ls -> busybox
lrwxrwxrwx  1 0   root    7 jun 30 2026 /bin/pwd -> busybox
lrwxrwxrwx  1 0   root    7 jun 30 2026 /bin/ascii -> busybox
.....

5.busybox的函数运行必须要有标准库函数的支持,所以文件系统中必须有glibc

上一篇
下一篇