构建最小根文件系统

时间:2022-11-11 16:30:38
PC端服务器系统:ubuntu12.04 LTS,已安装好NFS服务,IP地址为192.168.1.61
开发板平台:TQ2440,已经成功烧写u-boot和内核,内核版本:2.6.22.6,IP地址为192.168.1.59
busybox版本:1.7.0
交叉编译器:gcc-3.4.5-glibc-2.3.6

在编译安装busybox之前,先说明一下内核启动时第一个(也是唯一的一个)用户进程(PID = 1),init进程。

内核启动的最后一步,就是启动init进程,在内核代码 init/main.c中,使用 run_init_process 函数使用其参数所指定的程序来创建一个用户进程,一旦进程创建成功,函数将不会返回。

init 进程启动过程如下:
1.打开标准输入(stdin)、标准输出(stdout)、标准错误(stderr),在内核启动过程中,如果发现打印出“Warning: unable to open an initial console.”,大多数原因是:根文件系统虽然被正确挂接了,但里面的内容不正确,要么没有/dev/console这个文件,要么它没有对应的设备

2.如果 ramdisk_execute_command 变量指定了要运行的程序,启动它。(指命令行参数指定了rdinit=...)

3.如果 execute_command 变量指定了要运行的程序,启动它。(指命令行参数指定了init=...)

4.依次尝试执行 /sbin/init、/etc/init、/bin/init、/bin/sh

init进程是在busybox里面的,它对应的代码在busybox的代码里,路径是 init/init.c。init.c的工作主要是对信号处理函数、控制台的初始化以及解释inittab,解释完inittab后,会执行inittab里面的命令。

内核在启动init进程时,已经打开了 /dev/console 作为控制台,则busybox init进程就使用 /dev/console 。如果内核在启动init进程的同时设置了环境变量CONSOLE或console,则使用环境变量指定的值。busybox的init进程还会检查这个设备是否能打开,不能打开则使用 /dev/null

busybox只作为其他进程的发起者和控制者,并不需要控制台与用户交互,所以init进程会把控制台关闭。init进程在创建其他子进程时,如果没有在 /etc/inittab 中指明它的控制台,则使用前面确定的控制台

/etc/inittab 文件的相关文档代码都在busybox-1.7.0的examples/inittab 文件中

如果存在 /etc/inittab 文件,busybox 的init进程会尝试去解析它,并按照其指示创建各种子进程,否则使用默认的配置创建子进程

/etc/inittab 文件的每个条目用来定义一个子进程,并确定其启动方法,格式如下:
<id>:<runlevels>:<action>:<process>  例如:ttySAC0::askfirst:-/bin/sh
各个字段的意思是:
id:表示这个子进程要使用的控制台(即标准输入、标准输出、标准错误设备),如果省略,则使用与init进程一样的控制台。
runlevels:对于busybox的init程序,这个字段没有意义,可以省略。
action:表示init进程如何控制这个子程序。取值为:sysinit, respawn, askfirst, wait, once, restart, ctrlaltdel,shutdown
process:要执行的程序,它可以是可执行程序,也可以是脚本。如果<process>字段有“-”字符,这个程序被称为“交互的”

在 /etc/inittab 文件的控制下,init进程的行为总结如下:
1.在系统启动前期,init进程首先启动<action>为sysinit、wait、once 的3类子进程
2.在系统正常运行期间,init进程首先启动<action>为respawn、askfirst的两类子进程并监视它们,发现某个子进程退出时重新启动它。
3.在系统退出时,执行<action>为shutdown、restart、ctrlaltdel的3类子进程(之一或全部)。

如果根文件系统中没有 /etc/inittab 文件,busybox init程序将使用如下默认的inittab条目。
::sysinit:/etc/init.d/rcS
::askfirst:/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
::restart:/sbin/init
tty2::askfirst:/bin/sh
tty3::askfirst:/bin/sh
tty4::askfirst:/bin/sh

一、移植busybox
从http://www.busybox.net/downloads/下载busybox-1.7.0.tar.bz2
使用如下命令解压busybox,tar xfj busybox-1.7.0.tar.bz2

1.配置busybox
在busybox-1.7.0的根目录下,执行“make menuconfig”命令即可进入配置界面。
选择Busybox Settings ---> Busybox Library Tuning ---> [*] Tab completion ,增加 Tab 补全功能

不使用静态链接,Build Options ---> []Build BusyBox as a static binary(no share libs)

其他选项默认即可

2.编译和安装busybox
编译之前,先修改busybox根目录下的Makefile,使用交叉编译器,修改以下内容:
ARCH                   ?=  arm
CROSS_COMPILE          ?=  arm-linux-

修改完后,在busybox的根目录下,执行 make 指令

编译完成后,安装busybox(不能直接执行 make install,否则会破坏PC端的文件系统),假设安装在 /work/nfs_root/first_fs 目录下,执行以下命令: make CONFIG_PREFIX=/work/nfs_root/first_fs install 

安装完成后,会在 /work/nfs_root/first_fs 目录下生成以下的目录、文件:
bin目录、linuxrc链接、sbin目录、usr目录

