构建Linux根文件系统

时间:2022-07-26
本文章向大家介绍构建Linux根文件系统,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

本章目标

l 了解Linux的文件系统层次标准(FHS)

l 了解根文件系统下各目录的作用

l 掌握构建根文件系统的方法:移植Busybox、构造各个目录、文件等

l 掌握制作yaffs、jffs2文件系统映象文件的方法

推广:

想了解更多嵌入式知识请移步到

100ask.taobao.com

17.1  Linux文件系统概述

17.1.1  Linux文件系统的特点

类似于Windows下的C、D、E等各个盘,Linux系统也可以将磁盘、Flash等存储设备划分为若干个分区,在不同分区存放不同类别的文件。与Windows的C盘类似,Linux一样要在一个分区上存放系统启动所必需的文件,比如内核映象文件(在嵌入式系统中,内核一般单独存放在一个分区中)、内核启动后运行的第一个程序(init)、给用户提供操作界面的shell程序、应用程序所依赖的库等。这些必需的、基本的文件,合称为根文件系统,它们存放在一个分区中。Linux系统启动后首先挂接这个分区──称为挂接(mount)根文件系统。其他分区上所有目录、文件的集合,也称为文件系统,比如我们常说:“挂接硬盘第二个分区”、“挂接硬盘第二个分区上的文件系统”。

Linux中并没有C、D、E等盘符的概念,它以树状结构管理所有目录、文件,其他分区挂接在某个目录上──这个目录被称为挂接点或安装点(mount point),然后就可以通过这个目录来访问这个分区上的文件了。比如根文件系统被挂接在根目录“/”上后,在根目录下就有根文件系统的各个目录、文件:/bin、/sbin、/mnt等;再将其他分区挂接到/mnt目录上, /mnt目录下就有这个分区的各个目录、文件。

在一个分区上存储文件时,需要遵循一定的格式,这种格式称为文件系统类型,比如fat16、fat32、ntfs、ext2、ext3、jffs2、yaffs等。除这些拥有实实在在的存储分区的文件系统类型外,Linux还有几种虚拟的文件系统类型,比如proc、sysfs等,它们的文件并不存储在实际的设备上,而是在访问它们时由内核临时生成。比如proc文件系统下的uptime文件,读取它时可以得到两个时间值(用来表示系统启动后运行的时间秒数、空闲的时间秒数),每次读取时都由内核即刻生成,每次读取结果都不一样。

“文件系统类型”常被简称为“文件系统”,比如“硬盘第二个分区上的文件系统是EXT2”──这时指的就是文件系统类型。所以“文件系统”这个术语,有时候指的是分区上的文件集合,有时候指的是文件系统类型,需要根据语境分辨,读者在阅读各类文献时需要注意这点。

17.1.2  Linux根文件系统目录结构

为了在安装软件时能够预知文件、目录的存放位置,为了让用户方便地找到不同类型的文件,在构造文件系统时,建议遵循FHS标准(Filesystem Hierarchy Standard,文件系统层次标准)。它定义了文件系统中目录、文件分类存放的原则、定义了系统运行所需的最小文件、目录的集合,并列举了不遵循这些原则的例外情况及其原因。FHS并不是一个强制的标准,但是大多的Linux、Unix发行版本遵循FHS。

本节根据FHS标准描述Linux根文件系统的目录结构,并不深入描述各个子目录的结构,读者可以自行阅读FHS标准了解这些内容。FHS文档可以从网站http://www.pathname.com/fhs/中下载。

Linux根文件系统中一般有如图17.1所示的几个目录。

图17.1 Linux根文件系统结构

下面依次讲述这几个目录的作用。

1. /bin目录

该目录下存放所有用户(包括系统管理员和一般用户)都可以使用的、基本的命令,这些命令在挂接其他文件系统之前就可以使用,所以/bin目录必须和根文件系统在同一个分区中。

