本实验完成如何构建自己所需要的根文件系统,包括初始化脚本、常用的命令、库文件及设备等。
1. 创建空的目录树
首先创建一个空的目录树,来作为根文件系统的基础。
mkdir rootfs cd rootfs/ mkdir bin dev etc lib mnt proc sbin sys tmp usr mkdir /mnt/etc tree . |-- bin |-- dev |-- etc |-- lib |-- mnt | `-- etc |-- proc |-- sbin |-- sys |-- tmp `-- usr
11 directories, 0 files |
这时rootfs 目录下还没有任何文件,下面将利用busybox 来完善根文件系统。
2. 移植BusyBox
2.1 .busybox 简介
熟悉嵌入式Linux 的人对busybox 一定不会陌生。它被非常形象地称为嵌入式Linux 系统中的“ 瑞士军刀” ,因为它将许多常用的UNIX 命令和 工具结合到了一个单独的可执行程序中。虽然与相应的GNU 工具比较起来,busybox 所提供的功能和参数略少,但在比较小的系统(例如启动盘)或者嵌入式系统中,已经足够了。
busybox 在设计上就充分考虑了硬件资源受限的特殊工作环境。它采用一种很巧妙的办法减少自己的体积:所有的命令都通过“ 插件” 的方式集中到一个可执行文件中,在实际应用过程中通过不同的符号链接来确定到底要执行哪个操作。例如最终生成的可执行文件为busybox ,当为它建立一个符号链接ls 的时候,就可以通过执行这个新命令实现列目录的功能。采用单一执行文件的方式最大限度地共享了程序代码,甚至连文件头、内存中的程序控制块等其他操作系统资源都共享了,对于资源比较紧张的系统来说,真是最合适不过了。
在busybox 的编译过程中, 可以非常方便地加减它的“ 插件” ,最后的符号链接也可以由编译系统自动生成。
2.2. 编译BusyBox
2.2.1 修改makefile
ARCH ?= arm CROSS_COMPILE ?= arm-linux- |
2.2.2 配置BusyBox
BusyBox 的配置界面如图2.1 所示。
图2.1 Busybox 配置界面
Busybox Settings ---> Build Options ---> [*] Build BusyBox as a static binary (no shared libs) //(1) |
这个选项是一定要选择的,这样才能把busybox 编译成静态链接的可执行文件,运行时才独立于其他函数库。否则必需要其他库文件才能运行,在单一个linux 内核不能使他正常工作。
Busybox Settings ---> Installation Options ---> [*] Don't use /usr //(2) |
这个选项也一定要选,否则make install 后,busybox 将安装在原系统的/usr 下,这将覆盖掉系统原有的命令。选择这个选项后,make install 后会在busybox 目录下生成一个叫_install 的目录,里面有busybox 和指向他的链接。
Linux System Utilities ---> [*] mdev //(3) [*] Support /etc/mdev.conf [*] Support command execution at device addition/removal |
成功移植完2.6.26 后,由于没有启动udev ,造成/dev 下没有设备文件。也就是说所有的设备都没有挂接进来。最新的busybox 已经包含了udev 的简化版本即mdev ,且使用非常简单。要使用mdev 还需要在rootfs 中做适当配置。
Shells ---> Choose your default shell (msh) ---> //(4) |
由于ash 功能不够强大,不能支持tab 补齐,历史纪录等等的高级功能,所以使用busybox 里面的msh 代替ash 。
配置完成之后,就可以执行命令:
make |
2.2.3 查找所依赖的共享库
cd _install/bin arm-linux-readelf -a ./busybox | grep "Shared library" |
用上面的命令可以查到当前的 busybox 依赖哪些共享库(这些共享库一般可以在你的交叉编译器所在目录的 lib 下找到)。可以看到输出结果如下:
0x00000001 (NEEDED) Shared library: [libm.so.6] 0x00000001 (NEEDED) Shared library: [libc.so.6] |
2.2.4 将生成的文件rootfs 相应目录下
在_install 目录下有bin 、sbin 、usr/bin 和usr/sbin 四个目录,并且在每个目录下都会有许多BusyBox 可执行文件的符号链接,BusyBox 可执行文件保存在bin 目录下。
cd _install cp -a * ~/rootfs |
2.2.5 出现的错误
在选择静态编译的时候,会出现编译问题。因为busybox 主要应用于对空间要求非常严格的嵌入式系统,所以它推荐使用uclibc 而不鼓励使用glibc, 如果你没有安装uclibc ,而且在 build Options 也选择了Build BusyBox as a static binary(no shared libs) ,就会出现问题,我们可以这样解决:把applets/applets.c 开头的几行 warning 注释掉。
/* #if ENABLE_STATIC && defined(__GLIBC__) && !defined(__UCLIBC__) #warning Static linking against glibc produces buggy executables #warning (glibc does not cope well with ld --gc-sections). #warning See sources.redhat.com/bugzilla/show_bug.cgi?id=3400 #warning Note that glibc is unsuitable for static linking anyway. #warning If you still want to do it, remove -Wl,--gc-sections #warning from top-level Makefile and remove this warning. #error Aborting compilation. #endif */ |
2.3 选择必要的动态共享库
为了节省Flash 空间,对于常用的函数库如libc 等,一般采用动态共享库的形式,这需要在根文件系统中添加必要的动态共享库。需要不要和主机的/lib 目录下的库文件混淆,我们这个需要添加的是用于开发板上的库文件。
一般动态链接共享库的搜索路径:
l ELF 可执行文件中动态段中DT_RPATH 所指定的路径。这实际上是通过在编译目标代码时,设置“-Wl,-rpath “指定动态库的搜索路径。
l 把需要添加的路径加入到LD_LIBRARY_PATH 中,注意如果多于一个要用冒号隔开。如:export LD_LIBRARY_PATH=/usr/local/lib/minigui 。
l 在/etc/ld.so.conf 文件中指定了默认的动态链接库查找路径,也就是说它间接的指定了定义路径的文件,我们只需要把需要的路径加到/etc/ld.so.conf.d 目录下的任何一个文件中,再运行 ldconfig 就可以了,但为了容易理解,最好是找一个相关的文件,或者重新建立一个文件,把需要添加的路径写入然后运行ldconfig 。
l 默认的动态库搜索路径/lib 。
l 默认的动态库搜索路径/usr/lib 。
在嵌入式系统中,一般可以把所需要的库统一放在/lib 目录下。如果有特殊需要可以通过设置连接参数“-Wl,-rpath “或者环境变量LD_LIBRARY_PATH 的方法来动态指定库的搜索路径。
如果在编译BusyBox 时选择的是静态链接,则可以省略此步骤。
2.4 初始化脚本
在编译内核时我们指定了内核启动之后的init 参数,即配置时将Boot options--->Default kerbel command string 设置改成了
noinitrd root=/dev/mtdblock2 init=/linuxrc console=ttySAC0,115200 mem=64M |
这时内核初始化之后就会找到/linuxrc 接着系统的初始化。在前面编译BusyBox 时已经生成了linuxrc 文件:
ls -l linuxrc lrwxrwxrwx 1 chenglian chenglian 11 2009-05-16 12:53 linuxrc -> bin/busybox |
可以看到,这里的linuxrc 实际上是指向busybox 的符号链接,需要将其删除,并编辑我们需要的linuxrc 文件,其内容如下:
# ! /bin/ash Echo “mount /etc as ramfs” /bin/mount –n –t ramfs ramfs /etc /bin/cp –a /mnt/etc/* /etc echo “re-create the /etc/mtab entries” /bin/mount –f –t cramfs –o remount,ro /dev/mtdblock/2 / echo “execute /sbin/init” exec /sbin/init |
另外,还需要设置/mnt/etc/rcS ,/mnt/etc/fstab 和/mnt/etc/profile 等。
2.5 模块和设备文件
在编译内核时除了编译内核映像zImage 之外,还编译了一些可加载模块,这时需要将它们复制到根文件系统中来。
cp –a /linux-2.6.26/lib/modules ~/rootfs/lib |
对于设备文件,由于在编译内核时加入了devfs 的支持,使得内核在申请设备的同时会向devfs 申请相应的设备文件,devfs 会在/dev 目录建立相应的设备文件,做到内核使用多少设备就建立多少设备,这样就相对简单了。
但也可以手动建立必要的设备文件,如:
cd ~/rootfs/dev mknod –m 600 mem c 1 1 mknod –m 666 null c 1 3 mknod –m 666 zero c 1 5 … |
或者采用更简单的方法——直接从桌面linux 的/dev 目录将必要的设备文件复制过去,如:
cd ~/rootfs/dev cp –R /dev/mem ./ cp –R /dev/null ./ cp –R /eev/zero ./ … |
到此为止,根文件系统就基本建立完成了,Busybox 工具,必要的库/ 模块以及设备文件和初始化脚本是构建根文件系统时需要重点考虑的内容。
3. 建立Cramfs 根文件系统映像
Cramfs 是NAND Flash 上广为使用的文件系统,这里将利用Cramfs 工具包来构建压缩的根文件系统。
3.1 Cramfs 文件系统简介
CramFS(Compressed Rom File System) 是Linux Torvalds 在Transmeta 任职时,所参与开发的文件系统。它是针对Linux 内核2.4 之后的版本所设计的一种新型只读文件系统,采用了 zlib 压缩,压缩比一般可以达到1 :2 ,但仍可以作到高效的随机读取,Linux 系统中,通常把不需要经常修改的目录压缩存放,并在系统引导的时候再将压缩文件 解开。因为Cramfs 不会影响系统的读取文件的速度,而且是一个高度压缩的文件系统。因此非常广泛应用于嵌入式系统中。
在嵌入式的环境之下,内存和外存资源都需要节约使用。如果使用RAMDISK 方式来使用文件系统,那么在系统运行之后,首先要把Flash 上的映像文件解 压缩到内存中,构造起RAMDISK 环境,才可以开始运行程序。但是它也有很致命的弱点。在正常情况下,同样的代码不仅在Flash 中占据了空间( 以压缩 后的形式存在) ,而且还在内存中占用了更大的空间( 以解压缩之后的形式存在) ,这违背了嵌入式环境下尽量节省资源的要求。
使用CramFS 文件系统就是一种解决这个问题的方式。CramFS 是一个压缩格式的文件系统,它并不需要一次性地将文件系统中的所有内容都解压缩到内存 之中,而只是在系统需要访问某个位置的数据的时候,马上计算出该数据在CramFS 中的位置,将它实时地解压缩到内存之中,然后通过对内存的访问来获取文 件系统中需要读取的数据。CramFS 中的解压缩以及解压缩之后的内存中数据存放位置都是由CramFS 文件系统本身进行维护的,用户并不需要了解具体的 实现过程,因此这种方式增强了透明度,对开发人员来说,既方便,又节省了存储空间。一个完整的cramfs 文件系统通常包含以下几个目录:
/linuxrc 启动脚本文件,由Boot Loader 核心命令行确定init=/linuxrc ,加载/etc 目录为ramfs ,(cramfs 为只读文件系统) ,拷贝/mnt/etc 到 /etc ,配置文件目录重新加载根文件系统和/etc 文件系统, 执行init 进程。
/bin 引导启动所需的命令或用户可能用的命令。
/sbin 系统管理员服务程序,其中最重要的是供内核初始化之后执行的/sbin/init 进程,系统启动时由init 解释并运行/etc/inittab, inittab 将指导int 去调用一个系统初始化程序/etc/init.d/rcS 。
/etc 特定机器的配置文件以及用户数据存放目录,其中的所有内容是在内核运行后,由linuxrc 从/mnt/etc 拷贝得到的。
/lib 文件系统上的程序所需的动态库。
/dev 驱动程序存放目录,可以在这里存放自己编写的驱动程序。
/usr 用于存放用户程序和配置文件的目录,可以根据需要进行设置。目录下的/usr/etc/rc.local 执行本地所需要的初始化,如安装核心模块,进行网络,运行应用程序,启动图形界面等。/etc/modules.conf 在系统运行期间自动加载模块。
/mnt 用于设备安装的目录。/mnt/etc/init.d/rcS 完成各个文件系统的Mount, 执行/usr/etc/rc.local ;通过rcS 可以调动dhcp 程序配置网络。rcS 执行完以后,就会打开Shell 。
/proc 系统状态文件目录, 目录中的文件可以用于访问有关内核的状态、计算机的属性、正在运行的进程的状态等信息。尽管 /proc 中的文件是虚拟的,但它们仍可以使用任何文件编辑器或像'more', 'less' 或 'cat' 这样的程序来查看。
3.2 Cramfs 工具包的使用
利用Cramfs 工具包主要是为了生成mkcramfs 和cramfsck 两个工具,其中mkcramfs 工具是用来创建Cramfs 文件系统的,而carmfsck 工具则用来进行Cramfs 文件系统的释放和检查。Cramfs 可以从http://sourceforge.net/projects/cramfs/ 下载,然后解压缩并编译,生成mkcramfs 和cramfsck 工具,如下所示:
3.3 构建Cramfs 文件系统
编译完成之后,会生成mkcramfs 和cramfsck 两个工具,其中cramfsck 工具是用来创建cramfs 文件系统的,而mkcramfs 工具则用来进行cramfs 文件系统的释放以及检查。
下面是mkcramfs 的命令格式:
mkcramfs [-h] [-e edition] [-i file] [-n name] dirname outfile |
mkcramfs 的各个参数解释如下:
-h :显示帮助信息
-e edition :设置生成的文件系统中的版本号
-i file :将一个文件映像插入这个文件系统之中
-n name :设定cramfs 文件系统的名字
dirname :指明需要被压缩的整个目录树
outfile :最终输出的文件
cramfsck 的命令格式:
cramfsck [-hv] [-x dir] file |
cramfsck 的各个参数解释如下:
-h :显示帮助信息
-x dir :释放文件到dir 所指出的目录中
-v :输出信息更加详细
file :希望测试的目标文件
下面通过mkcramfs 命令将前面做好的根文件系统压缩成Cramfs 格式的映像文件。输出为root.cramfs 文件。
mkcramfs rootfs rootfs.cramfs |
这时可以通过mount 命令来检查添加的根文件系统目录是否正确,比如用tree 命令,得到的目录树如下:
mount –o loop rootfs.cramfs /mnt cd /mnt tree -L 2 -d . |-- bin |-- dev |-- etc |-- lib |-- mnt | `-- etc |-- proc |-- sbin |-- sys |-- tmp `-- usr |-- bin `-- sbin |
3.3 下载到开发板
4. 小结
CramFS 是一个压缩格式的文件系统,如果系统存储资源比较紧张的话,采用CramFS 作为嵌入式Linux 系统的根文件系统是一个不错的选择。同时这也需要很多嵌入式Linux 的知识和技能,比如Linux 的启动过程,Busybox 的移植和使用,共享库的版本、搜索路径,文件系统的基础知识等等。这些都需要反复的实验,才能灵活运用。