制作根文件系统

文件系统简介

    理论上说一个嵌入式设备如果内核能运行起来,且不需要用户进程的话(估计这种情况很少),是不需要文件系统的。文件系统简单的说就是一种目录结构,由于linux操作系统的设备在系统中是以文件的形式存在,将这些文件分类管理以及提供和内核交互的接口,就形成了一定的目录结构也就是文件系统。文件系统是为用户反映系统的一种形式,为用户提供一个检测控制系统的接口。

    而根文件系统,就是一种特殊的文件系统。那么根文件系统和普通的文件系统有什么区别呢?借用书上的话说就是,根文件系统就是内核启动时挂载的第一个(第一个的意思就是不止一个吧,你看,区别出来了啊)文件系统。由于根文件系统是启动时挂载的第一个文件系统,那么根文件系统就要包括Linux启动时所必须的目录和关键性的文件,例如Linux 启动时都需要有用户进程 init 对应的文件,在Linux挂载分区时一定要会找 /etc/fstab这个挂载文件等,根文件系统中还包括了许多的应用程序,如 /bin目录下的命令等。任何Linux启动时所必须的文件的文件系统都可以称为根文件系统。

FHS(Filesystem Hierarchy Standard)标准介绍

所有的linux发行版在对根文件系统布局上都遵循FHS标准的建议规定,该标准规定了根目录下各个子目录的名称及其存放的内容:

目录存放的内容
/bin必备的用户命令,例如ls、cp等
/sbin必备的系统管理员命令,例如ifconfig、reboot等
/dev设备文件,例如mtdblock0、tty1等
/etc系统配置文件,包括启动文件,例如inittab等
/lib必要的链接库,例如C链接库、内核模块
/home普通用户主目录
/rootroot用户主目录
/usr/bin非必备的用户程序,例如find、du等
/usr/sbin非必备的管理员程序,例如chroot、inetd等
/usr/lib库文件
/var守护程序和工具程序所存放的可变,例如日志文件
/proc用来提供内核与进程信息的虚拟文件系统,由内核自动生成目录下的内容
/sys用来提供内核与设备信息的虚拟文件系统,由内核自动生成目录下的内容
/mnt文件系统挂接点,用于临时安装文件系统
/tmp临时性的文件,重启后将自动清除

制作根文件系统就是要建立以上的目录,并在其中建立完整目录内容。其过程大体包括:

  • 编译/安装busybox,生成/bin、/sbin、/usr/bin、/usr/sbin目录

  • 利用交叉编译工具链,构建/lib目录

  • 手工构建/etc目录

  • 手工构建最简化的/dev目录

  • 创建其它空目录

  • 配置系统自动生成/proc目录

  • 利用udev构建完整的/dev目录

  • 制作根文件系统的映像文件

下面就来详细介绍这个过程:

1、编译/安装busybox,生成/bin、/sbin、/usr/bin、/usr/sbin目录

/bin, /sbin,/usr/bin,/usr/sbin这些目录下存储的主要是常用命令的二进制文件。我们可以使用busybox自动生成这些目录。

(1)从http://www.busybox.net/ 下载busybox

(2)解包

tar xjvf busybox-1.7.0.tar.bz2

(3)修改Makefile文件

   ARCH    ?= arm
   CROSS_COMPILE   ?= arm-linux-

(4)配置busybox

make menuconfig

busybox配置主要分两部分。

第一部分是Busybox Settings,主要编译和安装busybox的一些选项。这里主要需要配置:

[1]Build Options -- Build BusyBox as a static binary (no shared libs),表示编译busybox时,是否静态链接C库。我们选择动态链接C库(即Build BusyBox as a static binary (no shared libs)不选,选择下面的share library)。

[2]Installation Options -- Applets links (as soft-links) -- (X) as soft-links,表示安装busybox时,将各个命令安装为指向busybox的软链接还是硬链接。我们选择软链接。

[3]Installation Options -- (/work/nfs_root/fs_mini3) BusyBox installation prefix,表示busybox的安装位置。我们选择/work/nfs_root/fs_mini3