/bin目录下常用的命令有:cat、chgrp、chmod、cp、ls、sh、kill、mount、umount、mkdir、mknod、[、test等。额外说明,[命令其实就是test命令,在脚本文件中“[ expr ]”就等价于“test expr”。

2. /sbin目录

该目录下存放系统命令,即只有管理员能够使用的命令,系统命令还可以存放在/usr/sbin、/usr/local/sbin目录下。/sbin目录中存放的是基本的系统命令,它们用于启动系统、修复系统等。与/bin目录相似,在挂接其他文件系统之前就可以使用/sbin,所以/sbin目录必须和根文件系统在同一个分区中。

/sbin目录下常用的命令有:shutdown、reboot、fdisk、fsck等。

不是急迫需要使用的系统命令存放在/usr/sbin目录下。本地安装的(Locally-installed)的系统命令存放在/usr/local/sbin目录下。

3. /dev目录

该目录下存放的是设备文件。设备文件是Linux中特有的文件类型,在Linux系统下,以文件的方式访问各种外设,即通过读写某个设备文件操作某个具体硬件。比如通过“/dev/ttySAC0”文件可以操作串口0,通过“/dev/mtdblock1”可以访问MTD设备(NAND Flash、NOR Flash等)的第2个分区。

设备文件有两种:字符设备和块设备。在PC上执行命令“ls /dev/ttySAC0 /dev/hda1 -l”可以看到如下结果。其中首行的字母“b”、“c”表示这是一个块设备文件或字符设备文件;“3,   1”、“4,  64”表示设备文件的主、次设备号;主设备号用来表示这是哪类设备,次设备号用来表示这是这类设备中的哪个。

brwxrwxr-x    1 root     49         3,   1 Oct  9  2005 /dev/hda1

crwxrwxr-x    1 root     root       4,  64 Sep 24  2007 /dev/ttySAC0

设备文件可以使用mknod命令创建,比如:

mknod /dev/ttySAC0 c 4 64

mknod /dev/hda1 b 3 1

/dev的创建有3种方法:

(1)手动创建。

在制作根文件系统的时候,就在/dev目录下创建好要使用的设备文件,比如ttySAC0等。系统挂接根文件系统后,就可以使用/dev目录下的设备文件了。

(2)使用devfs文件系统:这种方法已经过时

在以前的内核中,有一个配置选项CONFIG_DEVFS_FS,它用来将虚拟文件系统devfs挂接在/dev目录上,各个驱动程序注册时会在/dev目录下自动生成各种设备文件。这就免去了手动创建设备文件的麻烦,在制作根文件系统时,/dev目录可以为空。

使用devfs比手动创建设备节点带来很多便利,但是它仍有一些无法克服的缺点,比如:

① 不确定的设备映射:

比如USB接口连接两台打印机A和B,在都开机的情况下以/dev/usb/lp0访问A、以/dev/usb/lp1访问B。但是假如A没有上电,则系统启动时会根据扫描到的设备的顺序,以/dev/usb/lp0访问B。

② 没有足够的主/次设备号:

主次设备号是两个8位的数字,它们并不足以与日益增加的外设一一对应。

③ 命名不够灵活:

由于devfs由内核创建设备节点,当想重新修改某个设备的名字时需要修改、编译内核。

④ devfs消耗大量的内存

由于这些缺点,在linux 2.3.46引入devfs之后,又在linux 2.6.13后面的版本中移除了devfs,而使用udev机制代替。

(3)udev。

udev是个用户程序(u 是指user space,dev是指device),它能够根据系统中硬件设备的状态动态地更新设备文件,包括设备文件的创建,删除等。

使用udev机制也不需要在/dev目录下创建设备节点,它需要一些用户程序的支持,并且内核要支持sysfs文件系统。它的操作相对复杂,但是灵活性很高。

在busybox中有一个mdev命令,它是udev命令的简化版本。

4. /etc目录。

如表17.1、17.2所示,该目录下存放各种配置文件。对于PC上的Linux系统,/etc目录下目录、文件非常多,比如下面两个表格所列出来的。这些目录、文件都是可选的,它们依赖于系统中所拥有的应用程序,依赖于这些程序是否需要配置文件。在嵌入系统中,这些内容可以大为精减。

表17.1                       

/etc目录下的子目录

目录

描述

opt

用来配置/opt下的程序(可选)

X11

用来配置X Window(可选)

sgml

用来配置SGML(可选)

xml

用来配置XML(可选)

表17.2    

 /etc目录下的文件

该目录下存放共享库和可加载模块(即驱动程序),其中的共享库用于启动系统、运行根文件系统中的可执行程序,比如/bin、/sbin目录下的程序。其他不是根文件系统所必需的库文件可以放在其他目录,比如/usr/lib、/usr/X11R6/lib、/var/lib等。

表17.3是/lib目录中的内容。

表17.3                           

/lib目录中的内容

目录/文件

描述

libc.so.*

动态连接C库(可选)

ld*

连接器、加载器(可选)

modules

内核可加载模式存放的目录(可选)

6. /home目录

用户目录,它是可选的。对于每个普通用户,在/home目录下都有一个以用户名命名的子目录,里面存放用户相关的配置文件。

7. /root目录

根用户(用户名为root)的目录,与此对应,普通用户的目录是/home下的某个子目录。

8. /usr目录

/usr目录的内容可以存在另一个分区中,在系统启动后再挂接到根文件系统中的/usr目录下。里面存放的是共享的、只读的程序和数据,这表明/usr目录下的内容可以在多个主机间共享──这要这些主机也是符合FHS标准的,/usr中的文件应该是只读的,其他主机相关的、可变的文件应该保存在其他目录下,比如/var。

/usr目录通常包含如下内容,嵌入式系统中,这些内容可以进一步精减。/usr目录中的内容如表17.4所示。

表17.4                              

/usr目录中的内容

目录

描述

bin

很多用户命令存放在这个目录下

include

C程序的头文件,这在PC上进行开发时才用到,在嵌入式系统中不需要

lib

库文件

local

本地目录

sbin

非必需的系统命令(必需的系统命令放在/sbin目录下)

share

架构无关的数据

X11R6

XWindow系统

games

游戏

src

源代码

9. /var目录

与/usr目录相反,/var目录中存放可变的数据,比如spool目录(mail、news、打印机等用的), log文件、临时文件。

10. /proc目录

这是一个空目录,常作为proc文件系统的挂接点。proc文件系统是个虚拟的文件系统,它没有实际的存储设备,里面的目录、文件都是由内核临时生成的,用来表示系统的运行状态,也可以操作其中的文件控制系统。

系统启动后,使用以下命令挂接proc文件系统(常在/etc/fstab进行设置以自动挂接):

# mount –t proc none /proc

11. /mnt目录

用于临时挂接某个文件系统的挂接点,通常是空目录;也可以在里面创建一些空的子目录,比如/mnt/cdram、/mnt/hda1等,用来临时挂接光盘、硬盘。

12. /tmp目录

用于存放临时文件,通常是空目录。一些需要生成临时文件的程序要用到/tmp目录,所以/tmp目录必须存在并可以访问。

为减少对Flash的操作,当在/tmp目录上挂接内存文件系统,如下:

# mount –t tmpfs none /tmp

17.1.3  Linux文件属性介绍

Linux系统有如表17.5所示的几种文件类型。

表17.5                             

Linux文件类型

文件类型

描述

普通文件

这是最常见的文件类型

目录文件

目录也是一种文件

字符设备文件

用来访问字符设备

块设备文件

用来访问块设备

FIFO

用于进程间的通信,也称为命名管道

套接口

用于进程间的网络通信

连接文件

它指向另一个文件,有软连接、硬连接

使用“ls -lih”命令可以看到各个文件的具体信息,下面选取这几种文件,列出它们的信息:

228883 -rw-r--r--    2 root     root            6 Sep 27 22:10 readme.txt

228884 lrwxrwxrwx    1 root     root           10 Sep 27 22:11 ln_soft -> readme.txt

 228883 -rw-r--r--    2 root     root            6 Sep 27 22:10 ln_hard

 228882 drwxr-xr-x    2 root     root         4.0K Sep 27 22:10 tmp_dir

 228880 crw-r--r--    1 root     root       4,  64 Sep 27 22:09 ttySAC0

 228881 brw-r--r--    1 root     root      31,   0 Sep 27 22:09 mtdblock0

 228885 prw-r--r--    1 root     root            0 Sep 27 22:16 my_fifo

343929 srwxr-xr-x    1 root     root            0 May 20  2006 klaunchertIdhOa.slave-socket

除设备文件ttySAC0、mtdblock0外,这些信息都分为8个字段,比如:

228883 -rw-r--r--    2 root     root            6 Sep 27 22:10 readme.txt

字段1     2         3  4        5              6      7          8

它们的意义如下:

(1)字段1:文件的索引节点inode

索引节点里存放一个文件的上述信息,比如文件大小、属主、归属的用户组、读写权限等,并指明文件的实际数据存放的位置。

(2)字段2:文件种类和权限

这字段共分10位,格式如下:

图17.2 文件类型及属性

文件类型有7种,“-”表示普通文件,“d”表示目录,“c”表示字符设备,“b”表示块设备,“p”表示FIFO(即管道),“l”表示软连接(也称符号连接),“s”表示套接口(socket)。

没有专门的符号来表示“硬连接”类型,硬连接也是普通文件,只不过文件的实际内容只有一个副本,连接文件、被连接文件都指向它。比如上面的ln_hard文件是使用命令“ln readme.txt ln_hard”创建出来的到readme.txt文件的硬连接,readme.txt和ln_hard的地位完全一致,它们都指向文件系统中的同一个位置,它们的“硬连接个数”都是2,表示这个文件的实际内容被引用两次──可以从上面的文件信息中看到这两个文件的inode都是228883。

硬连接文件的引入作用有二:使得可以用别名来引用一个文件,避免文件被误删除──只有当硬连接个数为1时,对一个文件执行删除操作才会真正删除文件的副本。但是它有如下缺点:不能创建到目录的连接,被连接文件和连接文件必须在同一个文件系统中。对此,引入软连接,也称符号连接,软连接只是简单地指向一个文件(可以是目录),并不增加它的硬连接个数。比如上面的ln_soft文件就是使用命令“ln -s readme.txt ln_hard”创建出来的到readme.txt文件的软连接,它使用另一个inode。

剩下的9位分为3组,分别用来表示文件拥有者、同一个群组的用户、其他用户对这个文件的访问权限。每组权限由rwx三位组成,表示可读、可写、可执行。如果某一位被设为“-”,则表示没有相应的权限,比如“rw-”表示只有读写权限,没有执行权限。

(3)字段3:硬连接个数,这在上面已经提到。

(4)字段4:文件拥有者

(5)字段5:所属群组

(6)字段6:文件或目录的大小

(7)字段7:最后访问或修改时间

(8)字段8:文件名或目录名

对于设备文件,字段6表示主设备号,字段7表示次设备号。

17.2  移植Busybox

所谓制作根文件系统,就是创建上节提到的各种目录,并且在里面创建各种文件。比如在/bin、/sbin目录下存放各种可执行程序,在/etc目录下存放配置文件,在/lib目录下存放库文件。这节讲述如何使用Busybox来创建/bin、/sbin等目录下的可执行文件。

17.2.1  Busybox概述

Busybox是一个遵循GPL v2协议的开源项目。Busybox将众多的UNIX命令集合进一个很小的可执行程序中,可以用来替换GNU fileutils、shellutils等工具集。Busybox中各种命令与相应的GNU工具相比,所能提供的选项较少,但是能够满足一般应用。Busybox为各种小型的或者嵌入式系统提供了一个比较完全的工具集。

Busybox在编写过程对文件大小进行的优化,并考虑了系统资源有限(比如内存等)的情况。与一般的GNU工具集动辄几M的体积相比,动态连接的Busybox只有几百K,即使静态连接也只有1M左右。Busybox按模块进行设计,可以很容易地加入、去除某些命令,或增减命令的某些选项。

在创建一个最小的根文件系统时,使用Busybox的话,只需要在/dev目录下创建必要的设备节点、在/etc目录下创建一些配置文件就可以了──当然,如果Busybox使用动态连接,还要在/lib目录下包含库文件。

Busybox支持uClibc库和glibc库,对Linux 2.2.x之后的内核支持良好。

Busybox的官方网站是http://www.busybox.net/,源码可以从http://www.busybox.net/downloads/下载,本书使用busybox-1.7.0.tar.bz2。

17.2.2  init进程介绍及用户程序启动过程

本节介绍Linux系统中用户程序启动的一些基础知识,读者可以直接阅读下一节开始移植Busybox。

init进程是由内核启动的第一个(也是唯一的一个)用户进程(进程ID为1),它根据配置文件决定启动哪些程序,比如执行某些脚本、启动shell、运行用户指定的程序等。init进程是后续所有进程的发起者,比如init进程启动/bin/sh程序后,才能够在控制台上输入各种命令。

init进程的执行程序通常是/sbin/init,上面讲述的init进程的作用只不过是/sbin/init这个程序的功能。我们完全可以编写自己的/sbin/init程序,或者传入命令行参数“init=xxxxx”指定某个程序作为init进程运行。

一般而言,在Linux系统有两种init程序:BSD init和System V init。BSD和System V是两种版本的UNIX系统。这两种init程序各有优缺点,现在大多Linux的发行版本使用System V init。但是在嵌入式领域,通常使用Busybox集成的init程序,下面基于它进行讲解。

1. 内核如何启动init进程

内核启动的最后一步就是启动init进程,代码在init/main.c文件中:

static int noinline init_post(void)
{

     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 %sn",
                 ramdisk_execute_command);
     }

     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.");
 }

