文件系统是对一个存储设备上的数据和元数据进行组织的机制,根文件系统是Linux内核启动时所挂载的第一个文件系统。对于一个可启动的Linux系统,根文件系统是其不可或缺的一部分。笔者此处就根文件系统的构建作一个简单的介绍。

1、根文件系统概述

Linux系统为了精简以及便于维护,分成了内核空间用户空间。Linux内核由内存管理、进程管理、设备驱动程序、网络管理等组成,它是操作系统的核心,具有很多最基本的功能,决定了系统的性能和稳定性。用户空间的文件系统用来提供管理系统的各种配置,提供相应的应用程序、服务、数据交换等。文件系统作为一种载体,它是用来实现用户与操作系统内核的交互。

Linux内核启动完成之后,会尝试执行用户空间的第一个程序:init进程(用户空间根据这个进程,派生出其它的用户子进程)。由于内核空间与用户空间的分离,Linux内核必须要有一定的方法去加载执行用户空间的init进程,通常Linux内核会在根文件系统相应的目录去尝试执行这个用户进程,如果未能加载执行,则内核panic。因此,一个可启动的Linux系统必须包含Linux内核以及一个根文件系统。

2、构建最简单的根文件系统

根文件系统可以有不同的文件系统类型,如yaffs、网络文件系统NFS、initramfs等,也可以在不同的存储设备上,如nandflash、sd/mmc、u盘等。

此处以initramfs为例说明根文件系统的构建。initramfs是一个ram文件系统,在initrd技术问题背景下,所提出一种更简单、更高效的新的处理方式。基于内存的文件系统,往往只是做为一种过渡手段,用来挂载系统真正的根文件系统,但对于很多嵌入式系统来说,initramfs往往也是最终的文件系统。

如前面所述,Linux内核启动到最后会尝试加载执行根文件系统里的init用户程序,因此根文件系统里只要给内核提供init可执行文件,转到用户空间,Linux系统则算启动成功。

此处编写一个简单的init应用程序 Hello World :

#include "stdio.h"
#include "stdlib.h"

int main(void)
{
    int i;
    i = 0;

    while(1)
    {
        printf("Hello world %d\n", i);
        sleep(1);
        i++;
    }
}

此处可用宿主机gcc进行编译执行以验证代码效果,然后用 arm-linux-gcc 静态编译,使之生成arm CPU可执行的指令,并且可脱离任何库独立运行

arm-linux-gcc -static -o init init.c

生成init可执行文件,用readelf命令查看确保生成的init可执行文件为armv5指令集,软浮点(s3c2416)

在PC机上的根目录/root/rootfs/目录下创建一个example文件夹用作为根文件系统目录,把init可执行文件放在example目录下,由于用printf打印,需要使用控制台(console),因为还需要控制台设备文件,所以此时我们需要创建dev目录,在dev目录下创建console设备节点

#假设当前在dev目录下了:
mknod console c 5 1

通过下图可以看出创建设备节点前后的区别:

05_构建根文件系统 - 图1


因此,在example目录下,一个最简单的打印 Hello World 的根文件系统就构建成功了,它只需init可执行文件、/dev目录及该目录下的console设备节点。

SofTool.CN Notes:
1、最初我在启动过程中遇到: Warning: unable to open an initial console.

05_构建根文件系统 - 图2


可能原因是设备节点已丢失,需要自己再手动创建一下即可。
再重新编译Linux后更新到开发板,就出现了作者上面的测试用init内容:

05_构建根文件系统 - 图3

2、此时对应的bootloader的BootCmd的值为:

noinitrd root=/dev/mtdblock3 rootfstype=yaffs2 init=/init console=ttySAC0

如果使用:

noinitrd root=/dev/nfs init=/init console=ttySAC0 nfsroot=192.168.0.10:/nfs/rootfs,nolock rw ip=192.168.0.20:192.168.0.10:192.168.0.10:255.255.255.0::eth0:off

则无法使用上面的initramfs。

编译Linux内核,使其支持initramfs,此处为了测试,内核关闭看门狗,以免不能喂狗造成系统复位。在General setup下打开支持initramfs以及相应的根文件系统目录路径:

05_构建根文件系统 - 图4

图2-1 initramfs编译进linux内核

SofTool.CN Notes:
默认”Initramfs source file(s)”为空:

05_构建根文件系统 - 图5


回车进入:

05_构建根文件系统 - 图6


然后自定义路径(在自己的PC机上的系统根目录下root,如果没有rootfs,就自己逐一新建):
/root/rootfs/example

05_构建根文件系统 - 图7


OK之后就可以看到作者写入的内容了:

05_构建根文件系统 - 图8

编译内核,最后在内核源码树 /usr 目录下有一个 initramfs_data.cpio 档案,即为initramfs,initramfs可以直接编译进内核,当独立于内核时,由bootloader进行加载进ram。这里initramfs编译进内核,bootloader只需加载linux内核并启动即可。此处把编译生成的linux内核zImage复制到sd卡的/image目录下并命名为kernel.bin,由bootloader从sd卡加载内核启动:

05_构建根文件系统 - 图9

图2-2 bootloader从sd卡启动linux内核

3、BusyBox构建根文件系统

SofTool.CN Notes:
更多关于 busybox-1.26.2 交叉编译内容,可以参考: https://www.softool.cn/blog-236.html