[4]Busybox Library Tuning。保留Command line editing以支持命令行编辑;保留History size以支持记忆历史命令;选中Tab completion和Username completion以支持命令自动补全

第二部分是Applets,他将busybox的支持的几百个命令分门别类。我们只要在各个门类下选择想要的命令即可。这里我们基本保持默认设置。

[1]选中Networking Utilities -- httpd下的Enable -u <user> option,以启用http服务器的功能allows the server to run as a specific user

(5)编译busybox

make

(6)安装busybox

make install

 

安装完成后,可以看到在/work/nfs_root/fs_mini3目录下生成了binsbinusr/binusr/sbin目录,其下包含了我们常用的命令,这些命令都是指向bin/busybox的软链接,而且busybox本身的大小不到800K:

dennis@dennis-desktop:/work/nfs_root/fs_mini3$ ls
bin  linuxrc  sbin  usr
dennis@dennis-desktop:/work/nfs_root/fs_mini3$  ls -l bin
total 740
lrwxrwxrwx 1 dennis dennis      7 2010-04-03 23:57 addgroup -> busybox
lrwxrwxrwx 1 dennis dennis      7 2010-04-03 23:57 adduser -> busybox
lrwxrwxrwx 1 dennis dennis      7 2010-04-03 23:57 ash -> busybox
-rwxr-xr-x 1 dennis dennis 749632 2010-04-03 23:57 busybox
lrwxrwxrwx 1 dennis dennis      7 2010-04-03 23:57 cat –> busybox

2、利用交叉编译工具链,构建/lib目录

因为应用程序本身需要使用C库的库函数,因此还必需制作for ARM的C库,并将其放置于/lib目录。由于交叉编译工具链由交叉编译器、for ARM的C库和二进制工具3个部分组成,我们只需要将交叉编译工具链的for ARM的C库拷贝过来就可以了。

但是整个C库目录下的文件总大小有26M。需要C库目录下所有的文件吗?让我们来分析一下glibc库目录下内容的组成。该目录下的子目录和文件共分8类:

  • 目标文件,如crtn.o,用于gcc链接可执行文件

  • libtool库文件(.la),在链接库文件时这些文件会被用到,比如他们列出了当前库文件所依赖的其它库文件,程序运行时无需这些文件

  • gconv目录,里面是各种链接脚本,在编译应用程序时,他们用于指定程序的运行地址,各段的位置等

  • 静态库文件(.a),例如libm.a,libc.a

  • 动态库文件 (.so、.so.[0-9]*)

  • 动态链接库加载器ld-2.3.6.so、ld-linux.so.2

  • 其它目录及文件

很显然,第1、2、3、4、7类文件和目录是不需要拷贝的。

由于动态链接的应用程序本身并不含有它所调用的C库函数的代码,因此执行时需要动态链接库加载器来为它加载相应的C库文件,所以第6类文件是需要拷贝的。

除此之外,第5类文件当然要拷贝。但第5类文件的大小也相当大。

dennis@dennis-desktop:/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib$ du -c --si *.so*
7.2M    total

需要全部拷贝吗?非也,非也!其实,需要哪些库完全取决于要运行的应用程序使用了哪些库函数。如果我们只制作最简单的系统,那么我们只需要运行busybox这一个应用程序即可。通过执行

dennis@dennis-desktop:/work/nfs_root/fs_mini3$ arm-linux-readelf -a bin/busybox | grep 'Shared'
0x00000001 (NEEDED)                     Shared library: [libcrypt.so.1]
0x00000001 (NEEDED)                     Shared library: [libm.so.6]
0x00000001 (NEEDED)                     Shared library: [libc.so.6]

我使用上面的命令没有显示任何东西,于是我grep 'NEEDED',而不是'Shared',即arm-linux-readelf -a bin/busybox | grep 'NEEDED'

可知:busybox只用到了3个库:通用C库(libc)、数学库(libm)、加密库(libcrypt),因此我们只需要拷贝这3个库的库文件即可。但是每个库都有4个文件,4个文件都要拷贝吗?当然不是。