代码并不复杂,其中的run_init_process函数使用它的参数所指定的程序来创建一个用户进程。需要注意,一旦run_init_process函数创建进程成功,它将不会返回。

内核启动init进程的过程如下:

(1) 打开标准输入、标准输出、标准错误设备。

Linux中最先打开的3个文件分别称为标准输入(stdin)、标准输出(stdout)、标准错误(stderr),它们对应的文件描述符分别为0、1、2。所谓标准输入就是在程序中使用scanf(……)、fscanf(stdin, ……)获取数据时,从哪个文件(设备)读取数据;标准输出、标准错误都是输出设备,前者对应printf(……)、fprintf(stdout, ……),后者对应fprintf(stderr, ……)。

第756行尝试打开/dev/console设备文件,如果成功,它就是init进程标准输入设备。

第759、760将文件描述符0复制给文件描述符1、2,所以标准输入、标准输出、标准错误都对应同一个文件(设备)。

在移植Linux内核时,如果发现打印出“Warning: unable to open an initial console.”,其原因大多是:根文件系统虽然被正确挂接了,但是里面的内容不正确──要么没有/dev/console这个文件,要么它没有对应的设备。

(2) 如果ramdisk_execute_command变量指定了要运行的程序,启动它。

ramdisk_execute_command的取值(代码也在init/main.c中)分3种情况:

① 如果命令行参数中指定了“rdinit=……”,则ramdisk_execute_command等于这个参数指定的程序。

② 否则,如果/init程序存在,ramdisk_execute_command就等于“/init”。

③ 否则,ramdisk_execute_command为空。

本书所用的命令行没有设定“rdinit=……”,根文件系统中也没有/init程序,所以ramdisk_execute_command为空,第763~765这几行的代码不执行。

(3) 如果execute_command变量指定了要运行的程序,启动它。

如果命令行参数中指定了“init=……”,则execute_command等于这个参数指定的程序,否则为空。

本书所用的命令行没有设定“init=……”,所以第775~777这几行的代码不执行。

(4) 依次尝试执行/sbin/init、/etc/init、/bin/init、/bin/sh。

第779行执行/sbin/init程序,这个程序在我们的根文件系统中是存在的,所以init进程所用的程序就是/sbin/init。从此系统的控制权交给/sbin/init,不再返回init_post函数中。

run_init_process函数也在init/main.c中,代码如下:

 static char * argv_init[MAX_INIT_ARGS+2] = { "init", NULL, };
 char * envp_init[MAX_INIT_ENVS+2] = { "HOME=/", "TERM=linux", NULL, };

 static void run_init_process(char *init_filename)
 {
     argv_init[0] = init_filename;
     kernel_execve(init_filename, argv_init, envp_init);
 }

