Linux根文件系统分析

来源:互联网 发布:学历 知乎 编辑:程序博客网 时间:2024/06/05 18:22

部分内容参考的博客CrazyCatJack:http://www.cnblogs.com/CrazyCatJack/p/6184564.html

整体框架:

这里写图片描述
这里写图片描述

1)在Linux kernel的源代码中,对如何启动应用程序有着明确的定义。首先我们需要挂载根文件系统,只有正确挂载了根文件系统,才能够从根文件系统中读出应用程序。我们启动的第一个程序就是init程序。init进程完成了对应用程序的各项配置(进程ID、执行时机、命令、终端、下一个执行的进程等),并最终依据配置执行了应用程序。

2)要执行应用程序,首先进行配置。配置文件inittab里有着对应用程序的详细配置,这些都是C文件。init进程读出配置、分析配置并配置应用程序、配置C库(用到很多C库里的函数)。最后执行程序。

3)Busybox是一个遵循GPL v2协议的开源项目。Busybox将众多的UNIX命令集合进一个很小的可执行程序中,可以用来替换GNU fileutils、shellutils等工具集。Busybox中各种命令与相应的GNU工具相比,所能提供的选项较少,但是能够满足一般应用。Busybox为各种小型的或者嵌入式系统提供了一个比较完全的工具集。更多详细介绍参考README。

我们执行命令的时候实际是执行busybox 命令

这里写图片描述

我们查看软连接

这里写图片描述
这里写图片描述

内核检测根文件系统并启动init

内核启动的最后一步就是启动init进程,代码在init/main.c/init_post函数

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.");}

内核启动init进程的过程如下:

(1)打开标准输入、标准输出、标准错误设备

open("/dev/console") 尝试打开/dev/console设备文件,如果成功即为init进程标准输入设备。

(void) sys_dup(0); (void) sys_dup(0);将文件描述符0复制给文件描述符1、2,所以标准输入、输出、错误都对应同一个文件(设备)

(2)如果execute_command变量指定了要运行的程序,启动它。

  if (execute_command) {    run_init_process(execute_command);  }

    其中execute_command为命令行参数,在我们uboot传给内核的参数中,init设置了init=/linuxrc,所以这里的execute_command就等于/linuxrc。
    如果传值成功则执行run_init_process,否则打印printk(KERN_WARNING “Failed to execute %s. Attempting “”defaults…\n”, execute_command);
    并接着往下执行,接着检测其他位置的init进程,若成功则执行,失败则接着往下检测,直到找到合适的init进程或者没找到则打印panic(“No init found. Try passing init= option to kernel.”);

那么这里我们可以先使用nand erase root擦除root分区,也就是说擦除根文件系统,然后启动只有bootloader和kernel的系统。在结果是否和代码中说明的一致,结果Linux kernel在启动过程中,打印出了如下的信息:

VFS: Mounted root (yaffs filesystem).  Freeing init memory: 140KWarning: unable to open an initial console.Failed to execute /linuxrc.  Attempting defaults...Kernel panic - not syncing: No init found.  Try passing init= option to kernel.

    首先是VFS:挂载了根文件系统,可能大家会问,不是刚刚已经擦除了根文件系统,为什么说这里挂载了?
    这是因为当我们擦除了根文件系统的root分区后,Linux kernel认为它是任意格式的根文件系统(其实分区里面什么都没有),而默认的又是yaffs格式,所以这里说挂载了yaffs格式的根文件系统。
    这里的warning难道不是和我们init_post函数中的printk(KERN_WARNING “Warning: unable to open an initial console.\n”);相对应吗?
    同理,Failed to execute /linuxrc. Attempting defaults…和printk(KERN_WARNING “Failed to execute %s. Attempting “”defaults…\n”, execute_command);相对应。
    Kernel panic - not syncing: No init found. Try passing init= option to kernel.和panic(“No init found. Try passing init= option to kernel.”);相对应。
so=>这证明我们的分析是正确的。

Busybox init进程的启动过程

这里写图片描述

其中与构建根文件系统关系密切的是控制台的初始化、对inittab文件的解释执行。

    内核启动init进程时已经打开“/dev/console”设备作为控制台,一般情况下Busybox init程序就使用/dev/console。
    但是如果内核启动init进程的同时设置了环境变量CONSOLE或console,则使用环境变量所指定的设备。
    在Busybox init程序中,还会检查这个设备是否可以打开,如果不能打开则使用”/dev/null”。

/etc/inittab文件的相关文档和示例代码都在Busybox的examples/inittab文件中,我们来一探究竟

查看inittab文件得知inittab格式:Format for each entry:# <id>:<runlevels>:<action>:<process>#id:        The id field is used by BusyBox init to specify the controlling tty for the specified process to run on.  #runlevels:  The runlevels field is completely ignored.#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./*******************************解析************************************/从默认的new_init_action反推出默认的配置文件:# inittab格式:# <id>:<runlevels>:<action>:<process># id => /dev/id,用作终端:stdin,stdout,stderr:printf, scanf, err(即标准输入、输出、错误设# 备),如果省略,则使用与Init进程一样的控制台。# runlevels : 忽略 # action      :执行时机 sysinit, respawn, askfirst, wait, once,#                        restart, ctrlaltdel, and shutdown.# process     :应用程序或脚本,如果前有“-”字符,这个程序被称为“交互的”。

在init_main函数中,调用了parse_inittab函数来读取配置文件inittab。如果根文件系统中没有/etc/inittab文件,Busybox init程序将使用默认的inittab条目。这里我们可以通过默认的配置语句,倒推出默认的配置文件内容。