dennis@dennis-desktop:/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib$ ls -l libcrypt[.-]*
-rwxr-xr-x 1 dennis dennis 30700 2008-01-22 05:32 libcrypt-2.3.6.so
-rw-r--r-- 1 dennis dennis 23118 2008-01-22 05:32 libcrypt.a
lrwxrwxrwx 1 dennis dennis    13 2008-12-22 15:38 libcrypt.so -> libcrypt.so.1
lrwxrwxrwx 1 dennis dennis    17 2008-12-22 15:38 libcrypt.so.1 -> libcrypt-2.3.6.so
dennis@dennis-desktop:/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib$ ls -l libm[.-]*
-rwxr-xr-x 1 dennis dennis  779096 2008-01-22 05:31 libm-2.3.6.so
-rw-r--r-- 1 dennis dennis 1134282 2008-01-22 05:32 libm.a
lrwxrwxrwx 1 dennis dennis       9 2008-12-22 15:38 libm.so -> libm.so.6
lrwxrwxrwx 1 dennis dennis      13 2008-12-22 15:38 libm.so.6 -> libm-2.3.6.so
dennis@dennis-desktop:/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib$ ls -l libc[.-]*
-rwxr-xr-x 1 dennis dennis 1435660 2008-01-22 05:48 libc-2.3.6.so
-rw-r--r-- 1 dennis dennis 2768280 2008-01-22 05:31 libc.a
-rw-r--r-- 1 dennis dennis     195 2008-01-22 05:34 libc.so
lrwxrwxrwx 1 dennis dennis      13 2008-12-22 15:38 libc.so.6 -> libc-2.3.6.so

4个文件中的.a文件是静态库文件,是不需要拷贝的。另外3个文件是:

  • 实际的共享链接库:libLIBRARY_NAME-GLIBC_VERSION.so。当然需要拷贝。

  • 主修订版本的符号链接,指向实际的共享链接库:libLIBRARY_NAME.so.MAJOR_REVISION_VERSION,程序一旦链接了特定的链接库,将会参用该符号链接。程序启动时,加载器在加载程序前,会检索该文件。所以需要拷贝。

  • 与版本无关的符号链接,指向主修订版本的符号连接(libc.so是唯一的例外,他是一个链接命令行:libLIBRARY_NAME.so,是为编译程序时提供一个通用条目)。这些文件在程序被编译时会被用到,但在程序运行时不会被用到,所以不必拷贝它。

关于共享库的2个符号链接的作用的特别说明:

当 我们使用gcc    hello.c    -o    hello    -lm编译程序时,gcc会根据-lm的指示,加头(lib)添尾(.so)得到libm.so,从而沿着与版本无关的符号链接(libm.so -> libm.so.6)找到libm.so.6并记录在案(hello的ELF头中),表示hello需要使用libm.so.6这个库文件所代表的数学库 中的库函数。而当hello被执行的时候,动态链接库加载器会从hello的ELF头中找到libm.so.6这个记录,然后沿着主修订版本的符号链接 (libm.so.6 -> libm-2.3.6.so)找到实际的共享链接库libm-2.3.6.so,从而将其与hello作动态链接。可见,与版本无关的符号链接是供编译器 使用的,主修订版本的符号链接是供动态链接库加载器使用的,而实际的共享链接库则是供应用程序使用的。

通过以上分析,我们只需要拷贝3个库(每个库各1个主修订版本的符号链接和1个实际的共享链接库)以及动态链接库加载器(1个符号链接和1个实体文件)。步骤如下:

dennis@dennis-desktop:/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib$ mkdir /work/nfs_root/fs_mini3/lib
dennis@dennis-desktop:/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib$ cp  libcrypt-* /work/nfs_root/fs_mini3/lib
dennis@dennis-desktop:/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib$ cp -l libcrypt.so.* /work/nfs_root/fs_mini3/lib
dennis@dennis-desktop:/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib$ cp  libm-* /work/nfs_root/fs_mini3/lib
dennis@dennis-desktop:/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib$ cp -l libm.so.* /work/nfs_root/fs_mini3/lib
dennis@dennis-desktop:/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib$ cp  libc-* /work/nfs_root/fs_mini3/lib
dennis@dennis-desktop:/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib$ cp -l libc.so.* /work/nfs_root/fs_mini3/lib
dennis@dennis-desktop:/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib$ cp -l ld-* /work/nfs_root/fs_mini3/lib  

3、手工构建/etc目录

/etc 目录存放的是系统程序的主配置文件,因此需要哪些配置文件取决于要运行哪些系统程序。即使最小的系统也一定会运行1号用户进程init,所以我们至少要手 工编写init的主配置文件inittab。busybox的inittab文件的语法、语义与传统的SYSV的inittab有所不同。

inittab 文件中每个条目用来定义一个需要init启动的子进程,并确定它的启动方式,格式 为<id>:<runlevel>:<action>:<process>。例 如:ttySAC0::askfirst:-/bin/sh

  • <id>表示子进程要使用的控制台,若省略则使用与init进程一样的控制台

  • <runlevel>表示运行级别,busybox init程序这个字段没有意义

  • <action>表示init进程如何控制这个子进程

    • sysinit:系统启动后最先执行,只执行一次,init进程等待它结束后才继续执行其它动作

    • wait:系统执行完sysinit条目后执行,只执行一次,init进程等待它结束后才继续执行其它动作

    • once:系统执行完wait条目后执行,只执行一次,init进程不等待它结束

    • respawn:启动完once进程后,init进程监测发现子进程退出时,重新启动它

    • askfirst:启动完respawn进程后,与respawn类似,不过init进程先输出” Please press Enter to activate this console“,等用户输入回车后才启动子进程

    • shutdown:当系统关机时

    • restart:Busybox中配置了CONFIG_FEATURE_USE_INITAB,并且init进程接收到SIGUP信号时执行,先重新读取、解析/etc/inittab文件,再执行restart程序

    • ctrlaltdel:按下ctrl+alt+del键时执行,不过在串口控制台中无法输入它

  • <process>表示进程对应的二进制文件。如果前面有-号,表示该程序是“可以与用户进行交互的”

我们制作最简单的/etc/inittab文件,其内容如下:

::sysinit:/etc/init.d/rcS
::askfirst:-/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a –r

制作最简单的脚本程序文件/etc/init.d/rcS,其内容如下:

#!/bin/sh
ifconfig eth0 192.168.2.17

修改shell脚本文件/etc/init.d/rcS的权限,以使其可被执行:

chmod a+x /etc/init.d/rcS

 

4、手工构建最简化的/dev目录

在linux机器上,执行ls    /dev可看到几百个设备文件,我需要手工创建它们吗?先看看开发板上的linux的反应再说。

启动Linux操作系统,显示:

VFS: Mounted root (nfs filesystem).
Freeing init memory: 112K
Warning: unable to open an initial console.

这说明,内核已经成功挂载根文件系统,但却未能成功启动第1个用户进程init。通过错误消息“unable to open an initial console”搜索内核源代码,找到init/main.c文件。

748 static int noinline init_post(void)
749 {
750         free_initmem();
751         unlock_kernel();
752         mark_rodata_ro();
753         system_state = SYSTEM_RUNNING;
754         numa_default_policy();
755 
756         if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
757                 printk(KERN_WARNING "Warning: unable to open an initial console.\n");
758 
759         (void) sys_dup(0);
760         (void) sys_dup(0);
761 
762         if (ramdisk_execute_command) {
763                 run_init_process(ramdisk_execute_command);
764                 printk(KERN_WARNING "Failed to execute %s\n",
765                                 ramdisk_execute_command);
766         }
767 
768         /*
769          * We try each of these until one succeeds.
770          *
771          * The Bourne shell can be used instead of init if we are
772          * trying to recover a really broken machine.
773          */
774         if (execute_command) {
775                 run_init_process(execute_command);
776                 printk(KERN_WARNING "Failed to execute %s.  Attempting "
777                                         "defaults...\n", execute_command);
778         }
779         run_init_process("/sbin/init");
780         run_init_process("/etc/init");
781         run_init_process("/bin/init");
782         run_init_process("/bin/sh");
783 
784         panic("No init found.  Try passing init= option to kernel.");
785 }