所以执行/sbin/init程序时,它的环境参数为“"HOME=/", "TERM=linux"”。

2. Busybox init进程的启动过程

Busybox init程序对应的代码在init/init.c文件中,下面以busybox-1.7.0为例进行讲解。

先概述其流程,再结合一个/etc/inittab文件讲述init进程的启动过程。

(1)Busybox init程序流程。

流程图如图17.3所示,其中与构建根文件系统关系密切的是控制台的初始化、对inittab文件的解释及执行。

图17.3 Busybox init程序流程图

内核启动init进程时已经打开“/dev/console”设备作为控制台,一般情况下Busybox init程序就使用/dev/console。但是如果内核启动init进程的同时设置了环境变量CONSOLE或console,则使用环境变量所指定的设备。在Busybox init程序中,还会检查这个设备是否可以打开,如果不能打开则使用“/dev/null”。

Busybox init进程只是作为其他进程的发起者和控制者,并不需要控制台与用户交互,所以init进程会把它关掉──系统启动后运行命令“ls /proc/1/fd/”可以看到该目录为空。init进程创建其他子进程时,如果没有在/etc/inittab中指明它的控制台,则使用前面确定的控制台。

/etc/inittab文件的相关文档和示例代码都在Busybox的examples/inittab文件中。

如果存在/etc/inittab文件,Busybox init程序解析它,然后按照它的指示创建各种子进程;否则使用默认的配置创建子进程。

/etc/inittab文件中每个条目用来定义一个子进程,并确定它的启动方法。格式如下:

<id>:<runlevels>:<action>:<process>

例如:

ttySAC0::askfirst:-/bin/sh

对于Busybox init程序,上述各个字段作用如下:

① <id>:表示这个子进程要使用的控制台(即标准输入、标准输出、标准错误设备)。如果省略,则使用与init进程一样的控制台。

② <runlevels>:对于Busybox init程序,这个字段没有意义,可以省略。

③ <action>:

表示init进程如何控制这个子进程,有如表17.6所示的8种取值:

表17.6                  /etc/inittab文件中<action>字段的意义

action名称

执行条件

说明

sysinit

系统启动后最先执行

只执行一次,init进程等待它结束才继续执行其他动作

wait

wait

系统执行完sysinit进程后

只执行一次,init进程等待它结束才继续执行其他动作

wait

once

系统执行完wait进程后

只执行一次,init进程不等待它结束

respawn

启动完once进程后

init进程监测发现子进程退出时,重新启动它

askfirst

启动完respawn进程后

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

shutdown

当系统关机时

即重启、关闭系统命令时

restart

Busybox中配置了CONFIG_FEATURE_USE_INITTAB,并且init进程接收到SIGHUP信号时

先重新读取、解析/etc/inittab文件,再执行restart程序

ctrlaltdel

按下Ctrl+Alt+Delete组合键时

④ <process>:要执行的程序,它可以是可执行程序,也可以是脚本。

如果<procss>字段前有“-”字符,这个程序被称为“交互的”。

在/etc/inittab文件文件的控制下,init进程的行为总结如下:

① 在系统启动前期,init进程首先启动<action>为sysinit、wait、once的3类子进程。

② 在系统正常运行期间,init进程首先启动<action>为respawn、askfirst的两类子进程,并监视它们,发现某个子进程退出时重新启动它。

③ 在系统退出时,执行<action>为shutdown、restart、ctrlaltdel的3类子进程(之一或全部)。

如果根文件系统中没有/etc/inittab文件,Busybox init程序将使用如下默认的inittab条目:

::sysinit:/etc/init.d/rcS

::askfirst:/bin/sh

tty2::askfirst:/bin/sh

tty3::askfirst:/bin/sh

tty4::askfirst:/bin/sh

::ctrlaltdel:/sbin/reboot

::shutdown:/sbin/swapoff -a

::shutdown:/bin/umount -a -r

::restart:/sbin/init

(2)/etc/inittab实例。

仿照Busybox的examples/inittab文件,创建一个inittab文件,内容如下:

# /etc/inittab

# 这是init进程启动的第一个子进程,它是一个脚本,可以在里面指定用户想执行的操作

# 比如挂接其他文件系统、配置网络等

::sysinit:/etc/init.d/rcS

# 启动shell,以/dev/ttySAC0作为控制台

ttySAC0::askfirst:-/bin/sh

# 按下Ctrl+Alt+Delete之后执行的程序,不过在串口控制台中无法输入Ctrl+Alt+Delete组合键

::ctrlaltdel:/sbin/reboot

# 重启、关机前执行的程序

::shutdown:/bin/umount -a -r

17.2.3  编译/安装Busybox

http://www.busybox.net/downloads/下载busybox-1.7.0.tar.bz2。

使用如下命令解压得到busybox-1.7.0目录,里面就是所有的源码:

$ tar xjf busybox-1.7.0.tar.bz2

Busybox集合了几百个命令,在一般系统中并不需要全部使用。可以通过配置Busybox来选择这些命令、定制某些命令的功能(选项)、指定Busybox的连接方法(动态连接还是静态连接)、指定Busybox的安装路径。

1.  配置Busybox。

在busybox-1.7.0目录下执行“make menuconfig”命令即可进入配置界面。Busybox将所有配置项分类存放,表17.7列出了这些类别,其中的“说明”是针对嵌入式系统而言的:

表17.7                         Busybox配置选项分类

配置项类型

说明

Busybox Settings

Busybox的一些总体设置,里面分为下面5个子类

Busybox Settings ---> General Configuration

一些通用的设置,一般不需要理会

Busybox Settings ---> Build Options

连接方式、编译选项等

Busybox Settings ---> Debugging Options

调试选项,使用Busybox时将打印一些调试信息。一般不选。

Busybox Settings ---> Installation Options

Busybox的安装路径,不需设置,可以在命令行中指定

Busybox Settings ---> Busybox Library Tuning

Busybox的性能微调,比如设置在控制台上可以输入的最大字符个数,一般使用默认值即可

Archival Utilities

各种压缩、解压缩工具,根据需要选择相关命令

Coreutils

核心的命令,比如ls、cp等。

Console Utilities

控制台相关的命令,比如清屏命令clear等。只是提供一些方便而已,可以不理会。

Debian Utilities

Debian命令(Debian是Linux的一种发行版本),比如which命令可以用来显示一个命令的完整路径。

Editors

编辑命令,一般都选中vi

Finding Utilities

查找命令,一般不用

Init Utilities

init程序的配置选项,比如是否读取inittab文件。使用默认配置即可。

Login/Password Management Utilities

登录、用户帐号/密码等方面的命令

Linux Ext2 FS Progs

Ext2文件系统的一些工具

Linux Module Utilities

加载/卸载模块的命令,一般都选中

Linux System Utilities

