根文件系统的设计与编译
嵌入式系统的根文件系统(Root File System)设计包括存放的内容、文件系统类型、在哪里存放文件系统和如何启动它。
根文件系统通常存放在目标板的闪存(flash memory)中或CF、SD、MMC卡等中,可以使用像ELDK这样的Linux发布版本创建根文件系统的内容。
下面使用来自ELDK的SELF(Simple Embedded Linux Framework)映像创建根文件系统。
在ELDK软件包中,各种平台构架的SELF映像文件在目录/opt/eldk//images/下,文件名为ramdisk_image.gz,是个压缩的ramdisk映像。
由SELF创建根文件系统tar包的方法列出如下:
//解压缩ramdisk映像
bash$ gzip -d -c -v /opt/eldk/ppc_8xx/images/ramdisk_image.gz >/tmp/ramdisk_image
/opt/eldk/ppc_8xx/images/ramdisk_image.gz: 61.4%
//下面的操作需要root权限
//挂接ramdisk映像
bash# mount -o loop /tmp/ramdisk_image /mnt/tmp
//创建tar包,为了避免需要root权限,这里不包括在tar包中的设备(device)文件
bash# cd /mnt/tmp
bash# tar -zc --exclude='dev/*' -f /tmp/rootfs.tar.gz *
//需要时,为CramFs创建一个单独的tar包,用来包括设备条目
bash# tar -zcf /tmp/devices.tar.gz dev/
bash# cd /tmp
//卸载ramdisk映像
bash# umount /mnt/tmp
(1)根文件系统在ramdisk上
ramdisk常用于存放嵌入式系统的根文件系统,但很少情况将ramdisk映像作为嵌入式系统的根文件系统解决方案。
根文件系统包括在ramdisk中的优点是:编译简单;基于RAM,速度快;Linux内核支持完美;容易使用,甚至可将带有Linux内核的ramdisk放在一个映像文件中;根文件系统是可写的;每次重启动郈的初始状态容易恢复。 缺点是:每次必须将整个文件系统装入RAM,造成启动慢,需要附加存储器存放可写的永久数据。
在ramdisk映像中,根文件系统常用ext2文件系统类型,创建在ramdisk上根文件系统的方法说明如下:通过解开tar包创建目标板根文件系统内容的目录树
$ mkdir rootfs
$ cd rootfs
$ tar zxf /tmp/rootfs.tar.gz使用工具genext2fs创建ramdisk映像,可用设备表文本文件定义在产生的文件系统映像中应创建的设备,这意味着需要root权限。设备表rootfs_devices.tab部分内容列出如表2所示。
表2 设备表文件rootfs_devices.tab部分内容nametypemodeuidgidmajorminorstartinccount
/devd75500-----
/dev/consolec6400051---
/dev/fb0c64000290---
使用工具genext2fs 创建文件系统映像的方法如下:
$ ROOTFS_DIR=rootfs # 根文件系统内容目录
$ ROOTFS_SIZE=3700 # 文件系统映像的大小
$ ROOTFS_FREE=100 # 期望的空闲空间
$ ROOTFS_INODES=380 # 节点(inode)数
$ ROOTFS_DEVICES=rootfs_devices.tab # 设备描述表文件
$ ROOTFS_IMAGE=ramdisk.img # 产生的文件系统映像
$ genext2fs -U \
-d ${ROOTFS_DIR} \
-D ${ROOTFS_DEVICES} \
-b ${ROOTFS_SIZE} \
-r ${ROOTFS_FREE} \
-i ${ROOTFS_INODES} \
${ROOTFS_IMAGE}压缩文件系统映像,方法如下:
$ gzip -v9 ramdisk.img
rootfs.img: 55.6% -- replaced with ramdisk.img.gz创建U-Boot使用的根文件系统映像uRamdisk,方法如下:
$ mkimage -T ramdisk -C gzip -n 'Test Ramdisk Image' \
> -d ramdisk.img.gz uRamdisk
Image Name: Test Ramdisk Image
Created: Sun Jun 12 16:58:06 2005
Image Type: PowerPC Linux RAMDisk Image (gzip compressed)
Data Size: 1618547 Bytes = 1580.61 kB = 1.54 MB
Load Address: 0x00000000
Entry Point: 0x00000000
We now have a root file system image uRamdisk that can be used with U-Boot.
(2)根文件系统在JFFS2文件系统上
在嵌入式系统中,JFFS2作为根文件系统的优点是:文件系统使用压缩,可有效利用闪存空间;它是日志结构的文件系统,对无序的关机是强壮的;它是可写闪存文件系统。缺点是:挂接时间长;由于压缩/解压缩,造成读写操作时间长;垃圾回收线程可在任何时间启动,消耗CPU时间,阻塞对文件系统的访问。
尽管有上面一些不利因素,但基于JFFS2 的根文件系统容易编译,可有效利用可用的资源,并能可靠运行。
创建基于JFFS2的根文件系统的方法如下:通过解开tar包创建目标板根文件系统的内容,方法如下:
$ mkdir rootfs
$ cd rootfs
$ tar zxf /tmp/rootfs.tar.gz使用工具mkfs.jffs2创建一个JFFSW文件系统映像,方法如下:
$ ROOTFS_DIR=rootfs # 根文件系统内容目录
$ ROOTFS_EBSIZE=0x20000 # 闪存的擦除块大小
$ ROOTFS_ENDIAN=b # 目标板系统是大端字节序
$ ROOTFS_DEVICES=rootfs_devices.tab # 设备描述表文件
$ ROOTFS_IMAGE=jffs2.img # 产生的文件系统映像
$ mkfs.jffs2 -U \
-d ${ROOTFS_DIR} \
-D ${ROOTFS_DEVICES} \
-${ROOTFS_ENDIAN} \
-e ${ROOTFS_EBSIZE} \
-o ${ROOTFS_IMAGE}
mkfs.jffs2: skipping device_table entry '/dev': no parent directory!
注意:如果写JFFSW文件系统到NAND flash设备中时,不需要加"-n"或"选项,--no-cleanmarkers",因为不需要清除的标识。
启动内核时,终端显示缺省的分区映射表,列出如下:
0x00000000-0x00040000 : "u-boot"
0x00040000-0x00100000 : "kernel"
0x00100000-0x00200000 : "user"
0x00200000-0x00400000 : "initrd"
0x00400000-0x00600000 : "cramfs"
0x00600000-0x00800000 : "jffs"
0x00400000-0x00800000 : "big_fs"
使用U-Boot装载和存储JFFS2映像到最后一个分区,并建立Linux启动参数,将该映像作为根设备,方法如下:擦除flash
=> era 40400000 407FFFFF
................. done
Erased 35 sectors下载JFFS2映像
=> tftp 100000 /tftpboot/TQM860L/jffs2.img
Using FEC ETHERNET device
TFTP from server 192.168.3.1; our IP address is 192.168.3.80
Filename '/tftpboot/TQM860L/jffs2.img'.
Load address: 0x100000
Loading: #################################################################
done
Bytes transferred = 2033888 (1f08e0 hex)拷贝映像到flash
=> cp.b 100000 40400000 ${filesize}
Copy to Flash... done建立使用flash分区6作为根设备的启动参数
=> setenv mtd_args setenv bootargs root=/dev/mtdblock6 rw rootfstype=jffs2
=> printenv addip
addip=setenv bootargs ${bootargs} ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}:${hostname}:${=> setenv flash_mtd 'run mtd_args addip;bootm ${kernel_addr}'
=> run flash_mtd
Using FEC ETHERNET device
TFTP from server 192.168.3.1; our IP address is 192.168.3.80
Filename '/tftpboot/TQM860L/uImage'.
Load address: 0x200000
Loading: #################################################################
done
Bytes transferred = 719233 (af981 hex)
## Booting image at 40040000 ...
(3)根文件系统在cramfs文件系统上
cramfs是压缩的仅读文件系统,作为根文件系统的优点是:文件系统是压缩的,可有效利用闪存空间;允许快速启动,因为仅装载使用的文件,且仅解压缩。缺点是:仅能替换整个映像文件而非单个文件;需要附加的存储空间给可写的永久数据;工具mkcramfs不支持设备表。
创建基于cramfs的根文件系统的方法列出如下:通过解开tar包创建目标板根文件系统的内容,方法如下:
$ mkdir rootfs
$ cd rootfs
$ tar zxf /tmp/rootfs.tar.gz创建需要的设备文件,这里通过解开仅含有设备文件条目的tar包来实现,方法列出如下:
# cd rootfs# tar -zxf /tmp/devices.tar.gz
许多工具需要一些存储地方,这里提供了一个小的可写文件系统。临时文件系统tmpfs是一个选择,为了创建tmpfs文件系统,需要将下面的行加入到脚本/etc/rc.sh中:
# 挂接tmpfs,是因为根文件系统仅读
/bin/mount -t tmpfs -o size=2M tmpfs /tmpfs一些工具需要对某些设备节点有写权限,如:改变属主和权限,可动态创建设备节点,如:/dev/log。这些设备文件需要放在一个可写的文件系统中,仅读的根文件系统用符号链接指向可读设备到新的位置(即在可读写文件系统中的位置),对应符号链接位置部分列出如下:
dev/ptyp0 € /tmpfs/dev/ptyp0 dev/ttyp0 € /tmpfs/dev/ttyp0
dev/ptyp1 € /tmpfs/dev/ptyp1 dev/ttyp1 € /tmpfs/dev/ttyp1
dev/ptyp2 € /tmpfs/dev/ptyp2 dev/ttyp2 € /tmpfs/dev/ttyp2为了放置相应的目录与设备文件在tmpfs文件系统,放置下面的代码在/etc/rc.sh脚本中:
mkdir -p /tmpfs/tmp /tmpfs/dev \
/tmpfs/var/lib/dhcp /tmpfs/var/lock /tmpfs/var/run
while read name minor
do
mknod /tmpfs/dev/ptyp$name c 2 $minor
mknod /tmpfs/dev/ttyp$name c 3 $minor
done <<__eod__>
0 0
1 1
2 2
……
e 14
f 15
__EOD__
chmod 0666 /tmpfs/dev/*使用工具mkcramfs创建cramfs文件系统映像,方法如下:
$ ROOTFS_DIR=rootfs # 带有根文件系统内容的目录
$ ROOTFS_ENDIAN="-r" # 目标板系统有相反的字符序列
$ ROOTFS_IMAGE=cramfs.img # 产生的文件系统映像
PATH=/opt/eldk/usr/bin:$PATH
mkcramfs ${ROOTFS_ENDIAN} ${DEVICES} ${ROOTFS_DIR} ${ROOTFS_IMAGE}
Swapping filesystem endian-ness
bin
dev
etc
...
-48.78% (-86348 bytes) in.ftpd
-46.02% (-16280 bytes) in.telnetd
-45.31% (-74444 bytes) xinetd
Everything: 1864 kilobytes
Super block: 76 bytes
CRC: c166be6d
warning: gids truncated to 8 bits. (This may be a security concern.)可以使用前面JFFS2文件系统的方法建立启动参数,只是将启动参数设置到"rootfstype=cramfs "。
(4)根文件系统在仅读的ext2文件系统上
在闪存上使用标准的ext2文件系统不合适,因为它不含有任何类型的wear leveling,在闪存上的可写文件系统需要有wear leveling保护,需要对无序异常系统关闭有强壮的保护。使用仅读的ext2文件系统将可不需要这些保护。
挂接ext2文件系统时可挂接成仅读的,在某些情况下仅读的ext2文件系统很有用,如:根文件系统在闪存卡上。
仅读ext2文件系统优点是速度快,占用较少的RAM。缺省是由于没有压缩而占用较多的闪存。
与cramfs文件系统作为根文件系统一样,仅读ext2文件系统作为根文件系统,还需要使用tmpfs这样的可写文件系统,存放可写的文件数据。
仅读ext2文件系统作为根文件系统的操作步骤与cramfs文件系统一样,创建方法列出如下:通过解开tar包创建目标板根文件系统的内容,方法如下:
$ mkdir rootfs
$ cd rootfs
$ tar zxf /tmp/rootfs.tar.gz像cramfs根文件系统一样,这里使用tmpfs作为可写的文件系统,并添加下面的行在/etc/rc.sh脚本中:
# 因为根文件系统是仅读,所以挂接tmpfs文件系统作为可写的文件系统
/bin/mount -t tmpfs -o size=2M tmpfs /tmpfs创建设备文件的符号链接放在可写的文件系统中,符号链接从仅读文件系统到可写文件系统,符号链接的目录如下:
dev/ptyp0 € /tmpfs/dev/ptyp0 dev/ttyp0 € /tmpfs/dev/ttyp0
dev/ptyp1 € /tmpfs/dev/ptyp1 dev/ttyp1 € /tmpfs/dev/ttyp1放置相应目录和设备文件在tmpfs文件系统中,将下面的代码放在/etc/rc.sh脚本中:
mkdir -p /tmpfs/tmp /tmpfs/dev \
/tmpfs/var/lib/dhcp /tmpfs/var/lock /tmpfs/var/run
while read name minor
do
mknod /tmpfs/dev/ptyp$name c 2 $minor
mknod /tmpfs/dev/ttyp$name c 3 $minor
done <<__eod__>
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
a 10
b 11
c 12
d 13
e 14
f 15
__EOD__
chmod 0666 /tmpfs/dev/*使用genext2fs工具创建ext2文件系统映像,方法如下:
$ ROOTFS_DIR=rootfs # 根文件系统内容的目录
$ ROOTFS_SIZE=3700 # 文件系统映像的大小
$ ROOTFS_FREE=100 # 期望的空闲空间
$ ROOTFS_INODES=380 # 节点号
$ ROOTFS_DEVICES=rootfs_devices.tab # 设备描述文件
$ ROOTFS_IMAGE=ext2.img # 产生的文件系统映像
$ genext2fs -U \
-d ${ROOTFS_DIR} \
-D ${ROOTFS_DEVICES} \
-b ${ROOTFS_SIZE} \
-r ${ROOTFS_FREE} \
-i ${ROOTFS_INODES} \
${ROOTFS_IMAGE}可以使用前面JFFS2文件系统的方法建立启动参数,只是将启动参数设置到"rootfstype=ext2 "。
(5)根文件系统在闪存卡上
可在闪存卡(如:CF、SD、MMC等)上使用仅读ext2文件系统 作为根文件系统,用于启动系统。方法如下:将用工具genext2fs制作的ext2文件系统映像文件ext2.img拷贝到CF卡上,下面列出使用U-Boot拷贝映像文件到CF卡上的方法:
=> tftp 100000 /tftpboot/TQM860L/ext2.img
Using FEC ETHERNET device
TFTP from server 192.168.3.1; our IP address is 192.168.3.80
Filename '/tftpboot/TQM860L/ext2.img'.
Load address: 0x100000
Loading: #################################################################
done
Bytes transferred = 3788800 (39d000 hex)
=> ide part
Partition Map for IDE device 0 -- Partition Type: DOS
Partition Start Sector Num Sectors Type
1 32 500704 6
=> ide write 100000 20 1ce8 #装载地址,第一个分区的起始扇区,扇区数
IDE write: device 0 block # 32, count 7400 ... 7400 blocks written: OK
命令ide write用16进制参数,每个扇区数为512字节,0x20为第一个分区的起始扇区,扇区数计算方法为:3788800/512 = 7400 = 0x1CE8 。设置启动参数,将CF的分区作为仅读根设备,方法如下:
=> setenv cf_args setenv bootargs root=/dev/hda1 ro
=> setenv flash_cf 'run cf_args addip;bootm ${kernel_addr} - ${fdt_addr}'
=> setenv bootcmd run flash_cf
(6)根文件系统在FAT文件系统的一个仅读文件中
在用户嵌入式设备上,用户可使用CF、MMC或SD卡等通用存储介质存储Linux内核和根文件系统。用户可能期望Internet发布软件更新:客户可以从用户的网站下载更新文件,或者将映像文件通过Email发送给客户,客户可将文件拷贝到CF卡上,该CF卡仅有一个FAT或VFAT文件系统,不能直接用作Linux根文件系统,这需要进行一些处理,下面是其中的一种解决方法。
用户的发布软件由两个文件组成,第一个文件是Linux内核带有一个很小的ramdisk映像(U-Boot使用多个文件映像),U-boot能从FAT或VFAT文件系统装载和启动这些映像文件。第二个文件是根文件系统。为了方便和速度,这里使用ext2文件系统作为根文件系统。当Linux启动时,启动刚开始时,它使用附加的ramdisk作为根文件系统。在ramdisk中的程序会以仅读的方式挂接FAT或VFAT文件系统。接着,可使用loop设备将根文件系统与块设备联系起来。最后使用pivot_root切换根文件系统到CF卡上的映像。详细步骤说明如下:用前面制作仅读ext2根文件系统的方法制作ext2.img映像文件。
用前面制作ramdisk映像的方法制作初始ramdisk,需要加入空目录bin、dev、etc、lib、loopfs、mnt、proc和sysroot,加入文件bin/nash(脚本解释器)、linuxrc脚本和从sbin到bin的符号链接,列出如下:
drwxr-xr-x 2 wd users 4096 Apr 13 01:11 bin
-rwxr-xr-x 1 wd users 469512 Apr 11 22:47 bin/nash
drwxr-xr-x 2 wd users 4096 Apr 12 00:04 dev
drwxr-xr-x 2 wd users 4096 Apr 12 00:04 etc
drwxr-xr-x 2 wd users 4096 Apr 12 00:04 lib
-rwxr-xr-x 1 wd users 511 Apr 13 01:28 linuxrc
drwxr-xr-x 2 wd users 4096 Apr 12 00:04 loopfs
drwxr-xr-x 2 wd users 4096 Apr 12 00:09 mnt
drwxr-xr-x 2 wd users 4096 Apr 12 00:04 proc
lrwxrwxrwx 1 wd users 3 Jun 12 18:54 sbin -> bin
drwxr-xr-x 2 wd users 4096 Apr 12 00:04 sysroot还需要用于创建初始ramdisk的小规模设备表,列出如表3。
表3 创建初始ramdisk的小规模设备表nametypemodeuidgidmajorminorstartinccount
/devd75500-----
/dev/consolec6400051---
/dev/hdab6400030---
/dev/hdab6400031118
/dev/loopb6400070014
/dev/nullc6400013---
/dev/ramb6400010012
/dev/ramb6400011---
/dev/ttyc6400040014
/dev/ttyc6400050---
/dev/ttySc64000464014
/dev/zeroc6400015---创建初始ramdisk,方法如下:
$ INITRD_DIR=initrd
$ INITRD_SIZE=490
$ INITRD_FREE=0
$ INITRD_INODES=54
$ INITRD_DEVICES=initrd_devices.tab
$ INITRD_IMAGE=initrd.img
$ genext2fs -U \
-d ${INITRD_DIR} \
-D ${INITRD_DEVICES} \
-b ${INITRD_SIZE} \
-r ${INITRD_FREE} \
-i ${INITRD_INODES} \
${INITRD_IMAGE}
$ gzip -v9 ${INITRD_IMAGE}
结果产生了一个233KB的压缩的ramdisk映像。假定用户已编译了Linux内核映像,现可以用mkimage编译一个U-Boot多文件映像,将Linux内核与初始的ramdisk组合起来,方法如下:
$ LINUX_KERNEL=linuxppc_2_4_devel/arch/ppc/boot/images/vmlinux.gz
$ mkimage -A ppc -O Linux -T multi -C gzip \
> -n 'Linux with Pivot Root Helper' \
> -d ${LINUX_KERNEL}:${INITRD_IMAGE}.gz linux.img
Image Name: Linux with Pivot Root Helper
Created: Mon Jun 13 01:48:11 2005
Image Type: PowerPC Linux Multi-File Image (gzip compressed)
Data Size: 1020665 Bytes = 996.74 kB = 0.97 MB
Load Address: 0x00000000
Entry Point: 0x00000000
Contents:
Image 0: 782219 Bytes = 763 kB = 0 MB
Image 1: 238433 Bytes = 232 kB = 0 MB
新创建的linux.img是必须拷贝到CF卡中的第二个映像文件。linuxrc脚本用于完成根文件系统的设置,其内容列出如下:
#!/bin/nash #用nash脚本解释器解析此文件
echo Mounting /proc filesystem
mount -t proc /proc /proc #挂接/proc文件系统,用于查询一些需要信息
echo Creating block devices
mkdevices /dev #为列在/proc/partitions中的所有分区创建块设备条目
echo Creating root device
mkrootdev /dev/root #为新根文件系统创建块设备
echo 0x0100 > /proc/sys/kernel/real-root-dev
echo Mounting flash card
mount -o noatime -t vfat /dev/hda1 /mnt #挂接CF卡,假定它仅有一个分区在/dev/hda1上
echo losetup for filesystem image
#在CF卡上的根文件系统映像名为rootfs.img,使用用loop设备挂接
losetup /dev/loop0 /mnt/rootfs.img
echo Mounting root filesystem image
mount -o defaults --ro -t ext2 /dev/loop0 /sysroot
echo Running pivot_root
pivot_root /sysroot /sysroot/initrd #将/sysroot作为新的根文件系统
上面的脚本有点小缺陷:由于CF卡挂接在ramdisk中一个目录下,以便能够从根文件系统映像访问。这意味着用户不能卸载(umount)CF卡,阻止用户释放初始ramdisk的空间,将永久失去450kb左右的RAM(用于ramdisk)。首先,在目标板上拷贝2个映像文件到CF卡,方法如下:
bash-2.05b# fdisk -l /dev/hda
Disk /dev/hda: 256 MB, 256376832 bytes
16 heads, 32 sectors/track, 978 cylinders
Units = cylinders of 512 * 512 = 262144 bytes
Device Boot Start End Blocks Id System
/dev/hda1 * 1 978 250352 6 FAT16
bash-2.05b# mkfs.vfat /dev/hda1
mkfs.vfat 2.8 (28 Feb 2001)
bash-2.05b# mount -t vfat /dev/hda1 /mnt
bash-2.05b# cp -v linux.img rootfs.img /mnt/
`linux.img' -> `/mnt/linux.img'
`rootfs.img' -> `/mnt/rootfs.img'
bash-2.05b# ls -l /mnt
total 4700
-rwxr--r-- 1 root root 1020729 Jun 14 05:36 linux.img
-rwxr--r-- 1 root root 3788800 Jun 14 05:36 rootfs.img
bash-2.05b# umount /mnt现准备用U-Boot从CF卡装载uMulti文件(将Linux内核和初始ramdisk组合在一起),并启动它,方法如下:
=> setenv fat_args setenv bootargs rw
=> setenv fat_boot 'run fat_args addip;fatload ide 0:1 200000 linux.img;bootm'
=> setenv bootcmd run fat_boot