显然,内核错误是由175行不能打开/dev/console所致。通过查看已经安装好的linux机器的/dev/console设备文件,可知其是字符设备文件,主设备号为5,次设备号为1:

dennis@dennis-desktop:/work/nfs_root/fs_mini3/etc$ ls -l /dev/console
crw------- 1 root root 5, 1 2010-04-08 08:40 /dev/console

因此,我们使用下面的命令创建它:

dennis@dennis-desktop:/work/nfs_root/fs_mini3/dev$ sudo mknod console c 5 1

还需要创建其它设备文件吗?只有天知道!再看看linux的反应。

VFS: Mounted root (nfs filesystem).
Freeing init memory: 112K
init: can't open '/dev/null': No such file or directory

这次我们有经验了,如法炮制,创建/dev/null设备文件:

dennis@dennis-desktop:/work/nfs_root/fs_mini3/dev$ sudo mknod null c 1 3

再次重启开发板上的linux,显示

VFS: Mounted root (nfs filesystem).
Freeing init memory: 112K
init started: BusyBox v1.7.0 (2010-04-03 23:53:55 CST)
starting pid 229, tty '': '/etc/init.d/rcS'

Please press Enter to activate this console.
starting pid 231, tty '': '/bin/sh'
#

5、创建其它空目录

dennis@dennis-desktop:/work/nfs_root/fs_mini3$ mkdir home root proc sys tmp mnt var

再次重启动开发板上的linux。咦,似乎有些问题。

VFS: Mounted root (nfs filesystem).
Freeing init memory: 112K
init started: BusyBox v1.7.0 (2010-04-03 23:53:55 CST)
starting pid 229, tty '': '/etc/init.d/rcS'

Please press Enter to activate this console.
starting pid 231, tty '': '/bin/sh'
# ps
PID  Uid        VSZ Stat Command
#

ps竟然看不到任何进程的存在!让我想想。对了,ps的机制是通过查看/proc中的内容来获得进程信息的。那么,目前/proc里有哪些内容呢?

# ls /proc
#

竟然空空如野!这可如何是好?

6、配置系统自动生成/proc目录

其实/proc是用来提供内核与进程信息的虚拟文件系统,由内核自动生成目录下的内容。不过需要我们设置一下,将/etc/init.d/rcS修改为:

#!/bin/sh
ifconfig eth0 192.168.2.17
mount -t proc none /proc

对 于mount -t proc none /proc 的解释:通常情况下mount命令应该写为mount –t ext2 /dev/hdb1 /proc。但由于现在挂载的/proc是虚拟文件系统,它不与任何物理硬盘分区相对应,因此在表示物理硬盘分区的位置用占位符none来表示。

重启开发板上的linux,显示成功了:

Please press Enter to activate this console.
starting pid 232, tty '': '/bin/sh'
# ps
PID  Uid        VSZ Stat Command
1 0          3088 S   init
2 0               SW< [kthreadd]
3 0               SWN [ksoftirqd/0]
4 0               SW< [events/0]
5 0               SW< [khelper]
41 0               SW< [kblockd/0]
42 0               SW< [ksuspend_usbd]
45 0               SW< [khubd]
47 0               SW< [kseriod]
59 0               SW  [pdflush]
60 0               SW  [pdflush]
61 0               SW< [kswapd0]
62 0               SW< [aio/0]
177 0               SW< [mtdblockd]
226 0               SW< [rpciod/0]
232 0          3092 S   -sh
233 0          3092 R   ps
#

7、利用udev构建完整的/dev目录