一些系统命令,比如显示内核打印信息的dmesg命令、分区命令fdisk等。

Miscellaneous Utilities

一些不好分类的命令

Networking Utilities

网络方面的命令,可以选择一些可以方便调试的命令,比如telnetd、ping、tftp等。

Process Utilities

进程相关的命令,比如查看进程状态的命令ps、查看内存使用情况的命令free、发送信号的命令kill、查看最消耗CPU资源的前几个进程的命令top等。为方便调试,可以都选中。

Shells

有多种shell,比如msh、ash等。一般选择ash。

System Logging Utilities

系统记录(log)方面的命令

Runit Utilities

本书没有用到

ipsvd utilities

监听TCP、DPB端口,发现有新的连接时启动某个程序

本节使用默认配置,执行“make menuconfig”后退出、保存配置即可。

下面只讲述一些常用的选项,以便读者参考。Busybox的配置过程大多是选择、去除各种命令,一目了然。

(1)Busybox的性能微调。

设置TAB键补全,比如在控制给上输入一个“ifc”后按TAB键,它会补全为“ifconfig”。如下配置:

Busybox Settings  --->

    Busybox Library Tuning  --->

        [*]   Tab completion

(2)连接/编译选项。

以下选项指定是否使用静态连接:

Build Options  --->

    [ ] Build BusyBox as a static binary (no shared libs)

使用glibc时,如果静态编译Buxybox会提示以下警告信息,表示会出现一些莫名其秒的问题:

#warning Static linking against glibc produces buggy executables

所以,本书使用动态连接的Busybos,在构造根文件系统时需要在/lib目录下放置glibc库文件。

(3)Archival Utilities选项。

选择tar命令:

Archival Utilities  --->

    [*] tar

    [*]     Enable archive creation

    [*]     Enable -j option to handle .tar.bz2 files

    [*]     Enable -X (exclude from) and -T (include from) options)

    [*]     Enable -z option

    [*]     Enable -Z option

    [*]     Enable support for old tar header format

    [*]     Enable support for some GNU tar extensions

    [*]     Enable long options

(4)Linux Module Utilities选项。

要使用可加载模块,下面的配置要选上:

Linux Module Utilities  --->    

    [*] insmod

    [*]   Module version checking

    [*]   Add module symbols to kernel symbol table

    [*]   In kernel memory optimization (uClinux only)

    [*]   Enable load map (-m) option

    [*]     Symbols in load map

    [*] rmmod

    [*] lsmod

    [*] Support version 2.6.x Linux kernels

(5)Linux System Utilities选项。

支持mdev,这可以很方便地构造/dev目录,并且可以支持热拔插设备。另外,为方便调试,选中mount、umount命令,并让mount命令支持NFS(网络文件系统)。

Linux System Utilities  --->

    [*] mdev

    [*]   Support /etc/mdev.conf

    [*]     Support command execution at device addition/removal

    [*] mount

    [*]     Support mounting NFS file systems

    [*] umount

    [*]   umount -a option

(6)Networking Utilities选项。

除其他默认配置外,增加ifconfig命令:

Networking Utilities  --->

    [*] ifconfig

    [*]     Enable status reporting output (+7k)

    [ ]     Enable slip-specific options "keepalive" and "outfill"

    [ ]     Enable options "mem_start", "io_addr", and "irq"

    [*]     Enable option "hw" (ether only)

    [*]     Set the broadcast automatically

2.  编译和安装Busybox

编译之前,先修改Busybox根目录的Makefile,使用交叉编译器:

175 ARCH            ?= $(SUBARCH)

176 CROSS_COMPILE   ?=

改为:

175 ARCH            ?= arm

176 CROSS_COMPILE   ?= arm-linux-

然后可执行“make”命令编译Busybox。

最后是安装,执行“make CONFIG_PREFIX=dir_path install”就可以将Busybox安装在dir_name指定的目录下。执行以下命令在/work/nfs_root/fs_mini目录下安装Busybox:

$ make CONFIG_PREFIX=/work/nfs_root/fs_mini install

一切完成后,将在/work/nfs_root/fs_mini目录下生成如下文件、目录:

drwxr-xr-x 2 book book 4096 2008-01-22 06:56 bin

lrwxrwxrwx 1 book book   11 2008-01-22 06:56 linuxrc -> bin/busybox

drwxr-xr-x 2 book book 4096 2008-01-22 06:56 sbin

drwxr-xr-x 4 book book 4096 2008-01-22 06:56 usr

其中linuxrc和上面分析的/sbin/init程序功能完全一样;其他目录下是各种命令,不过它们都是到/bin/busybox的符号连接,比如/work/nfs_root/fs_mini/sbin目录下:

lrwxrwxrwx 1 book book 14 2008-01-22 06:56 halt -> ../bin/busybox

lrwxrwxrwx 1 book book 14 2008-01-22 06:56 ifconfig -> ../bin/busybox

lrwxrwxrwx 1 book book 14 2008-01-22 06:56 init -> ../bin/busybox

lrwxrwxrwx 1 book book 14 2008-01-22 06:56 insmod -> ../bin/busybox

lrwxrwxrwx 1 book book 14 2008-01-22 06:56 klogd -> ../bin/busybox

……

除bin/busybox外,其他文件都是到bin/busybox的符号连接──busybox是所有命令的集合体,这些符号连接文件可以直接运行。比如在开发板上,运行“ls”命令和“busybox ls”命令是一样的。

17.3  使用glibc库

在第二章制作交叉编译工具链时,已经生成了glibc库,可以直接使用它来构建根文件系统。

17.3.1  glibc库的组成

第二章制作的交叉编译工具链中,glibc库的位置是/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib。

需要澄清一点,这个目录下的文件并非都属于glibc库,比如crt1.o、libstdc++.a等文件是GCC工具本身生成的。本书不区分它们的来源,统一处理。

里面的目录、文件可以分为8类:

① 加载器ld-2.3.6.so、ld-linux.so.2:

动态程序启动前,它们被用来加载动态库。

② 目标文件(.o):

比如crt1.o、crti.o、crtn.o、gcrt1.o、Mcrt1.o、Scrt1.o等。在生成应用程序时,这些文件像一般的目标文件一样被连接。

③ 静态库文件(.a):

比如静态数学库libm.a、静态c++库libstdc++.a等,编译静态程序时会连接它们。

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

比如动态数学库libm.so、动态c++库libstdc++.so等,它们可能是一个链接文件。编译动态库时会用到这些文件,但是不会连接它们──在运行时才连接。

⑤ libtool库文件(.la):

在连接库文件时,这些文件会被用到,比如它们列出了当前库文件所依赖的其他库文件。程序运行时无需这些文件。

⑥ gconv目录:

里面是有头字符集的动态库,比如ISO8859-1.so、GB18030.so等。

⑦ ldscripts目录:

里面是各种连接脚本,在编译应用程序时,它们被用样指定程序的运行地址、各段的位置等。

⑧ 其他目录及文件

17.3.2  安装glibc库

在开发板上只需要加载器和动态库,假设要构建的根文件系统目录为/work/nfs_root/fs_mini,如下操作即可:

$ mkdir -p /work/nfs_root/fs_mini/lib

$ cd /work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib

$ cp *.so* /work/nfs_root/fs_mini/lib –d

上面复制的库文件不是每个都会被用到,可以根据应用程序对库的依赖关系保留需要用到的。通过ldd命令可以查看一个程序会用到哪些库,主机自带的ldd命令不能查看交叉编译出来的程序,有两种替代方法:

① 如果有uClibc-0.9.28的代码,可以进入utils子目录生成ldd.host工具:

$ cd uClibc-0.9.28/utils

$ make ldd.host

然后将生成的ldd.host放到主机/usr/local/bin目录下即可使用。

比如对于动态连接的Busybox,它的库依赖关系如下:

$ ldd.host busybox

        libcrypt.so.1 => /lib/libcrypt.so.1 (0x00000000)

        libm.so.6 => /lib/libm.so.6 (0x00000000)

        libc.so.6 => /lib/libc.so.6 (0x00000000)

        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x00000000)

这表示Busybox要使用的库文件有libcrypt.so.1、libm.so.6、libc.so.6,加载器为/lib/ld-linux.so.2(实际上在交叉工具链目录下,加载器为ld-linux.so.2)。上面的“not found”表示主机上没有这个文件──这没关系,单板的根文件系统上有就行。

② 可以使用以下命令:

$ arm-linux-readelf -a "your binary" | grep "Shared"

比如对于动态连接的Busybox,它的库依赖关系如下:

$ arm-linux-readelf -a ./busybox | grep "Shared"

 0x00000001 (NEEDED)                     Shared library: [libcrypt.so.1]

 0x00000001 (NEEDED)                     Shared library: [libm.so.6]

 0x00000001 (NEEDED)                     Shared library: [libc.so.6]

里面没有列出加载器,构造根文件系统时,它也要复制进去。

17.4  构建根文件系统

上面两节在介绍了如何安装Busbybox、C库,建立了bin/、sbin/、usr/bin/、usr/sbin/、lib/等目录──最小根文件系统的大部分目录、文件已经建好。本节介绍剩下的部分,假设单板的根文件系统在主机上的目录为/work/nfs_root/fs_mini。

17.4.1  构建etc目录

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

etc目录下的内容取决于要运行的程序,本节只需要创建3个文件:etc/inittab、etc/init.d/rcS、etc/fstab。

1.  创建etc/inittab文件

仿照Busybox的examples/inittab文件,在/work/nfs_root/fs_mini/etc目录下创建一个inittab文件,内容如下(其中各行的意义在第17.2.2节的最后有说明):

# /etc/inittab

::sysinit:/etc/init.d/rcS

ttySAC0::askfirst:-/bin/sh

::ctrlaltdel:/sbin/reboot

::shutdown:/bin/umount -a –r

2.  创建etc/init.d/rcS文件

这是一个脚本文件,可以在里面添加想自动执行命令。以下命令完成配置IP地址、挂接/etc/fstab指定的文件系统:

#!/bin/sh

ifconfig eth0 192.168.1.17

mount -a

第一行表示这是一个脚本文件,运行时使用/bin/sh解析。

第二行用来配置IP地址。

第三行挂接/etc/fstab文件指定的所有文件系统。

最后,还要改变它的属性,使它能够执行:

chmod +x etc/init.d/rcS

3.  创建etc/fstab文件

内容如下,表示执行“mount -a”命令后将挂接proc、tmpfs文件系统:

# device     mount-point    type   options        dump  fsck order

proc           /proc        proc   defaults        0     0

tmpfs          /tmp         tmpfs  defaults        0     0

/etc/fstab文件被用来定义文件系统的“静态信息”,这些信息被用来控制mount命令的行为。文件中各字段意义如下:

① device:要挂接的设备

比如/dev/hda2、/dev/mtdblock1等设备文件;也可以是其他格式,比如对于proc文件系统这个字段没有意义,可以是任意值;对于NFS文件系统,这个字段为<host>:<dir>。

② mount-point:挂接点

③ type:文件系统类型

比如proc、jffs2、yaffs、ext2、nfs等;也可以是auto,表示自动检测文件系统类型。

④ options:挂接参数,以逗号隔开。

/etc/fstab的作用不仅仅是用来控制“mount -a”的行为,即使是一般的mount命令也受它控制,这可以从表17.8的参数看到。除与文件系统类型相关的参数外,常用的有以下几种取值:

表17.8                         /etc/fstab参数字段常用的取值

参数名

说明

默认值

auto noauto

决定执行“mount -a”时是否自动挂接。 auto:挂接;noauto:不挂接

auto

user nouser

user:允许普通用户挂接设备; nouser:只允许root用户挂接设备

nouser

exec noexec

exec:允许运行所挂接设备上的程序 noexec:不允许运行所挂接设备上的程序

exec

Ro

以只读方式挂接文件系统

rw

以读写方式挂接文件系统

sync async

sync:修改文件时,它会同步写入设备中; async:不会同步写入

sync

defaults

rw、suid、dev、exec、auto、nouser、async等的组合

⑤ dump和fsck order:用来决定控制dump、fsck程序的行为。

dump是一个用来备份文件的程序,fsck是一个用来检查磁盘的程序。要想了解更多信息,请阅读它们的man手册。

dump程序根据dump字段的值来决定这个文件系统是否需要备份,如果没有这个字段,或其值为0,则dump程序忽略这个文件系统。

fsck程序根据fsck order字段来决定磁盘的检查顺序,一般来说对于根文件系统这个字段设为1,其他文件系统设为2。如果设为0,则fsck程序忽略这个文件系统。

17.4.2  构建dev目录

本节使用两种方法构建dev目录。

1.  静态创建设备文件

为简单起见,本书先使用最原始的方法处理设备:在/dev目录下静态创建各种节点(即设备文件)。

从系统启动过程可知,涉及的设备有:/dev/mtdblock*(MTD块设备)、/dev/ttySAC*(串口设备)、/dev/console、/dev/null,只要建立以下设备就可以启动系统:

$ mkdir –p /work/nfs_root/fs_mini/dev

$ cd /work/nfs_root/fs_mini/dev

$ sudo mknod console c 5 1

$ sudo mknod null c 1 3

$ sudo mknod ttySAC0 c 204 64

$ sudo mknod mtdblock0 b 31 0

$ sudo mknod mtdblock1 b 31 1

$ sudo mknod mtdblock2 b 31 2

注意:在一般系统中,ttySAC0的主设备号为4,但是在S3C2410、S3C2440所用的Linux 2.6.22.6上,它们的串口主设备号为204。

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

