开始之前
目标
本教程展示如何在一个目标系统上安装 Linux。这不是一个预先构建的 Linux 发行版,而是您从头构建发行版。虽然在不同目标系统上安装 Linux 的过程在细节上有差异,但总的原则是相同的。
本教程帮助您构建(如果您有一个合适的目标系统)一个有效的 Linux 系统,您可以在这个系统上使用 shell 提示符。
关于本教程
本教程首先讨论交叉编译问题,然后讨论 Linux 系统的组成部分,以及它们是如何结合在一起的。本教程还谈到了构建和安装,以及目标系统的配置。
本教程讨论一个特定的目标 Technologic Systems TS-7800,它使用自己的默认启动和 bring-up 行为;其他系统将使用其他的机制,本文不详细地讨论每种可能的启动装载器。
先决条件和系统需求
本教程针对对目标嵌入式系统感兴趣,或者想学习更多关于 Linux 系统的开发人员。他们将从本教程获益不浅。
本教程使用的主机环境是 Ubuntu,但其他系统也可以。本教程假定用户基本熟悉 UNIX? 或 Linux 系统管理,并且有主机系统的根访问权限。
本教程假定您 shell 是 Bourne shell 的变体;如果您使用的是 C shell 变体,那么提示符可能会不同,需要使用不同的命令来设置环境变量。
对于交叉编译(在嵌入式系统中比较有用),我使用了 年 5 月发行的 crosstool-ng version 1.1.0。您可以从发行站点下载它(参见 参考资料)。后面有关于 安装和配置它 的详细信息。
关于目标和架构
目标
我选择的目标是一个 Technologic Systems TS-7800(详细信息请参阅 参考资料)。这是一个小型的嵌入式 ARM 系统,同时具有内置的和可移动的 flash 存储,还有一个 SATA 控制器。本教程引导您启动到一个登录提示符,而不需要依赖预先构建的二进制文件。
架构
我选择了 ARM 架构,这便于检查一个给定的二进制文件是主机还是目标,并且便于查看是否发生主机污染。使用一台总功率为 5W,能够安静运行的机器也不错。
交叉编译
什么是交叉编译?
交叉编译是在一个系统上使用编译器来开发在另一个系统上运行的代码。交叉编译对于偶尔使用 UNIX 的用户而言比较少见,因为在默认情况下,只在本系统上安装需要使用的编译器。然而,当以嵌入式系统为目标时,交叉编译就相当常见。即使主机和目标具有相同的架构,也必须区分它们的编译器。它们可能有不同版本的库,或者使用不同的编译器选项构建的库,所以用主机编译器编译的东西在目标系统上不能运行,或者不能像预期的那样运行。
获取交叉编译工具
理论上,可以自己构建一个交叉编译器,但这很不实际。因为所需的一系列启动阶段(bootstrap stage)很复杂耗时,而且常常需要构建一个非常小的编译器,用来部分地配置和构建库,然后使用这些库的头文件重新构建编译器,使新的编译器能够使用它们,等等。有很多商业源代码可以实现在不同架构组合上使用交叉编译器,也有一些免费的交叉编译工具包。
crosstool-ng 简介
Dan Kegel 的 crosstool(详细信息请参阅 参考资料)收集了多种不同的专门技术和一些专用的补丁,用于为许多系统自动构建工具链。crosstool 有一段时间没有更新了,但新的 crosstool-ng 项目是它的扩展。对于本教程,我使用了 年 5 月发行的 crosstool-ng version 1.1.0。可以从发行站点下载它(参见 参考资料)。
安装 crosstool-ng
crosstool-ng 有一个 configure 脚本。要配置它,只需使用 --prefix 来运行这个脚本,然后设置一个位置。例如:
$ ./configure --prefix=$HOME/7800/ctng
完成配置之后,分别使用 make 和 make install 来构建它。构建过程将在包含 crosstool-ng 构建脚本到 7800 工作目录下创建一个 ctng 目录。将 ctng/bin 子目录添加到路径中:
$ PATH=$PATH:$HOME/7800/ctng/bin
配置 crosstool-ng
crosstool-ng 使用一个 .config 文件,该文件类似于 Linux 内核使用的文件。为了使用 crosstool-ng,需要创建一个与目标相匹配的配置文件。为 crosstool-ng 构建创建一个工作目录:
$ mkdir toolchain-build
$ cd toolchain-build
现在,复制一个默认配置。虽然可以手动配置 crosstool-ng,但是有一个示例配置刚好符合要求:
$ cp ../ctng/lib/ct-ng-1.1.0/samples/arm-unknown-linux-uclibc/* .
最后,重命名 crosstool.config 文件:
$ mv crosstool.config .config
这将拷入一个配置文件,其目标是一个 armv5te 处理器,这是在 TS-7800 上使用的型号。它用 uClibc 构建,这是用于嵌入式系统的 libc 变种。但是,这个配置文件有一个地方需要修改。
修复配置路径
crosstool-ng 构建的默认目标目录是 $HOME/x-tools/$TARGET。例如,对于这个构建,这个目录是 x-tools/arm-unknown-linux-uclibc。如果为很多目标进行构建,这非常有用,但如果只是为一个目标进行构建,就不是很有用。编辑 .config 文件,将 CT_PREFIX_DIR 改为 ${HOME}/7800/toolchain。
构建工具链
要构建工具链,用 build 参数运行 ct-ng 脚本。为了提高性能,尤其是在多核系统上,可能需要运行多个任务,这些任务用 build.# 来指定。例如,下面的命令用 4 个任务进行构建:
$ ct-ng build.4
取决于主机系统,这可能需要较长时间。完成后,工具链被安装在 $HOME/7800/toolchain 中。该目录和它的内容被标记为只读。如果需要删除或移动它们,可以使用 chmod u+w。ct-ng 脚本还带有其他一些参数,例如 help。注意,ct-ng 是用于标准 make 实用程序的脚本,因此,--help 的输出只是标准的 make 帮助;可以使用 ct-ng help 获得关于 crosstool-ng 的帮助。
如果您以前没见过这个技巧,就会觉得它很巧妙。现代 UNIX 系统将第一行以 #! 开头的可执行文件解释为脚本,用于以第一行剩下部分命名的程序。例如,很多 shell 脚本以 #!/bin/sh 开头。该文件的文件名被传递给程序。对于将第一个参数当作要运行的脚本的程序,这样就足够了。虽然 make 不会自动那样做,但是可以使用 -f 标志为它提供一个可运行的文件。ct-ng 的第一行是 #!/usr/bin/make -rf。-r 标志禁用 make 内建的默认构造规则,而 -f 标志则告诉它接下来的单词(脚本的文件名)是一个文件的文件名,而不是一个有名称的 Makefile。结果得到一个使用 make 语法而不是 shell 语法的可执行脚本。
使用工具链
对于初学者,将包含编译器的目录添加到路径中:
$ PATH=~/7800/toolchain/bin:$PATH
目录在路径中之后,现在便可以编译程序了:
$ arm-unknown-linux-uclibc-gcc -o hello hello.c
$ file hello
hello: ELF 32-bit LSB executable, ARM, version 1 (SYSV), for
GNU/Linux 2.4.17, dynamically linked (uses shared libs), not stripped
库在哪里?
工具链用于链接二进制文件的库存储在 toolchain 目录下的 arm-unknown-linux-uclibc/sys-root 中。这个目录构成最终的根文件系统的基础,我们将在构建内核之后在 文件系统 中讨论这个话题。
内核设置
供应商提供的内核发行版树已经配置了交叉编译。在最简单的情况下(就像这里),交叉编译 Linux 内核惟一要做的就是在顶级 Makefile 中设置 CROSS_COMPILE 变量。这是一个前缀,放在构建期间使用的各个不同程序(gcc、as、ld)的名称前面。例如,如果将 CROSS_COMPILE 设置为 arm-,则编译器将尝试在路径中发现一个名为 arm-gcc 的程序。对于这个构建,正确的值是 arm-unknown-linux-uclibc。如果不想依赖于路径设置,也可以指定完整的路径,例如:
CROSS_COMPILE ?= $(HOME)/7800/toolchain/bin/arm-unknown-linux-uclibc-
构建内核
下载源代码
下载 Technologic 的 Linux 源代码和 TS-7800 配置文件,并将它们解压缩到适当位置。
内核配置
内核配置的详细讨论超出了本教程的范围。在这里,ts7800_defconfig 目标给出一个可用于 7800 的默认配置,但有一个小小的缺点:CONFIG_DMA_ENGINE 设置应该开启的时候却关闭了。
调试内核
通常最好使用 make menuconfig 编辑内核,它提供了一个半图形化的界面用于内核配置。在这个界面中,可以使用箭头键移动光标,使用 Tab 键选择屏幕底部的选项,并使用空格键或 Enter 键确定选项。例如,要不保存任何更改退出,可以按 Tab 键,直到屏幕底部的 高亮显示,然后按下 Enter。再次运行 make menuconfig 将重新打开编辑器。
更改默认的控制台
TS-7800 一般是静默启动的,因为默认内核配置指定了一个空控制台设备,它使显示屏保持安静。要改变这一点,可以使用箭头键导航到 “Boot options”,并按下 Enter 键。第三行显示默认的内核选项,它选择 ramdisk、启动脚本和控制台。使用箭头键导航到这一行,按下 Enter 键,将 console none 改为 console ttyS0,115200。然后,按 Tab 键将光标移动到面板底端的 ,并按下 Enter 键。现在按 Tab 键选择 ,并按下 Enter 键回到主菜单。
控制台设备对快速启动没有任何帮助,而且以较高的波特率发送内核消息要花费不少时间,这段时间要计入系统启动时间。但是,为了调试和测试,您需要这个控制台。
启用 DMA 引擎
向下导航到 “Device drivers”,按下 Enter 键。这个列表比通常显示的列表要长,所以必须向下滚动到快结束的地方才能到达 “DMA Engines” 选项。用箭头键导航到该选项,按下 Enter 键。在这个页面的顶端有两个带方括号的选项,表明这是布尔选项。在我使用的下载中,第二个选项 “Support for DMA engines” 在默认情况下没有启用。用箭头键导航到该选项,按下空格键切换它的状态。现在使用 Tab 和 Enter 键从屏幕中选择 ,退回到程序的顶层,然后再次选择 离开程序。当被询问是否要保存新的内核配置时,用 Tab 键选择 并按下 Enter 键。
编译内核
输入 make。没错,就这么简单!这将构建一个内核,以及一个模块集合。同样,多核用户可能需要多个任务,例如 make
-j 5。对于本项目,我打算忽略内核模块,只编译需要的特性。前面用于将所需的驱动器装入内核的启动 ramdisk 技术看上去有些繁琐,而构建一个根文件系统就已经够复杂了。当然,这又引出如何让内核启动的问题,这是下一节的主题。
启动装载器
什么是启动装载器?
启动装载器(或启动类装载器)是一个装载另一个程序的程序。启动装载器是一小块专用代码,它特定于一个目标系统,具有足够的复杂性来发现并装载内核,但不是一个包含全部功能的内核。不同的系统使用不同的启动装载器,从桌面 PC 上常见的大型的、复杂的 BIOS 程序,到嵌入式系统上常见的非常小的、简单的程序。
一个简单的启动装载器
TS-7800 使用一个非常简单的启动装载器,这个装载器只是从 SD 卡或主板 flash 一个预先确定的分区中获取内核。通过一个跳线(jumper)可以确定主板先查看主板 flash 还是先查看 SD 卡。这里没有其他配置设置。更复杂的启动装载器(例如桌面 PC 上常见的 grub)则有用于在启动时配置内核选项的选项。如前所述,在这个系统上,内核的默认选项必须编译进来。
在嵌入式系统中,决定在内核选项编译是有意义的,但是在桌面 PC 上则不合适。
内核格式
内核可以以很多不同的格式存储。最初的 Linux 内核二进制文件(名为 vmlinux)很少是启动装载器可直接使用的文件。在 TS-7800 上,启动装载器可以使用两种文件,一种是 Image(无压缩),另一种是 zImage(经过压缩)。这些文件是在内核树中的 arch/arm/boot 目录中创建的。
人们常将 zImage 内核描述为被压缩的内核,所以觉得启动装载器需要提供解压功能。实际上,它比想象的更聪明。zImage 内核是一个无压缩的可执行文件,其中包含特别大的静态数据对象,后者是一个压缩的内核镜像。当启动装载器装载并运行 zImage 可执行文件时,可执行文件就会解压内核镜像,并执行它。这样便可以最大限度地受益于压缩,而又不必在启动装载器上付出额外的努力。
设置 SD 卡
SD 卡需要一个包含 DOS 样式分区信息的 MBR 表。Technologic 站点上提供了一个可下载的示例 MBR 表。这个 MBR 表是用于 512MB 卡的,但是很容易通过编辑第 4 个分区,使之适用于任何大小的卡。前三个分区大小都是 4MB;第一个分区在较小的卡上不使用,第二和第三个分区分别存放内核和初始的 ramdisk 镜像。
对于本教程,我使用了 sfdisk 实用程序,这个实用程序并不总是值得推荐,但是当创建一个不在分区边界上的分区时,它的表现令我信服。
安装初始内核
用于 TS-7800 的内核被直接转储到 SD 卡的第二个分区中。这张 SD 卡的确切路径取决于如何访问它;通常,如果将这张卡放在一个 USB 读卡器上,那么它将被检测为一个 SCSI 设备。注意,这里没有涉及对文件系统的访问,只是将原始内核转储到分区。dd 命令将原始数据从一个源复制到另一个源,如下面的例子所示:
$ dd if=zImage of=/dev/sdd2 bs=16k
93+1 records in
93+1 records out
1536688 bytes (1.5 MB) copied, 0.847047 s, 1.8 MB/s
该命令将原始数据以 16KB 的块从 zImage 文件转储到 /dev/sdd 的第二个分区。
关于块的大小
这个命令的输出有点隐秘(和它的输入一样)。当 dd 运行时,它将数据复制到 “记录” 中,这些记录默认情况下是 512 字节的块。这个命令指定块大小为 16k(dd 理解为 16*1024)。
dd 命令报告首先复制到块中的数据量;加号后面的数字是复制的不完整的块。在这里,由于 1,536,688 不是块大小的倍数,文件中剩下的字节作为一个不完整的块单独读(写)。该信息对于大多数现代设备是无害的,但是对于一些较老的设备则是诊断问题的关键。
控制块大小(以及在传输数据时将数据重新分块)的能力对于使用磁带设备和要求以固定大小写数据的其他专用媒介特别有用,而且对于 flash 设备的性能和可靠性也有帮助。
虽然代表 flash 媒介的内核设备常常可以接受任意大小的写,但是对于底层设备,常见的是只进行整块的写,每个块的大小通常比较大(4KB 或更大)。如果要进行部分写,那么 flash 设备必须从整个块中提取当前内容,用输入数据修改它们,然后刷新整个块。如果一个设备使用 4KB 的块,并且按 512 字节的块向该设备写数据,那么对于一个复制,每个设备块要重写 8 次。这对于设备的使用寿命和性能而言很糟糕。(对于同一个文件,按 512 字节的块写数据的速度是我使用的 flash 卡的一半)。
启动内核
启动内核非常简单。将跳线设置为 SD 启动,将卡插入到系统中,然后加电。如果使用一张空白的卡,那么这将产生一个可预测的结果:
Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(1,0)
这条隐秘的消息表明,内核没能发现它的根文件系统。这意味着现在应该创建一个根文件系统。
文件系统
根文件系统
通常,Linux 只有一个根文件系统。但是,在启动期间,经常使用一个临时的根文件系统来装载和配置设备驱动器等,然后获得实际的根文件系统,并切换到该文件系统。TS-7800 为 SD 驱动器和多种不同的配置选项使用这个临时文件系统(称为初始 ramdisk 或 initrd)。实际上,这个 ramdisk 用于可能由另一个系统上的启动装载器处理的事情(例如确定最终使用的根文件系统)。
操作文件系统
有一条常见的注意事项:大多数文件系统操作需要根用户权限。但是也有一些例外和变通方法。最值得注意的是 fakeroot 实用程序/库,它允许用 “fakeroot 支持” 构建应用程序。这样便可以操纵一个虚拟根文件系统,并且可以控制所有权、许可等。但是,许可和相关材料是在一个单独的数据库中维护的,不需要真正但特权操作。这使得非根用户可以借助虚拟根权限操纵目录层次结构,并最终创建包含用户本无权限创建的文件归档或文件系统镜像。对于本教程,我假定拥有根用户权限。
更改根
chroot 命令虽然不得不提,但是对于更改根文件系统还不够。这里使用的工具是 pivot_root,它将一个有名称的目录指定为新的根目录,并将旧的根目录换为另一个名称(实际上是更改旧的根目录的挂载点)。
权限不是很充分的根
对于本教程,我假定主板提供的默认的 initrd 镜像已经足够。Linux 内核发行版具有对生成适用的 initrd 镜像的支持,这方面的细节对于理解构建一个真正的发行版并不重要。我将介绍使系统运行所需的关键东西,然后关注真正的根文件系统上是如何工作的。
文件系统镜像
大多数 Linux 用户都熟悉包含 ISO CD-ROM 或 DVD 文件系统镜像的文件。Linux 还支持其他类型的文件系统的创建和使用。实际上,通过使用 mount 的 -o loop 选项,可以将任何文件当作一个磁盘分区。例如,在研究这一点时,我为 TS-7800 使用的 initrd 镜像做了一个副本:
$ dd if=/dev/sdd3 of=initrd.dist bs=16k
$ cp initrd.dist initrd.local
有了这个文件,就可以挂载文件系统,以查看它。名为 initrd.local 的副本是用于本地编辑的版本,名为 initrd.dist 的副本是安全的原始副本,以防以后内容被损坏。
$ mount -o loop initrd.local /mnt
initrd 是做什么的
initrd 提供一个非常基本的初始文件系统,用于通过发现并挂载实际根文件系统来引导设备。默认的内核配置被硬接线为使用一个 initrd(/dev/ram0)作为根,并在启动时运行 linuxrc 脚本。这个脚本有一些用于不同根文件系统的变种。很明显,应该选择 linuxrc-sdroot 变种。
准备 initrd
linuxrc-sdroot 程序尝试将系统配置为使用一个 SD 卡作为根。假设 SD 卡经过正确的配置,且在第 4 个分区上有一个 ext3 文件系统,该脚本将挂载那个文件系统,并在它上面运行 /sbin/init。这看上去像是一个很好的策略。但还需要对 initrd 镜像作两处小小的修改。首先是将 linuxrc symlink 改为指向 linuxrc-sdroot。然后是将该脚本更新为使用 uClibc 构建,而不是之前它为之作了配置的 Debian 构建。
SD 驱动器
用于 TS-7800 的 SD 驱动器包括一个专用的模块,所以只需手动拷入 tssdcard.ko 文件,并在运行时装载它。发行版 initrd 上的文件可以在新内核上运行,因此不需要更改。
切换 bootstrap 例程
内核只需运行名为 linuxrc 的程序。默认情况下,这是程序 linuxrc-fastboot 的一个符号链接,该程序很快就会找到 initrd 根文件系统。如果去掉这个链接,而将 linuxrc 链接到 linuxrc-sdroot,则会导致在装载驱动程序之后系统尝试从 SD 上的最后一个分区启动。
更改 bootstrap 例程
该脚本检查错误,如果发现错误,则从内部 flash 挂载文件系统。如果没有发现错误,则从 SD 卡挂载文件系统,然后尝试更改它。bootstrap 例程实际上有一个小小的缺陷,这会影响到这个构建。在调用 pivot_root 之后,为了进行最后的清理工作,它尝试使用自己的 mount 实用程序,而不是根文件系统中的程序。这对于 Debian 安装是可以的,但是对于最低限度要求的 uClibc 却行不通。解决办法是更改文件最后 pivot_root 命令后面的行(在 else 子句之后):
pivot_root . ./initrd
/bin/mount -n --move ./initrd/dev ./dev
/bin/mount -n --move ./initrd/sys ./sys
/bin/mount -n --move ./initrd/proc ./proc
exec /sbin/init < $CONSOLE > $CONSOLE 2>&1
这导致脚本运行您要创建的新的可执行文件(uClibc,静态链接),而不是尝试运行动态链接的来自另一个系统的可执行文件。(在没有指导的情况下,您可能要等到完成所有其他设置并开始收到隐秘的错误消息才知道这一点)。
卸载磁盘镜像
像下面这样卸载 initrd 磁盘镜像:
$ umount /mnt
填充根文件系统
将什么装入根文件系统?
根文件系统需要一个 /sbin/init,它将做我们期望的事情。这是一个定制的程序,而不是一个常规的 UNIX 型的 init,但是如果提供一个具有控制台 shell 和所有东西的实用环境,系统就更易于使用。
基本库和头文件
crosstool-ng 构建创建了一个默认的系统根,它提供库代码和头文件。为它创建一个副本:
$ cp -r toolchain/arm-unknown-linux-uclibc/sys-root rootfs
注意,现在并没有做什么来修复许可;对于一个实际的发行版,可能需要对此作出更正,但是这对于演示并无大碍。这使您有了一个根文件系统的第一个基本部分,其中只包含将来的构建将使用的库(和头文件)。
Busybox
说到嵌入式根文件系统,busybox 可能是最好的起点。busybox 提供一套完整的核心系统实用程序,这些实用程序被构建在一个二进制文件中,它使用调用时提供的名称来确定执行什么函数。通过 busybox 和很多链接,数十个主系统程序得以挤进很小的空间中。这并不总是必需的,但对于我们来说比较方便。
下载 busybox
可以从下载页面下载 busybox(参见 参考资料)。这个归档文件是一个标准的 tarball,可以解压到一个构建目录中。我使用的是 1.10.2 版。
配置 busybox
busybox 围绕类似的配置工具构建成用于 crosstool-ng 和 Linux 的配置工具。为了构建一个默认的配置,运行 make defconfig。这将创建默认的配置。通过后面的 make menuconfig 可以更改设置;通过 “Busybox Settings” 下的 “Build Options” 菜单,可以指定一个静态的构建。运行该命令,因为 uClibc 的默认的 crosstool-ng 构建需要它。有时候,您可能偏爱一个动态链接的二进制文件,以减少可执行文件的大小,但是对于几乎每个二进制文件都只是一个符号链接的系统来说,大小问题不是那么重要。对于这个内核,顶级的 Makefile 有一个 CROSS_COMPILE 变量,该变量存放在编译器工具的名称上使用的前缀。将它设置成前面使用的同一个值。
构建 busybox
要构建 busybox,只需运行 make。令人惊讶的是,在构建期间有一个编译问题,这个编译问题要求将 添加到 libbb.h 头文件中。然后,编译很快就能完成。
安装 busybox
要安装 busybox,需要运行 make install,不过要指定根文件系统路径:
$ make CONFIG_PREFIX=$HOME/7800/rootfs install
这将拷入 busybox 二进制文件,并创建所有必要的链接。这个根文件系统现在有了库和所有常用的 UNIX 实用程序。还缺少什么呢?
设备节点
您需要设备节点。有两种方法为根文件系统提供设备节点。一种方法是静态地创建计划要有的所有设备节点,另一种方法是使用 udev 之类的东西,它为当前内核提供自动更新的设备节点树。虽然 udev 非常优雅,但是它很慢,嵌入式系统可以预测它们的硬件组件。
创建所需的设备节点
很多程序需要访问设备节点,例如 /dev/console 或 /dev/null。设备节点通常是在运行时使用 busybox 中的 mdev 实用程序在 ARM 系统上创建的。实际上,这项工作已经由 initrd 文件系统做了;一个变化是将已经填充好的 /dev 挂载点移到新的根文件系统上。您可以手动创建关键的设备节点,但是这比较麻烦。而 mdev 则更快捷。
挂载点
有些挂载点是必需的。/sys 和 /proc 挂载点用于移动 sysfs 和 procfs 虚拟文件系统。/dev 目录被用作一个 tmpfs 文件系统的挂载点,该文件系统是由 mdev 填充的。这些目录不需要任何其他内容:
$ cd $HOME/7800/rootfs
$ mkdir sys proc dev
其他
/etc 目录虽然不是获得 shell 提示符所必需的,但是它对于获得一个有用的 shell 提示符十分有用。在初始启动期间,最重要的是 init.d 子目录,init 从该目录中搜索系统启动文件。创建一个普通的 rcS 脚本,并获得初始系统启动时执行该脚本的许可:
$ mkdir rootfs/etc/init.d
创建 rootfs/etc/init.d/rcS 文件,其中包含以下内容:
#!/bin/sh
echo "Hello, world!"
现在将它标记为可执行:
$ chmod 755 rootfs/etc/init.d/rcS
创建一个磁盘镜像
创建一个大型的空文件
可以通过从 /dev/zero 中将数据库拷入一个文件中来创建一个文件系统镜像,或者直接复制一个已有的镜像。不管有多大的可用空间(记住,12MB 已经被预留给前三个分区),都可以用 dd 创建那么大的一个文件。这样将创建一个很好的空间充足的 64MB 根文件系统:
$ dd if=/dev/zero of=rootfs.local bs=1M count=64
格式化文件
不仅在磁盘上,还可以在文件上运行各种不同的 mkfs 实用程序。要构建一个 ext3 根文件系统(initrd 所需的文件系统),在磁盘镜像上运行 mkfs.ext3:
$ mkfs.ext3 rootfs.local
mkfs 实用程序提示您是否在文件不是块专业设备时仍然继续:
rootfs.tmp is not a block special device.
Proceed anyway? (y,n) y
挂载新的磁盘镜像
要挂载磁盘镜像,使用 mount 命令的 -o loop 选项将镜像挂载在某个地方。
$ mount -o loop rootfs.local /mnt
将 rootfs 文件复制到挂载的磁盘上
pax 实用程序在此特别管用。
$ cd 7800/rootfs
$ pax -r -w -p e . /mnt
这将把前面创建的目录树复制到 /mnt,保留符号链接和专用文件(除了嵌套字(socket),但是没有嵌套字也没关系)。
卸载镜像
同样,完成镜像上的工作后,卸载镜像:
$ umount /mnt
使镜像回到卡上
拷回文件
您可能想知道为什么前面的过程不直接使用卡作为文件系统。答案是,一般最好不要频繁使用 flash 文件系统,因为在重负载下 flash 设备的使用寿命远远短于硬盘驱动器。因此,复杂的编辑和复制都是先在磁盘镜像上执行,然后通过一次性的写操作将磁盘镜像拷回到卡上。
拷入 initrd
将 initrd 镜像拷入到 SD 卡的第三个分区:
$ dd if=initrd.local of=/dev/sdd3 bs=16k
拷入根文件系统
同样,将文件系统镜像拷入到 SD 卡的第四个分区。
$ dd if=rootfs.local of=/dev/sdd4 bs=16k
等待设备访问
不要马上从读卡器中拔出 SD 卡。有时候,还需多等几秒钟,让系统完成写操作。一直等到所有活动指示灯稳定下来,然后再多等几秒钟,以确保无误。
回顾启动过程
init 的作用
init 程序管理系统启动,然后使系统保持运行。例如,当进程关闭时,init 收集它们的退出状态值,使内核可以转存它们。具体的启动过程因 Linux 的版本而异。在这里,是由 busybox 版本的 init 启动测试系统。
您可能想知道,为什么前面的启动脚本使用 exec /sbin/init,而不是调用 init。原因是,启动脚本是前面的 init,有特殊的进程 ID 1。init 程序不会成为系统的启动守护进程,除非它的进程 ID 是 1;exec 导致它接过调用者 shell 的进程 ID,而不是获取一个新的 ID。
初始启动
init 程序首先运行第一个系统启动脚本 /etc/init.d/rcS。(如果没有那个文件,它打印出一条警告消息,并继续)。然后,如果有 /etc/inittab,它根据其中的指令运行。如果不存在 /etc/inittab,那么 busybox init 在控制台上运行一个 shell,并重新启动和停止请求。
结束语
有了自己的内核,一个稍加定制的 initrd,以及根自己但文件系统,现在您应该可以启动系统并进入一个 shell 提示符。如果您创建了 rcS 文件,那么系统应该会在启动时显示问候语。现在,您有了一个可以使用的小型的 Linux 系统,这个系统完全从源代码构建,并且是手工装配的。
接下来做什么就由您决定了。我建议安装一些视频游戏,不过您可以配置这个系统,使之作为数据库服务器或 Web 服务器。如果您打算做任何导致大量磁盘活动的事情,应该考虑使用一个通过 USB 连接的外部硬盘驱动器。