我们需要为嵌入式设备选择文件系统。很多的文件系统支持在线升级,包括增删、告警,否则在升级的时候,需要重新烧img,这通常用于内容很少或者任务简单的情况(也就是不适合MID)。能够进行数据的保留,有些文件系统将所有的数据和元数据防止在RAM中,这样在掉电的时候将无法获得数据的保留,例如/proc和/sys,用于保留临时或者实时的信息。嵌入式操作系统的一大特性是通常没有通过常规的系统关闭操作而是直接按power开关,我们需要保证在这种情况下文件系统不会现崩溃。有些文件系统对他们的数据和元数据引入掉电可靠性支持,通常是journaling(日志)方式,在改动的时候,在相关的改动写入log,只有在transaction完成的时候才将log删除,如果出现掉电,在重开的情况下,能够进行恢复。有物理日志方式(将逐字copy block)和逻辑日志方式(以某种方式保存改动信息),物理方式会引起两次写操作,而flash的写次数是有限的,会严重影响寿命,即使逻辑方式也通常会造成两次写操作。另一种方式是采用logstructured文件系统,他将所有的数据和元数据保留在一个log结构中,每个写操作在log中通过置前一个log无效作为一个坏的或者复原的事务,他和物理日志方式很相似,但是由于log就包含文件系统的数据,所以不需要double-write。文件系统可以提供压缩功能,不需要通过上次那个硬件,而是文件系统本身以block为单位进行压缩和解压。文件系统的特性依赖于存贮的类型。文件系统可以提供 wear leveling的技术尽可能地均匀使用block。
Linux支持超过50种文件系统,但是只有少数在嵌入式OS中常用。
Ext2文件系统:和Ext3比较不支持下电保护和压缩,快速,能支持16-32TB的存储容量,看用于RAM disk,IDE模式下的CF,以及只读(或者基本只读)的NAND和NOR(通过MTD或者FTL/NFTL)。对于需要写永久数据的数据的情况不建议使用ext2。可以使用mke2fs命令进行格式化,下面是格式化,挂载和copy的例子 :
#mke2fs /dev/nftla1
#mkdir /mnt/doc
#mount –t ext2 /dev/nftla1 /mnt/doc
cp –a rootfs/* /mnt/doc 如果不能直接在host获得存储设备,可以同NFS的方式。也可以将文件系统的数据做成二进制的img文件,通过JTAG链接器等方式写入存贮设备,通过gennext2fs来制作img。可以从http://genext2fs.sourceforge.net下载,./configure后,make , make check , make install。使用例子:
$ genext2fs -b 1024 -d src -D device_table.txt -e 0 flashdisk.img(不需要使用root权限)
Ext3文件系统:只journal元数据,以此妥协是的保证写的速度,可以在mount中加上采参数data=journal来打开数据的日志功能。还有一个选项是noatime,不更新最后回去的日期,虽然不兼容POSIX,但是可以提供闪存的寿命已经更好的I/O吞吐量。ext3建议用于IDE模式下的CF,以及通过FTL/NFTL的NAND/NOR。可以通过mke2fs进行格式化,增加-f参数。
Cramfs文件系统:是个简单,有时过分简单的,压缩的,只读的文件系统。除了可以用于MTD下只读并且极少更新img的NOR/NAND,IDE的CF,FTL/NFTL的NOR/NAND,可作为RAM disk的IMG存储。由于简单,Cramfs有一些限制,例如最大支持16M,没有当前(.)和父(..)目录,页大小固定为4096,gid(8位)最大为255,所有的链接文件计数都是1等等。如果想不受这些现实,可以考虑Squashfs文件系统。
Squashfs文件系统:是压缩的只读的fs,保留了Cramfs很多优点,去掉它的限制。对于存储空间和内存有限,只读的嵌入式操作系统适合。在2008年,linux kernel还不支持Squashfs,需要打补丁,并在配置中选取FileSystem->Miscellaneouse Systems->Squashed filesystem。除非使用RAM disk,都可以选择kernle module的方式。
JFFS2文件系统:可写、保留数据、压缩、掉电保护,提供wear leveling,适合板载NOR、NAND和Doc。JFFS2提供垃圾回收机制,通常可以很好地工作。但是当文件系统接近极限或者要求更新一个非常大的文件,垃圾回收的时间会很长,将会延迟文件系统的操作,这会对没有考虑到这种情况的某些实时要求的软件带来负面的影响。当空间满的时候,尝试更新或者阶段文件内容会被提示文件系统已满并失败,即使看起来不要求添加存储block,这是因为日志结构文件系统,需要在log中增加log。在使用的时候,我们应保证存储空间的充足,上面的应用能够容忍时延(避免快满时候垃圾回收的消耗影响)。 JFFS2用途很广,但是对于现在容量巨大的闪存不适合,新的UBI文件系统出现。JFFS2使用MTD工具:
$ mkfs.jffs2 –r ottfs/ –o images/rootfs-jffs2.img –e 128kiB 其中128是指擦写block的大小,如果超过实际闪存的大小,会引起文件系统的崩溃,如果太小,block内有部分存储空间没能利用。
# sumtool –i rootfs-jffs2.img –o rootfs-jffs2-summed.img –e 128KiB 增减erase block的summary node。
YAFFS2文件系统:是可写,下电保护的文件系统,广泛引用于Linux和RTOSeS,但是没有在Linux的mainline中提供,需要在kernel中打补丁。
Tmpfs文件系统:是一个存放在虚拟memory的文件系统,不提供永久保存,适合于存储临时的数据,例如/tmp,用于Linux的page cache和dentry chache。
RAM disk,在RAM中,而作为一个block设备,内核可以同时支持多个RAM disk。通常用于保存磁盘文件系统中压缩的img。这在嵌入式操作系统的初始化中使用,kernel可以从存储设备中提取initial RAM disk(initrd)img作为的他的根文件系统。一开始,kernel根据boot的参数是否表面intrid,如果是,他将从特定的存贮介质提取文件系统的img并放入RAM disk,同时mount为根文件系统。initrd的机制是以最简单的方式提供一个带根文件系统的内核在RAM中。对于新的系统,推荐使用Initramfs机制。
$ genext2fs -b 1024 -d rootfs -D device_table.txt -e 0 initrd.img
$ gzip -9 < imges/initrd.img > images/intrd.bin
Linux 2.6将它的init程序存储在一个压缩的文档中(以CPIO的格式),boot将文档抽取到Tmpfs文件系统的一个指定的实例。提取后,kenrel或在根文件系统下尝试定位init程序,如果存在,由其负责后续的系统建立(可能包括mount另外一个根文件系统在Rootfs上)如果init程序不存在,内核将执行在initrd机制或者kernel command-line root参数中的older code。我们需要在kernel的配置中,选中CONFIG_INTIRAMFS_SOURCE,是的将Initramfs文档作为hardcode加入到kernel的二进制中。
# a comment file name location mode uid gid dir name mode uid gid nod name mode uid gid dev_type maj min slink name target mode uid gid pipe name mode uid gid sock name mode uid gid
kernel build是自动生成并提取CPIO的,当然也可以自己建立。如果进行kernel编译的时候配置支持initial RAM disk support(initrd),一个外加的压缩的CPIO文档可以被linux kernel使用替代RAM disk。kernel将检查出Initrd是Initramfs文档而不是文件系统img。因为Initramfs使用linux的dentry cache和page cache来存储文件和元数据内容,因此比RAM disk更为高效,这也就是为什么对于新的系统推荐使用Initramfs的缘由。
我们需要根据应用的特性和闪存的特点来规划文件系统。例如:对于只读的,只在更新软件的时候才有写的操作,可以使用Tmpfs,可以使用可读的永久存储,例如Cramfs,Squashfs,也可以使用可写的永久存贮,但在mount的时候将权限设置为可读(这种方式不推荐)。对于嵌入式操作系统,有动态配置文件或者数据,例如网络配置,logfile,license信息,这需要可写,可使用Ext3、YAFFS2(具有wear leveling)或者JFFS2。对于临时的信息,例如我们在配置cisco路由器中的running-config看使用Tmpfs。Linux支持在一个介质上不同分区使用不同的文件系统。例如一个嵌入式操作系统,使用CF在NFTL下,可以有三个部分,一个是只读的SquashFS,存放压缩的根文件系统,一个是可写的Ext3,存放用户和配置数据,还有一个是在RAM中的可读写的Tmpfs。
我们在设计的时候应当考虑如何进行软件升级。一种是建议在实验室环境下的升级(Not-Fail-Safe),一种是自动升级(Fail-Safe)例如手机版本升级。
“Not-Fail-Safe”:对于在RAM中执行的文件系统,可以通过重写新的版本以及CPIO文档就可以,但是掉电或者新版本有问题时,系统将无法启动。 对于可写可永久保存可连上网的设备,可以使用rsync工具,通过rsh或者ssh,同步客户端(target)和服务器(server)之间的文件,保留文件的权限、链接等等,除了要求有一台运行rsync后台服务的服务器外,终端设备也需要安装rsync。如果我们选择ssh作为传输的协议,我们需要在target上安装ssh(Openssh)。 一般来讲我们不需要更新整个文件系统或者某个目录,可像工作站那样更新package。Busy-box提供dpgk工具,看安装dpkg格式的包,另外还可以使用iPGK(在http://www.handheld.org/z/wiki/iPKG)下载。但这些方式,都不能对升级过程中出现掉电现象进行保护。
“Fail-Safe”:设备存在多个文件系统,如果升级成功,使用新的文件系统,否则使用旧的。由于linux软件包括可执行的多个文件、共享的lib,kernel模块等,他们之间互相作用,因此我们不能逐个文件升级,而是升级整个系统。当成功下载后(通过了CRC校验),更改数据结构,高数bootloader在启动时使用那个软件。修改配置中,使用rename这个原子操作来覆盖旧的配置。还可以增加一种安全保护(觉得是另一种实现方式):bootloader生成一个特别的record,当完全升级成功后,将删除这个record(原子操作)。如果bootloader在启动的时候,发现这个record,说明升级没有成功,可以选择启动另外的软件,使得可以使用旧的版本。O’Reilly举例一个例子,预先学习一下如何装一个嵌入式操作系统,具体的在下一章节会讲。
- 使用U-Boot命令mkimage,将vmlinux.gz以及initramfs_data.cpio.data生成runtime.img
- 将这个img通过mkfs.jffs2按JFFS2的格式进行处理,得image.jffs2
- 修改U-Boot的配置文件的CONFIG_BOOTCOMMAND "fsload a2000000 runtime.img; ootm a2000000”
- 将U-Boot的binary和image.jffs2写入闪存
- 启动的时候,U-Boot会从JFFS2的分区中获得kernel img(通过mount来获得分区上的内容)
- 如果更新的时候,同上,我们有一个runtime2.img的写入闪存
- 通过setenv或者fw_setenv来引导新的启动
这里面没有什么实验,包括编写CPIO文件,但是文章说通过在kernel的配置,可以将CPIO作为hardcode写入kernel中,也就是说不需要第一步。而我看到在moblin给的例子是使用initrd的方式。我想在下一章节中,我们可以boot一个我们自己的系统。期待中……