实际上,各个Linux系统中dev目录的内容很相似,本书最终使用的dev目录就是从其他系统中复制过来的。

2.  使用mdev创建设备文件

mdev是udev的简化版本,它也是通过读取内核信息来创建设备文件。

mdev的用法请参考busybox-1.7.0/doc/mdev.txt文件。mdev的用途主要有两个:初始化/dev目录、动态更新。“动态更新”不仅是更新/dev目录,还支持热拔插──接入、卸下设备时,执行某些动作,它需要内核支持“hotplugging”,即热热拔插。

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

使用mdev的命令如下,请参考它们的注释以了解其作用:

$ mount -t tmpfs mdev /dev             /* 使用内存文件系统,减少对flash的读写 */

$ mkdir /dev/pts                       /* devpts用来支持外部网络连接(telnet)的虚拟终端 */

$ mount -t devpts devpts /dev/pts

$ mount -t sysfs sysfs /sys                   /* mdev通过sysfs文件系统获得设备信息 */

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

$ mdev –s                                     /* 在/dev目录下生成内核支持的所有设备的结点 */

要在内核启动时,自动运行mdev。这要修改/work/nfs_root/fs_mini中的两个文件:修改etc/fstab来自动挂载文件系统、修改etc/init.d/rcS加入要自动运行的命令。修改后的文件如下:

① 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生成的/dev目录中,S3C2410、S3C2440是串口名是s3c2410_serial0、1、 2,不是ttySAC0、1、2。需要修改etc/inittab文件:

ttySAC0::askfirst:-/bin/sh

改为:

s3c2410_serial0::askfirst:-/bin/sh

另外,mdev是通过init进程来启动的,在使用mdev构造/dev目录之前,init进程至少要用到设备文件/dev/console、/dev/null。所以要建立这两个设备文件:

$ mkdir –p /work/nfs_root/fs_mini/dev

$ cd /work/nfs_root/fs_mini/dev

$ sudo mknod console c 5 1

$ sudo mknod null c 1 3

17.4.3  构建其他目录

其他目录可以是空目录,比如proc、mnt、tmp、sys、root等,如下创建:

# cd /work/nfs_root/fs_mini

# mkdir proc mnt tmp sys root

现在,/work/nfs_root/fs_mini目录下就是一个非常小的根文件系统。单板可以将它作为网络根文件系统直接启动。如果要烧入单板,还要将它制作为一个文件──称为映象文件,这在下节介绍。

17.4.4  制作/使用yaffs文件系统映象文件

按照前面的方法,在/work/nfs_root目录下构造了两个根文件系统:fs_mini、fs_mini_mdev。前者使用dev/目录中事先建立好的设备文件,后者使用mdev机制来生成dev/目录,它们的差别只在于3点:etc/inittab文件、etc/init.d/rcS文件、dev/目录。下面两节以/work/nfs_root/fs_mini为例制作根文件系统映象。

所谓制作文件系统映象文件,就是将一个目录下的所有内容按照一定的格式存放到一个文件中,这个文件可以直接烧写到存储设备上去。当系统启动后挂接这个设备,就可以看到与原来目录一样的内容。

制作不同类型的文件系统映象文件,要使用不同的工具。

1.  修改制作yaffs映象文件的工具

在yaffs源码中有个utils目录(假设这个目录为/work/system/Development/yaffs2/utils),里面是工具mkyaffsimage和mkyaffs2image的源代码。前者用来制作yaffs1映象文件,后者用来制作yaffs2映象文件。

目前mkyaffsimage工具只能生成老格式的yaffs1映象文件,需要修改才能支持新格式。对mkyaffsimage代码的修改都在补丁文件yaffs_util_mkyaffsimage.patch中,读者可以直接打补丁,也可以根据本小节进行修改。

yaffs1新、老格式的不同在于oob区的使用发生了变化:一是ECC较验码的位置发生了变化,二是可用空间即标记(tag)的数据结构定义发生了变化。

另外,由于配置内核时没有设置CONFIG_YAFFS_DOES_ECC,yaffs文件系统将使用MTD设备层的ECC较验方法,制作映象文件时也使用与MTD设备层相同的函数计算ECC码。

① oob区中较验码的位置变化:

oob区中使用6字节来存放ECC较验码,前3字节对应上半页,后3字节对应下半页。

参考第16章中“yaffs文件系统移植”小节,从nand_oob_16结构可知,以前的较验码在oob区中存放的位置为8、9、10、13、14和15,现在改为0、1、2、3、6和7。

② oob区中可用空间的数据结构定义变化:

oob区中可用的空间有8字节,它用来存放文件系统的数据,代码中这些数据被称为标记(tag)。

老格式的yaffs1中,这8字节的数据结构如下定义(在yaffs_guts.h文件中):

typedef struct {
    unsigned chunkId:20;
    unsigned serialNumber:2;
    unsigned byteCount:10;
    unsigned objectId:18;
    unsigned ecc:12;
    unsigned unusedStuff:2;
} yaffs_Tags;

新格式的yaffs1中,它如下定义(在yaffs_packedtags1.h文件中):

typedef struct {
    unsigned chunkId:20;
    unsigned serialNumber:2;
    unsigned byteCount:10;
    unsigned objectId:18;
    unsigned ecc:12;
    unsigned deleted:1;
    unsigned unusedStuff:1;
    unsigned shouldBeFF;      /* 新格式中,这个字节没有使用,yaffs_PackedTags1还是8字节 */
} yaffs_PackedTags1;

新、老结构有细微差别:老结构中有两位没有使用(unusedStuff);新结构中只有一位没有使用,另一位(deleted)被用来表示当前页是否已经删除。

③ oob区中ECC码的计算:

如果配置内核时设置了CONFIG_YAFFS_DOES_ECC,则yaffs文件系统将使用yaffs2/yaffs_ecc.c文件中的yaffs_ECCCalculate函数来计算ECC码;否则使用drivers/mtd/nand/nand_ecc.c文件中的nand_calculate_ecc函数。

mkyaffsimage工具原来的代码中使用yaffs_ECCCalculate函数。 由于上面配置内核时,没有选择CONFIG_YAFFS_DOES_ECC,为了使映象文件与内核保持一致,要修改mkyaffsimage源码,使用nand_calculate_ecc函数

对mkyaffsimage的修改就是依据这3点进行:

① 增加头文件:

修改文件mkyaffsimage.c,加上下面这行,里面定义了yaffs_PackedTags1结构:

#include "yaffs_packedtags1.h"

② 修改mkyaffsimage.c文件的write_chunk函数:

代码如下:

231 static int write_chunk(__u8 *data, __u32 objId, __u32 chunkId, __u32 nBytes)
232 {
233 #ifdef CONFIG_YAFFS_9BYTE_TAGS   /* 如果要生成老格式的yaffs1映象文件,定义这个宏 */
…… /* 原来的代码 */
260 #else
261     yaffs_PackedTags1 pt1;
262     yaffs_ExtendedTags  etags;
263     __u8 ecc_code[6];
264     __u8 oobbuf[16];
265     
266     /* 写页数据,512字节 */
267     error = write(outFile,data,512);
268     if(error < 0) return error;
269 
270     /* 构造tag */
271     etags.chunkId       = chunkId;
272     etags.serialNumber  = 0;
273     etags.byteCount     = nBytes;
274     etags.objectId      = objId;
275     etags.chunkDeleted  = 0;
276 
277     /* 
278      * 重定位oob区中的可用数据(称为tag)
279      */
280     yaffs_PackTags1(&pt1, &etags);
281 
282     /* 计算tag本身的ECC码 */
283     yaffs_CalcTagsECC((yaffs_Tags *)&pt1);
284 
285     memset(oobbuf, 0xff, 16);
286     memcpy(oobbuf+8, &pt1, 8);
287 
288     /* 
289      * 使用与内核MTD层相同的方法计算一页数据(512字节)的ECC码  
290      * 并把它们填入oob
291      */
292     nand_calculate_ecc(data, &ecc_code[0]);
293     nand_calculate_ecc(data+256, &ecc_code[3]);
294 
295     oobbuf[0] = ecc_code[0];
296     oobbuf[1] = ecc_code[1];
297     oobbuf[2] = ecc_code[2];
298     oobbuf[3] = ecc_code[3];
299     oobbuf[6] = ecc_code[4];
300     oobbuf[7] = ecc_code[5];
301 
302     nPages++;
303 
304     /* 写oob数据,16字节 */
305     return write(outFile, oobbuf, 16);
306 #endif  
307 }
308 

值得注意的是:第275行设置新tag结构中增加的chunkDeleted成员;第292~300行将计算出来的ECC码填入新的ECC位置──它正是nand_oob_16结构的eccpos数组定义的位置。

其中第292、293行的nand_calculate_ecc函数是从内核源文件drivers/mtd/nand/nand_ecc.c修改而来:在/work/system/Development/yaffs2/utils目录下新建一个同名文件nand_ecc.c,把内核文件nand_ecc.c的nand_calculate_ecc函数、函数中用到的nand_ecc_precalc_table数组摘出来;并去除函数中的第一个形参“struct mtd_info *mtd”──在这个函数中没用到这个参数。

③ 添加文件,修改Makefile:

第280行的yaffs_PackTags1函数在上一层目录yaffs_packedtags1.c中定义,先将这个文件复制到当前目录:

$ cp ../yaffs_packedtags1.c ./

另外,and_calculate_ecc函数是在新加的nand_ecc.c中定义的,所以要修改Makefile,把yaffs_packedtags1.c和nand_ecc.c也编译进mkyaffsimage工具中:

31 MKYAFFSSOURCES = mkyaffsimage.c

改为:

31 MKYAFFSSOURCES = mkyaffsimage.c yaffs_packedtags1.c nand_ecc.c

现在,在/work/system/Development/yaffs2/utils目录下执行“make”命令生成mkyaffsimage工具,将它复制到/usr/local/bin目录:

$ sudo cp mkyaffsimage /usr/local/bin

$ sudo chmod +x /usr/local/bin/mkyaffsimage

2.  制作/烧写yaffs映象文件

使用如下命令将/work/nfs_root/fs_mini目录制作为fs_mini.yaffs文件:

# cd /work/nfs_root

# mkyaffsimage fs_mini fs_mini.yaffs

将fs_mini.yaffs放入tftp目录或nfs目录后,在U-Boot控制界面就可以下载、烧入NAND Flash中,操作方法请参考《15.2.6  U-Boot的常用命令》。为方便读者,将命令列出来(以下命令将yaffs.img烧入MTD2分区,即yaffs分区):

① tftp 0x30000000 fs_mini.yaffs 或 nfs 0x30000000 192.168.1.57:/work/nfs_root/fs_mini.yaffs

② nand erase 0xA00000 0x3600000

③ nand write.yaffs 0x30000000 0xA00000 $(filesize)

现在可以修改命令行参数以MTD2分区作为根文件系统,比如在U-Boot控制界面如下设置:

# set bootargs noinitrd console=ttySAC0 root=/dev/mtdblock2 rootfstype=yaffs

# saveenv

17.4.5  制作/使用jffs2文件系统映象文件

1.  编译制作yaffs映象文件的工具

/work/tools/mtd-utils-05.07.23.tar.bz2是MTD设备的工具包,编译它生成mkfs.jffs2工具,用它来将一个目录制作成jffs2文件系统映象文件。

这个工具包需要zlib压缩包,先安装zlib。在/work/GUI/xwindow/X/deps下有zlib源码zlib-1.2.3.tar.gz,执行以下命令进行安装:

$ cd /work/GUI/xwindow/X/deps

$ tar xzf zlib-1.2.3.tar.gz

$ cd zlib-1.2.3

$ ./configure --shared --prefix=/usr

$ make

$ sudo make install

然后编译mkfs.jffs2:

$ cd /work/tools

$ tar xjf mtd-utils-05.07.23.tar.bz2

$ cd mtd-utils-05.07.23/util

$ make

$ sudo make install

2.  制作/烧写jffs2映象文件

使用如下命令将/work/nfs_root/fs_mini目录制作为fs_mini.jffs2文件:

$ cd /work/nfs_root

$ mkfs.jffs2 -n -s 512 -e 16KiB -d fs_mini -o fs_mini.jffs2

上面命令中,“-n”表示不要在每个擦除块上都加上清除标志,“-s 512”指明一页大小为512字节,“-e 16KiB”指明一个擦除块大小为16KB,“-d”表示根文件系统目录,“-o”表示输出文件。

将fs_mini.jffs2放入tftp目录或nfs目录后,在U-Boot控制界面就可以将下载、烧入NAND Flash中,操作方法请参考《15.2.6  U-Boot的常用命令》。为方便读者,将命令列出来(以下命令将jffs2.img烧入MTD1分区,即jffs2分区):

① tftp 0x30000000 fs_mini.jffs2 或 nfs 0x30000000 192.168.1.57:/work/nfs_root/fs_mini.jffs2

② nand erase 0x200000 0x800000

③ nand write.jffs2 0x30000000 0x200000 $(filesize)

系统启动后,就可以使用“mount -t jffs2 /dev/mtdblock1 /mnt”挂接jffs2文件系统。

也可以修改命令行参数以MTD1分区作为根文件系统,比如在U-Boot控制界面如下设置:

# set bootargs noinitrd console=ttySAC0 root=/dev/mtdblock1 rootfstype=jffs2

# saveenv