linuxrc与init进程的功能完全一样,其他目录下的各种命令都是到 /bin/busybox 的符号链接,在开发板上,运行“ls”命令和“busybox ls”命令是一样的。

二、安装glibc库
在交叉编译器的目录下(假设我的目录是:/opt/EmbedSky/crosstools_3.4.5_softfloat/gcc-3.4.5-glibc-2.3.6/arm-linux/lib),一般都会有glibc库,开发板只需要加载器和动态库,执行以下命令:
mkdir -p /work/nfs_root/first_fs/lib
cd /opt/EmbedSky/crosstools_3.4.5_softfloat/gcc-3.4.5-glibc-2.3.6/arm-linux/lib
cp *.so* /work/nfs_root/first_fs/lib -d
最后一条命令的最后一个参数 “-d”,表示保持动态库里面的链接关系,如果把-d去掉,则会把链接关系去掉,从而导致复制出来的库很大

三、构建根文件系统
1.构建 etc 目录
init进程根据 /etc/inittab 文件来创建其他子进程,比如调用脚本文件配置IP地址、挂接其他文件系统、最后启动shell等。

etc目录主要创建三个文件: etc/inittab、etc/init.d/rcS、etc/fstab

etc/inittab 文件内容如下:
#/etc/inittab
#这是init进程启动的第一个子进程,它是一个脚本,可以在里面指定用户要执行的操作
#比如挂接其他文件系统,配置网络等
::sysinit:/etc/init.d/rcS

#启动shell,以/dev/ttySAC0作为控制台
ttySAC0::askfirst:-bin/sh

#按下Ctrl+Alt+Del之后执行的程序,不过在串口控制台下无法使用Ctrl+Alt+Del组合键
::ctrlaltdel:/sbin/reboot

#重启、关机前执行的程序
::shutdown:/bin/umount -a -r

etc/init.d/rcS文件内容如下:
#这是一个脚本文件,可以在里面添加想要自动执行的命令,例如设置IP地址,挂接/etc/fstab指定的文件系统
#创建完本脚本后,要执行 chmod +x /etc/init.d/rcS ,使脚本具有可执行属性
ifconfig eth0 192.168.1.59
mount -a
etc/fstab是etc/init.d/rcS 执行 “mount -a”后被调用的, 内容如下:
# device     mount-point    type   options        dump  fsck order
proc           /proc        proc   defaults        0     0
tmpfs          /tmp         tmpfs  defaults        0     0
sysfs          /sys         sysfs  defaults        0     0  #mdev通过sysfs文件系统获得设备信息
tmpfs          /dev         tmpfs  defaults        0     0  #使用内存文件系统,减少对Flash的读写

各个字段的意思如下:
device:要挂接的设备
mount-point:挂载点
type:文件系统类型
option:挂接参数,以逗号隔开

2.构建 dev 目录(可以使用静态创建或者使用mdev方式创建)
静态创建,执行以下指令可以静态创建设备节点
mkdir -p /work/nfs_root/first_fs/dev
cd /work/nfs_root/first_fs/dev
sudo mknod console c 5 1
sudo mknod null c 1 3
sudo mknod ttySAC0 c 204 64

其他设备文件当系统启动后,使用“cat /proc/devices”命令查看内核中注册了哪些设备,然后一一创建相应的设备文件

使用mdev创建设备文件
mdev的用途主要有两个:初始化 /dev 目录、动态更新(动态更新不仅是更新/dev目录,还支持热拔插)

要使用mdev,需要内核支持sysfs文件系统,为了减少对Flash的读写,还要支持tmpfs。先确保内核设置了CONFIG_SYSFS、CONFIG_TMPFS

在 etc/init.d/rcS 文件中加入以下几行:
#创建一个 /dev/pts 目录
mkdir /dev/pts
#devpts用来支持外部网络连接(telnet)的虚拟终端
mount -t devpts devpts /dev/pts

#设置内核,当有设备拔插时调用/bin/mdev程序
echo /bin/mdev > /proc/sys/kernel/hotplug

#在 /dev 目录下生成内核支持的所有设备节点
mdev -s

开发板通过mdev生成的/dev目录,S3C2410、S3C2440的串口名是 s3c2410_serial0、s3c2410_serial1、s3c2410_serial2,不是ttySACx,需要修改/etc/inittab文件。
修改前:ttySAC0::askfirst:-bin/sh
修改后:s3c2410_serial0::askfirst:-bin/sh

mdev是通过init进程来启动的,使用mdev构造/dev目录之前,init进程至少要用到 /dev/console 、/dev/null

3.构建其他目录
其他目录可以是空目录,执行以下指令创建
cd /work/nfs_root/first_fs
mkdir proc mnt tmp sys root

4.制作yaffs或者yaffs2文件系统
yaffs针对每一页为512字节(小页)的flash设备,yaffs2针对每一页为2048(大页)的flash设备
在安装编译器的时候,已经默认安装了制作yaffs2文件系统的工具
执行以下指令制作文件系统镜像:
cd /work/nfs_root/
mkyaffs2image first_fs first_fs.yaffs2

执行完后,会在/work/nfs_root/ 目录下生成first_fs.yaffs2

把first_fs.yaffs2通过u-boot烧写到开发板,然后重新上电启动