标签:
本文将介绍如何构建一个最简单的根文件系统,并且初步分析内核如何执行第一个内核程序。
在挂接根文件系统之前,需要制作根文件系统。根文件系统里面需要一些基本的命令,目录和设备文件,下面来介绍如何使用busybox来制作根文件系统。
我们都知道,init进程是系统启动后执行的第一个应用程序,根据一般的Linux应用程序配置结构,一个可执行文件通常搭配一个对于的配置文件,例如samba功能对于/etc/samba/smb.conf配置文件,nfs功能对于/dev/exports配置文件,那么init初始化功能肯定也是对于一个配置文件,这个配置文件叫做/etc/inittab.配置文件根据不同的级别,指定在不同的情况下,执行不同的应用程序。
通过Busybox自带的配置文件格式介绍,我们了解到inittab的文件格式:
Format for each entry: <id>:<runlevels>:<action>:<process>
id项 : 每一个id会变成 /dev/id ,用作程序的输入输出文件所使用的终端设备,例如id=s3c2410_serial0,则对于终端为s3c2410_serial0
不需要终端输出的可以设置为空
runlevels项:用于启动级别的控制,在arm-linux中可省略不写。
action项: 执行应用程序执行时机,只能设置为几个有效的时机,busybox支持如下几个时间点。
Valid actions include: sysinit, respawn, askfirst, wait, once, restart, ctrlaltdel, and shutdown.
askfirst :从名字上可以知道,该触发点是需要先向用户发出提问,看是否需要执行。
ctraltdel:当用户按下ctrl+alt+del时,内核会向init进程发起SIGINT信号,当init进程接收到此信号,会执行指定的ctrlaltdel类型的应用程序,默认执行reboot命令。
process:指定应用程序全路径
如果用户没有在根文件系统中设置/etc/inittab文件,busybox会提供一个默认的配置选项,其等效于如下配置。
busybox会按照sysinit--->wait---->once---->[respawn--->askfirst----> respawn----> askfirst—>… ]
除了busybox提供的基本linux文件框架之外,根文件系统还需要程序运行库。
有了上面的知识铺垫,下面来构建最小根文件系统,那么最小根文件系统里面应该有哪些呢?
1. /dev/console . /dev/null
2. /sbin/init ----> busybox
3. /etc/inittab 以及其中定制的应用程序
4. 运行时的C库
制作根文件系统的操作步骤:
1. 配置编译busybox
我这里选用的是busybox-1.7.0,编译步骤和编译kernel差不多,先make menuconfig,再make ,最后make install。这里面,需要注意如下几点:
如果在menuconfig中无法配置交叉编译工具,那么需要在 Makefile中修改,指定特定的工具。
添加命令行代码补全功能: Busybox Settings--->Busybox Library Tuning---> Tab completion
是否选用静态连接: Busybox Settings ----> Build Options ----> Build Busybox as a static binary 一般都设置成动态库
是否支持热插拔:Linux System Utilities -----> mdev
然后make,执行 make CONFIG_PREFIX=../test_busybox_compile install ,千万不要直接make install,这样会覆盖PC机上面的原有的代码。安装完成后结果如下:
busybox编译的结果有bin目录,sbin目录和usr目录,根目录下面还有一个linuxrc链接文件,指向bin/busybox。查看一下所有生成的文件,他们都是链接文件,均指向/bin/busybox。这样的目录构造对于启动一个正常的linux来说还是不够的,下面继续制作。
2. 添加其他必须的文件
添加基本的设备节点,仿照当前系统的console设备节点和null设备节点,在busybox的目录下也建立对于的节点。
3. 构造自己的启动配置文件 /etc/inittab
为了简单起见,这里面只设置启动时开启shell输入。首先创建etc目录,然后执行如下操作。
4. 安装C库
将交叉编译工具链中的所有动态链接库都拷贝到busybox的安装目录中,先创建lib目录,然后执行如下操作:
本机交叉编译工具安装在/opt目录下,在拷贝C库时要注意,-d选项,这个选项使得拷贝过程中,链接文件保持文件属性不变化,否则,链接文件的内容将是所指向的全部内容,而不在是链接文件本身。
5. 制作镜像文件
根文件系统的格式一般有两种,一种是yaffs格式,这种有两类,一类是针对小页512字节的nand flash文件系统,称为yaffs1,另一类是针对大页2K的nand flash文件系统。这里根据硬件的情况,采用的是yaffs2格式的文件系统。
生成最小根文件系统镜像,然后烧写到板子上去,烧写命令如下:
tftp 0x30000000 test_busybox_compile_fs.yaffs2
nand erase root ; nand write.yaffs 0x30000000 0x00260000 $(filesize); reset
启动后显示如下:
经过上述几个步骤的操作,一个最简单的根文件系统已经建立好了,此后,可以以此为基础,逐渐添加功能。
附加功能
上面制作的最小根文件系统有很多不完善的地方,在开机之后,需要自动执行一些挂载命令。
1。 创建proc目录
在/etc/inittab里面添加这句话, ::sysinit:/etc/init.d/rcS,使得开机后自动执行rcS脚本文件。
在rcS脚本文件中,添加 mount –t proc none /proc 就可以开启自动挂载proc虚拟文件系统
2. 如果需要挂载多个文件系统,推荐使用mount -a选项,这个命令会读取etc/fstab配置文件内的挂载命令。
此时,修改rcS中的文件,改成mount -a,在etc/fstab目录下创建上述文件内容。
3. 使能热插拔功能 mdev
根据Busybox中的mdev.txt中的说明,我们知道,使能mdev功能需要做如下操作:下述操作均在根文件系统跟目录
mkdir sys & mount –t sysfs sysfs /sys # mdev通过sysfs文件系统获得设备信息
mount –t tmpfs mdev /dev # 使用内存文件系统,减少对flash的读写
mkdir /dev/pts # devpts支持外部网络连接(telnet)的虚拟终端
mount –t devpts devpts /dev/pts
echo /bin/mdev > /proc/sys/kernel/hotplug # 设置内核热拔插事件回调程序
mdev –s #在/dev目录下,生成内核目前支持的所有设备节点
上述挂载相关的命令可以写在fstab中,其他执行命令可以写在rcS启动脚本中,最后结果如下:
fstab:
rcS:
按照上述操作进行制作烧写后,一个基本架构比较完整的根文件系统就全部建立好了。
知识介绍:mdev工作原理
执行mdev -s时,mdev扫描/sys/class 和 /sys/block中两个目录下的dev属性文件,从该dev属性文件中,获取设备编号,并以包含该dev属性文件的目录名称作为设备名,device_name。而/sys/class 和 device_name 之间的那部分目录成为subsystem,
[subsystem] [device_name] dev
例如:cat /sys/class/tty/tty0/dev 里面的内容为4:0 【major:minor】,那么subsystem为tty,device_name为tty0
mdev会根据此信息,在dev目录下创建相应的设备文件
当mdev因uevent事件(以前叫hotplug事件)被调用时,mdev通过由uevent事件传递给它的环境变量获得到两个变量,
一个是 引发该uevent事件的action
另一个是 该设备所在的device path。
mdev判断action,若是add,则表示有新设备(虚拟设备或者是物理设备)加入系统,mdev会通过设备路径下的dev属性文件获得设备编号,然后根据路径相关信息在/dev目录下建立设备节点。若动作是remove,即表示设备已从系统中移除,则删除/dev/目录下的设备节点。
由上可知,要想发挥mdev自动创建设备节点的功能,必须有三个条件。
1. 在/sys/class/的某一个subsystem下
2. 在subsystem下创建一个以设备名device_name作为名称的目录
3. 在此目录里面,包含一个dev属性文件,内容以”major:minor\n”的形式输出设备编号。
一个类class可以看成是一个容器,这个大容器里面包含并管理很多class_device,每一个class_device都对于着一个具体的设备。
优化操作:
按照上面制作出来的根文件系统,还有很多可以优化的地方,比如说,可以按照实际需要裁剪文件系统中的C库,可以使用arm-linux-strip优化lib下面的动态链接库和bin/busybox
1: static int noinline init_post(void)
2: {
3: free_initmem();
4: unlock_kernel();
5: mark_rodata_ro();
6: system_state = SYSTEM_RUNNING;
7: numa_default_policy();
8:
9: /*hao: open console device */
10: if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
11: printk(KERN_WARNING "Warning: unable to open an initial console.\n");
12:
13: (void) sys_dup(0); /*hao: copy an opened file num,make two file num pointed to the same file. */
14: (void) sys_dup(0); /*make 1(standard output) and 2(error output) point to 0(standard input)*/
15:
16: if (ramdisk_execute_command) {
17: run_init_process(ramdisk_execute_command);
18: printk(KERN_WARNING "Failed to execute %s\n",
19: ramdisk_execute_command);
20: }
21:
22: /*
23: * We try each of these until one succeeds.
24: *
25: * The Bourne shell can be used instead of init if we are
26: * trying to recover a really broken machine.
27: */
28: /*
29: hao: execute_command equal the bootargs named init. e.g : init=/linuxrc
30: then execute_command= = "/linuxrc
31: "*/
32: if (execute_command) {
33: run_init_process(execute_command);
34: printk(KERN_WARNING "Failed to execute %s. Attempting "
35: "defaults...\n", execute_command);
36: }
37:
38: /*hao: if we don‘t define init cmd, then we call /sbin/init to start system. */
39: run_init_process("/sbin/init");
40: run_init_process("/etc/init");
41: run_init_process("/bin/init");
42: run_init_process("/bin/sh");
43:
44: panic("No init found. Try passing init= option to kernel.");
45: }
在内核启动的后面,执行init_post,里面会打开一个终端设备(这里指的是/dev/console),然后将标准输出文件和错误输出文件指向标准输入文件所指向的console设备中去。
ramdisk_execute_command为uboot传给kernel的rdinit参数,这里为空,所以ramdisk_execute_command也为空。
execute_command为uboot传给kernel的init参数,这里为/linuxrc,所以execute_command=linuxrc。
如果uboot没有定义rdinit和init参数,那么kernel会搜索并依此执行/sbin/init,/etc/init…直到某一个程序执行成功,如果都没有找到,则会显示kernel panic,打印对应的提示信息。
参考链接:
标签:
原文地址:http://www.cnblogs.com/cherishui/p/4237694.html