高兴地插入U盘,内核显示识别到了U盘:

# usb 1-1: new full speed USB device using s3c2410-ohci and address 2
usb 1-1: not running at top speed; connect to a high speed hub
usb 1-1: configuration #1 chosen from 1 choice
scsi0 : SCSI emulation for USB Mass Storage devices
scsi 0:0:0:0: Direct-Access     Teclast  CoolFlash        0.00 PQ: 0 ANSI: 2
sd 0:0:0:0: [sda] 12560384 512-byte hardware sectors (6431 MB)
sd 0:0:0:0: [sda] Write Protect is off
sd 0:0:0:0: [sda] Assuming drive cache: write through
sd 0:0:0:0: [sda] 12560384 512-byte hardware sectors (6431 MB)
sd 0:0:0:0: [sda] Write Protect is off
sd 0:0:0:0: [sda] Assuming drive cache: write through
sda: sda1
sd 0:0:0:0: [sda] Attached SCSI removable disk

但当要使用的时候,却找不到设备文件:

# mount /dev/sda1 /mnt
mount: mounting /dev/sda1 on /mnt failed: No such file or directory
# ls /dev/sda1
ls: /dev/sda1: No such file or directory

/dev目录下只有可怜巴巴的2个设备文件。

# ls /dev
console  null

在linux机器上,执行ls    /dev可看到几百个设备文件,难道要我揉着酸疼的眼睛查看这几百个设备文件的主、次设备号,然后再手工使用mknod命令来生成这几百个设备文件吗?那你不如杀了我算了!其实构建/dev目录有3种方法:

  • 创建静态设备文件:需要使用mknod命令事先创建很多设备文件,麻烦大了

  • 使用devfs:使用上存在一些问题,在2.6.13后已被废弃

  • 使用udev(user dev),我们采用该方法

udev 的原理是:操作系统启动的时候将识别到的所有设备的信息自动导出到/sys目录,然后用户态的应用程序udev根据/sys中的设备信息,自动在/dev 目录下创建所有正确的设备文件。因此我们要做的就是:配置自动生成/sys目录下的内容并调用mdev(mdev是busybox中对udev的简化实现)。

# ls /sys
# mount -t sysfs none /sys
# ls /sys
block     class     firmware  kernel    power
bus       devices   fs        module
# ls /dev
console  null
# mdev -s
# ls -l dev | wc -l
140

# ls /dev/sda*
/dev/sda   /dev/sda1

可是,当我们将U盘拔出后,发现/dev/sda1并不自动消失;手工删除/dev/sda1后,再重新插入U盘,/dev/sda1也不会自动生成。我们需进行如下操作,以使系统能够实现即插即用:

# echo /sbin/mdev > /proc/sys/kernel/hotplug

将上述工作放到rcS中:

#!/bin/sh
ifconfig eth0 192.168.2.17
mount -t proc none /proc
mount -t sysfs none /sys
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s

8、使用tmpfs挂载/dev、/tmp、/var目录

似乎我们的根文件系统已经相当完善了。但仔细想一想Nand flash的擦写寿命是有限的这个事实,我们就应该明白,我们应该将/dev、/tmp、/var三个目录挂载为tmpfs文件系统。修改rcS如下:

#!/bin/sh
ifconfig eth0 192.168.2.17
mount -t proc none /proc
mount -t sysfs none /sys
mount -t tmpfs none /dev
mount -t tmpfs none /var
mount -t tmpfs none /tmp
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s

 

9、制作根文件系统的映像文件

1. 生成32M大小的镜像 

 dd if=/dev/zero of=a9rootfs.ext3 bs=1M count=32

2. 格式化成ext3文件系统 

mkfs.ext3 a9rootfs.ext3

3.  将文件拷贝到镜像中 

sudo mkdir tmpfs
sudo mount -t ext3 a9rootfs.ext3 tmpfs/ -o loop
 cp -r rootfs/*  tmpfs/
sudo umount tmpfs

转载于:https://my.oschina.net/wuqingyi/blog/832101

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值