3.1. BusyBox概述

对于Linux系统来说,用户空间往往需要很多常用的Linux命令、工具等来跟Linux内核进行交互。这些命令集可以自行实现、也可以下载相应的命令源码进行移植,除此之外,还有一些开源的工具已经打包实现了Linux常用的命令集,如BusyBox、embutils等。其中busybox是常用的一个工具,它能够以一个极小的应用程序来提供整个命令集的功能,而且需要哪些命令都是可以配置的,这非常适合于嵌入式系统的应用。

3.2. BusyBox配置编译

busybox可以根据功能需求进行裁剪,此处我们使用静态编译。使用交叉编译工具,对于s3c2416,要求交叉编译工具编译armv5指令集,软浮点等,不是默认需指定CFLAGS

05_构建根文件系统 - 图10

图3-1 busybox配置

编译BusyBox后,在源码目录_install有linuxrc链接文件以及命令集文件夹,/bin和/sbin,其中linuxrc链接到/bin/busybox,/bin和/sbin所有命令均是链接到/bin/busybox。

SofTool.CN Notes:
只有 make install 之后才会出现 _install 目录

3.3. BusyBox根文件系统 ★

创建BusyBox目录作为根文件系统目录,把 _install 上的 /bin、/sbin、以及linuxrc 复制到 BusyBox 目录下,由于 initramfs 执行第一个用户程序为 init ,此处把 linuxrc 重命名为 init 即可

BusyBox的一些命令需要一些目录、配置文件、设备等的支持。因此,基于BusyBox的根文件系统还需创建一些目录及文件。

此处以BusyBox源码目录examples/bootfloppy/etc为例说明/etc目录及其文件内容。

BusyBox启动后,首先会解析/etc/inittab的内容,根据这个配置文件,进行相应的运行动作。

::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh
::askfirst:-/bin/sh
::ctrlaltdel:/bin/umount -a -r
::restart:/sbin/init

注:
sysinit表明系统启动后最先执行/etc/init.d/rcS启动脚本,最后启动/bin/sh子进程。

/etc/init.d/rcS内容如下:

#! /bin/sh
/bin/mount –a

上面的内容是一个Shell脚本,它用来尝试挂载 /etc/fstab 列出的所有文件系统,因此需要 /etc/fstab 文件,其内容如下:

proc           /proc    proc     defaults    0   0

可知尝试挂载proc文件系统到/proc目录,因此对于这个BusyBox自带的/etc配置例程,根文件系统目录下需/proc目录,用mkdir /proc即可创建该目录。

总结:
mount –a 指令,需要根据 /etc/fstab 文件内容来挂在谁。

在例程/etc目录下,还有一个profile文件,该文件用于配置每个用户登录系统后的环境变量。其内容如下

echo -n "Processing /etc/profile... "
PATH=/sbin:/bin
runlevel=S
prevlevel=N
umask 022
export PATH runlevel prevlevel
echo

BusyBox需要使用控制台等设备文件,因此还需要/dev目录以及该目录下一些简单的设备节点,在/dev目录下创建console、null这两个设备节点即可:

#假设当前进入了dev目录则:
mknod console c 5 1
mknod null c 1 3

SofTool.CN Notes:
前后对比图为:

05_构建根文件系统 - 图11

至此基于BusyBox例程的配置修改完成,这是根据例程所实现较简单的目录及其配置,一般根据需要使用则需存在,不需使用则无需存在的原则,可以根据根文件系统的功能,进行更复杂的配置。

3.4. BusyBox测试

在内核中修改initramfs的目录路径为/root/rootfs/busybox,进行编译内核,把生成的内核拷贝到sd卡/image目录并重命名为kernel.bin,由bootloader从sd卡加载启动BusyBox内核。

SofTool.CN Notes: 根据提示此时按回车键哟,才能进入下面的界面^_^

05_构建根文件系统 - 图12

图3-2 基于busybox的linux内核启动

测试之前的Hello world程序,./helloworld,用ctrl+c终止进程:

SofTool.CN Notes: 经测试,我的ctrl+c不起作用,改用ctrl+z后正常,奇怪?

05_构建根文件系统 - 图13

图3-3 测试Hello world子进程

用vi编辑生成文本文件,mkdir创建文件夹等,成功后,当前的文件系统有相应的操作改变,但系统重启后,基于ram文件系统的initramfs又回到之前的内容。

4、附录

对于嵌入式系统来说,基于Linux进行构建整个系统,往往是因为Linux下驱动完善,有各种优秀的开源项目,网络等各种功能支持完备,从一定程序上减少了相当的开发工作。此时的Linux系统往往尽量精简,只需达到相应的项目需求即可,采用initramfs来精简文件系统无疑是一种不错的方法,可以实现Linux系统的秒启动。

本章所述的根文件系统:
http://pan.baidu.com/s/1eSMhdOA

附录为arm交叉编译工具链下基于newlib的s3c2416 Linux启动 bootloader工程,本章所述的根文件系统,bootloader从sd卡加载内核启动,工程直接make即可:
http://pan.baidu.com/s/1c2Fz7tI

mdk下s3c2416 Linux启动bootloader工程:
http://pan.baidu.com/s/1hs1Kl16