DIR: init.c-parse_inittab函数     /* 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, "");/*******************************解析************************************/::ctrlaltdel:/sbin/reboot::shutdown:/bin/umount -a -r::shutdown:/sbin/swapoff -a::restart:/sbin/init::askfirst:-/bin/shtty2::askfirst:-/bin/shtty3::askfirst:-/bin/shtty4::askfirst:-/bin/sh ::sysinit:/etc/init.d/rcS

这里涉及到了一个函数 new_init_action 。

它实际上的工作就是把各个程序的执行时机、命令行、控制台参数分别赋值给结构体,并把这些结构体组成一个单链表。这也就是我们所说的配置。

它的声明是:static void new_init_action(int action, const char *command, const char *cons);这三个参数不正是inittab配置文件中的配置命令吗?他们分别对应于<action>、<process>、<id>。

来看看new_init_action函数:

DIR:init.c-new_init_action函数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);}/* Set up a linked list of init_actions, to be read from inittab */struct init_action {   struct init_action *next;   int action;   pid_t pid;   char command[INIT_BUFFS_SIZE];   char terminal[CONSOLE_NAME_SIZE];  };

    new_init_action函数用于配置,参数为执行时机、命令行、控制台。
    结构体指针new_action开始指向上一个配置过的程序(其存储在结构体,参数是上一个程序的执行时机、命令行、控制台),这里首先进行了一个If判断,如果控制台等于bb_dev_null(宏定义等于 /dev/null)且action为ASKFIRST那么直接返回,不进行任何配置。
    接着这个for循环算是这个函数的一个重点吧,首先令结构体指针init_action_list赋值给a和last。
    这里的init_action_list(宏定义为NULL)开始为NULL,后来指向第一个配置的程序。
    也就是说,遍历所有配置过的程序,如果这个程序之前被配置过(命令行和控制台同时等于当前遍历的程序),那么执行时机action被重新赋值为当前值。
    通俗的说,这个for为了避免程序重复配置,查找之前配置过的程序有没有当前要配置的程序,如果有,则只改变其执行时机action。命令行和控制台不变。
    如果没有,接下来为new_action重新分配内存,并且给它赋值,令它的各项信息等于当前的程序。在上面的if语句中,last->next=new_action,也就是说,将所有程序的配置结构体连成一个单链表。

    new_init_action函数讲解完毕。

经过上面的讲解,我们明白了Linux根文件系统中,对于程序的配置是在parse_inittab函数完成的,它打开配置文件inittab,将程序信息一一填入结构体init_action,并将它们连接成单链表。现在配置已经完成,下一步是执行了。接着看init_main中的代码是怎样执行应用程序的:

busybox-> init_main            parse_inittab                file = fopen(INITTAB, "r"); //打开配置文件/etc/inittab                new_init_action     // ① 创建一个init_action结构,填充                                    // ② 把这个结构放入init_action_list链表            run_actions(SYSINIT);                waitfor(a, 0);      // 执行应用程序,等待它执行完毕                    run(a)          // 创建process子进程                    waitpid(runpid, &status, 0); // 等待它结束                delete_init_action(a);// 在init_action_list链表里删除                         run_actions(WAIT);                waitfor(a, 0);      // 执行应用程序,等待它执行完毕                    run(a)          // 创建process子进程                    waitpid(runpid, &status, 0); // 等待它结束                delete_init_action(a);// 在init_action_list链表里删除             run_actions(ONCE);                run(a);                delete_init_action(a);            while(1) {                run_actions(RESPAWN);                    if (a->pid == 0) {                        a->pid = run(a);                    }                run_actions(ASKFIRST);                    if (a->pid == 0) {                        a->pid = run(a);                                打印:Please press Enter to activate this console.                                等待回车                                创建子进程                    }                wpid = wait(NULL);  // 等待子进程退出                while (wpid > 0) {                    a->pid = 0;     // 退出后,就设置pid=0                }            }

在/etc/inittab文件的控制下,init进程的行为总结如下:

① 在系统启动前期,init进程首先启动为sysinit、wait、once的3类子进程。

② 在系统正常运行期间,init进程首先启动为respawn、askfirst的两类子进程。

③ 在系统退出时,执行为shutdown、restart、ctrlaltdel的三类子进程(之一或全部)

从而我们可以总结出来最小的根文件系统由5部分组成:

1./dev/console or/dev/null

2.init => busybox

3./etc/inittab

4.配置文件指定的程序

5.C库

busybox的配置、编译和安装

打开busybox自带的INSTALL文件查看我们该怎样配置、编译和安装busybox。

Building:=========The BusyBox build process is similar to the Linux kernel build:  make menuconfig     # This creates a file called ".config"  make                # This creates the "busybox" executable  make install        # or make CONFIG_PREFIX=/path/from/root installThe full list of configuration and install options is available by typing:  make help

1.配置

进入busybox文件夹

make menuconfig生成配置文件.config

这里写图片描述

2.编译

由于我们文件系统是给嵌入式板子用的,先修改Busybox的Makefile,使用交叉编译器。

修改前
ARCH        ?= $(SUBARCH)CROSS_COMPILE   ?=
修改后
ARCH        ?= $(SUBARCH)CROSS_COMPILE   ?= arm-linux-

然后make

3.安装

注意:如果你是在虚拟机上安装busybox,安装不可直接执行make INSTALL,必须在虚拟机下自己创建一个文件夹,将安装路径指向这个文件夹的路径。再执行make CONFIG_PREFIX=dir_path install否则会破坏系统。

注:除bin/busybox外,其他文件都是到bin/busybox的符号连接。busybox是所有命令的集合体,这些符号连接文件可以直接运行。比如在开发板上,运行“ls”命令和”busybox ls”命令是一样的。

原创粉丝点击