构建根文件系统
根文件系统的基本概念
在Linux中,是以树状结构管理所有目录、文件,其他分区挂接在某个目录上,这个目录被称为挂接点或者安装点,然后就可以通过这个目录来访问这个分区上的文件了;
在一个分区上存储文件时需要遵循一定的格式,这种格式称为文件系统类型,比如fat16、fat32、ext2、ext3、jffs2、yaffs等,除了这些实实在在的存储分区的文件系统类型外,Linux还有几种虚拟的文件系统类型,比如proc、sysfs等,它们的文件并不存储在实际的设备上,而是在访问它们 时由内核临时生成,比如proc文件系统下的uptime文件,读取它时可以得到两个时间值(用来表示系统启动后运行的秒数、空闲的秒数),每次读取都是由内核实时生成,每次读取到的结果都不一样;
init进程和用户程序启动过程
内核启动的最后一步就是启动init进程,代码在init/main.c文件中,会调用init_post()函数;
init进程是由内核启动的第一个(也是唯一的一个)用户进程(进程ID为1),它根据配置文件决定启动哪些程序,比如执行某些脚本、启动shell或者运行用户指定的程序等;
init进程的执行程序通常是/sbin/init,也可以自己编写/sbin/init程序,或者通过bootloader传入命令行参数"init=xxxxx"指定某个程序作为init进程运行;
内核启动init进程的过程如下:
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."); }
打开标准输入、标准输出和标准错误设备;
如果ramdisk_execute_command变量制定了要运行的程序,则启动它;
ramdisk_execute_command变量的取值分3种情况:
- 如果命令行参数制定了“rdinit=xxxx”,则ramdisk_execute_command等于这个参数指定的程序;
- 如果/init程序存在,ramdisk_execute_command就等于"/init";
- 否则,ramdisk_execute_command为空;
如果execute_command变量指定了要运行的程序,则启动它;
execute_command变量的参数就是命令行参数中传入的"init=xxxx",即运行这个参数指定的程序,否则为空;
依次尝试执行:/sbin/init、/etc/init、/bin/init、/bin/sh等;
其中run_init_process函数使用它的参数所指定的程序来创建一个用户进程,需要注意,一旦run_init_process函数创建进程成功,它将不会返回;
Busybox init进程的启动过程
在Linux系统中有两种init程序,分别是BSD init和System V init,这两种init程序各有优缺点,现在绝大多数的Linux的发行版本使用的是System V init,但是在嵌入式领域,通常使用Busybox集成init程序;
init程序的处理包括:
- 读取配置文件(/etc/inittab);
- 解析配置文件;
- 根据配置解析执行用户程序;
如果存在/etc/inittab文件,Busybox init程序解析它,然后按照它的指示创建各种子进程,否则使用默认的配置创建子进程;/etc/inittab文件的相关文档和示例代码可以参考Busybox的examples/inittab文件;
配置编译Busybox
操作步骤:
make menuconfig /* 执行后会创建一个.config文件 */ make /* 编译busybox,编译之前确保Makefile种的CROSS_COMPILE已对应修改 */ make install /* 或者执行“make CONFIG_PREFIX=/path/from/root install” * 实际执行"make CONFIG_PREFIX=/work/nfs_root/fs_mini install" 前提是 * 在/work目录下已经创建了/nfs_root/fs_mini目录 */
编译,安装后,会在对应路径文件夹下(即/work/nfs_root/fs_mini种)有:/bin、/linuxrc、/sbin、/usr等;
构建根文件系统
构建一个最小的根文件系统需要包含如下部分内容:
/dev/console、/dev/null;
在/dev目录下: mknod console c 主设备号 次设备号 /* c 意思是字符设备文件 */ mknod null c 主设备号 次设备号
上述操作属于静态创建各种节点(即设备文件);
mdev是udev的简化版本,也是通过读取内核信息来创建设备文件;
mdev的用途主要有两个:
- 初始化/dev目录;
- 动态更新(不仅是更新/dev目录,还支持热插拔,即接入、卸下设备时执行某些动作);
要使用mdev,需要内核支持sysfs文件系统,为了减少对Flash的读写,还要支持tmpfs文件系统,所以先确保内核已经设置了CONFIG_SYSFS和CONFIG_TMPFS配置项;
要在内核启动时,自动运行mdev,需要修改两个文件:
/etc/fstab来自动挂载文件系统;
#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 tmpfs /dev tmpfs defaults 0 0
/etc/init.d/rcS加入自动运行的命令;
mount -a mkdir /dev/pts mount -t devpts devpts /dev/pts echo /sbin/mdev > /proc/sys/kernel/hotplug mdev -s
另外需要注意:mdev是通过init进程来启动的,在使用mdev构造/dev目录之前,init进程至少要用到设备文件/dev/console、/dev/null,所以要手动创建这两个设备文件:
mkdir -p /nfs_root/mini_rootfs/dev /* 构建的文件系统目录下创建/dev目录 */ cd /nfs_root/mini_rootfs/dev sudo mknod console c 5 1 sudo mknod null c 1 3
init -> busybox(busybox编译后就有);
/etc/inittab;
创建/etc目录,并进入此目录创建配置文件inittab;
在内核中当前有哪些应用程序在运行,这些信息在内核提供的一个虚拟文件系统proc,可执行命令ps查看;
当内核启动后挂载了根文件系统后,进入命令行模式,在根目录下创建一个目录/proc,然后执行如下命令:
mount -t proc none /proc /* 将虚拟文件系统proc挂接在/proc目录下 */ ps /* 之后执行ps命令后,回去/proc目录下查看有哪些程序 */
如果不想手动挂载,可以在配置文件中修改,改为自动挂载;
比如在/etc/inittab配置文件中增加一条脚本:
::sysinit:/etc/init.d/rcS
在/etc/init.d目录下增加rcS文件,内容为:
mount -t proc none /proc chmod +x /etc/init.d/rcS /* 增加可运行的属性 */
还可以按照如下做法:
在/etc/init.d目录下增加rcS文件,文件的内容为:
mount -a
然后在/etc目录下新增文件fstab,意思就是当执行了"mount -a"命令后,会读出/etc/fstab文件,根据这个配置文件中的内容来进行挂载文件系统;
其中,fstab中的内容格式如下:
#device mount-point type options dump fsck order proc /proc proc default 0 0
格式说明:
- device 要挂接的设备,对于proc文件系统这个字段没有意义,可以是任意值;
- mount-point 挂接点;
- type 文件系统类型,比如proc、jffs2、yaffs等,或者auto自动检测文件类型;
- options 挂接参数,以逗号隔开;
配置文件指定的程序;
C库;
在制作交叉编译工具链时,已经生成了glibc库(位置在/gcc-3.4.5-glibc-2.3.6/arm-linux/lib,这个目录下并非都属于glibc库,比如ctrl.o、libstdc++.a等文件时GCC工具本身生成的),可以直接使用它来构建根文件系统;
在开发板上只需要加载器和动态库,假设要构建的根文件系统目录为/nfs_root/mini_rootfs,操作如下:
mkdir -p /nfs_root/mini_rootfs/lib cd /gcc-3.4.5-glibc-2.3.6/arm-linux/lib cp *.so* /nfs_root/mini_rootfs/lib -d /* 加上-d作用就是原来是链接文件,拷贝过来仍然是链接文件, 否则会将链接指向的文件拷贝过来,导致文件过大 */
构架其他目录
其他目录可以是空目录,比如proc、mnt、tmp、sys等;
根文件系统的使用
对于根文件系统,开发板可以将它作为网络根文件系统直接启动或者将它制作为一个文件(映像文件)烧入开发板;
网络文件系统可以使用NFS,需要注意两点:
- 确保服务器允许那个目录可被挂载;
- 单板启动后去挂载;
具体操作如下:
修改NFS服务的配置文件
vi /etc/exports /* 将需要可被挂载的文件系统目录增加到配置文件中 */ sudo /etc/init.d/nfs-kernel-server restart /* 重启NFS服务 */ sudo mount -t nfs 服务器地址:/可被挂载的目录 /mnt /* 可先在电脑上检验下是否可以挂载 */
手动挂载(从Flash上启动根文件系统,再用命令挂接NFS)
ifconfig eth0 up ifconfig eht0 192.168.3.11 mkdir /mnt mount -t nfs -o nolock 服务器地址:根文件系统所在的目录 /mnt
可以直接从NFS启动
需要设置启动参数,设置格式具体可参考linux内核/Documentation目录下的nfsroot.txt文档;
具体设置格式如下:
/* 格式:noinitrd root=/dev/nfs nfsroot=[<server-ip>:]<root-dir>[,<nfs-options>] ip=<client-ip>:<server-ip>:<gw-ip>:<netmask>:<hostname>:<device>:<autoconf> */ set bootargs noinitrd root=/dev/nfs nfsroot=192.168.3.5:/work/nfs_root/mini_rootfs ip=192.168.3.10:192.168.3.5:192.168.3.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC0