19.6 Flash文件系统的建立
19.6.1 Flash 转换层
由于无法重复地在 Flash 的同一块存储位置做写入操作(必须事先擦除该块后才能再写入),因此一般在硬盘上使用的文件系统,如 VFAT、NTFS、EXT2、EXT3 等将无法直接用在 Flash 上,为了沿用这些文件系统,必须透过一层转换层(Translation Layer)来将逻辑块地址(Logical Block Address)对应到 Flash存储器的物理位置,使系统能把 Flash 当作普通的硬盘一样处理,称这层为 FTL(Flash TranslationLayer)。FTL 应用于 NOR Flash,而 NFTL 则应用于NAND Flash,如图 19.8 所示。
图19.8 FTL 和 NFTL
一个闪存转换层的最简单的实现就是将模拟的块设备一对一地映射到闪存上。举例来说,当上层的文件系统要写一个块设备的扇区时,闪存转换层要做下面的操作来完成这个写请求。
(1)将这个扇区所在擦除块的数据读到内存中,放在缓存中。
(2)将缓存中与这个扇区对应的内容用新的内容替换。
(3)对该擦除块执行擦除操作。
(4)将缓冲中的数据写回该擦除块。
这种实现方式的缺点如下。
效率低,对一个扇区的更新要重写整个擦除块上的数据,造成数据带宽很大的浪费。可行的办法是只有当文件系统的写请求超过了一个擦除块的边界时,才去执行对闪存的擦除、写回操作。
没有提供磨损平衡,那些被频繁更新的数据所在擦除块将首先变成坏块。
非常不安全,很容易引起数据的丢失。如果在上面的第(3)步和第(4)步之间发生了突然掉电,那么整个擦除块中的数据就全部丢失了。
为了解决上面这种实现方式的问题,闪存转换层不能只是简单地实现块设备与闪存的一一映射,它还需要将模拟块设备的扇区存储在闪存的不同位置,并且维持扇区到闪存的映射关系。为了进行垃圾回收(Garbage Collection),闪存转换层必须能理解上层文件系统的语义。这样实现导致的最大问题就是效率不高,具体来说,闪存转换层为了能理解上层文件系统的语义,必须对文件系统的每个写请求进行解析,因此导致写操作的性能下降。另外,从软件的架构上来讲,要求文件系统下面的一层去理解文件系统的语义,也不太合理。因此,在 Flash 上,应尽可能地避免使用传统的依赖闪存转换层的文件系统,最好应采用专门的针对 Flash 的文件系统。
19.6.2 CramFS
在嵌入式 Linux 中,大多会采用 RAMDISK 来储存文件系统的内容,RAMDISK 的含义是在启动时,把一部份内存虚拟成磁盘,并且把之前准备好的文件系统映像文件解压缩到该RAMDISK 中。假设压缩后的文件系统映像为 8MB,存放于 Flash,解压缩后为 16MB,如果采用 RAMDISK,将需要 8MB 的 Flash 和 16MB 的 RAM 空间,而采用 CramFS 后,就不再需要消耗 16MB 的 RAM 空间。
CramFS 的源代码位于linux/fs/cramfs 中,CramFS 是一种压缩的只读文件系统,当浏览 Flash 中的目录或读取文件时,CramFS 文件系统会动态地计算出压缩后的数据所储存的位置,并实时地解压缩到内存中,对用户来说,使用 CramFS 与 RAMDISK 感觉不到使用上的差异性。
CramFS 工具的下载地址为 http://sourceforge.net/projects/cramfs/,通过如下命令创建CramFS 文件系统映像:
mkcramfs my_cramfs/ cramfs.img (my_cramfs 是要创建映像的目录)
如下命令将生成的 cramfs.img 映像复制到 Flash 的第一个分区并 mount 到/mnt/nor 目录:
cp cramfs.img /dev/mtd1
mount -cramfs /dev/mtdblock1 /mnt/nor
很多时候,工程中需要基于已有的文件系统映像添加、删除一些文件后建立新的文件系统映像,这时并不需要完全重新操作,可用如下的方法。
(1)将映像以 loop 方式挂载到某目录。
mkdir tmpdir
mount rootfs.cramfs tmpdir -o loop
cd tmpdir
(2)压缩被挂载的文件系统。
tar -cvf ../rootfs.tar ./ 将 tmpdir 中的内容打包放在其父目录下
umount tmpdir
(3)解压缩文件系统到新目录。
mkdir rootfs
tar -xvf rootfs.tar -C rootfs
(4)修改新目录(这里是 rootfs)中的内容,以符合新的需要。
(5)重新创建映像文件。
mkcramfs rootfs rootfs.cramfs
19.6.3 JFFS/JFFS2
JFFS 是由瑞典 Axis Communications AB 公司开发的,于 1999 年末基于 GNU GPL 发布的文件系统。最初的发布版本基于 Linux 2.0,后来 Red Hat 将JFFS移植到 Linux 2.2,在使用的过程中,JFFS 设计中的局限被不断地暴露出来。在 2001 年初,Red Hat 决定实现一个新的 JFFS2。
JFFS2 是一个日志结构的文件系统,JFFS2 在闪存上顺序地存储包含数据和原数据(meta-data)的节点。JFFS2 的日志结构存储方式使得它能对闪存进行 out-of-place(不合适的) 更新,而不是磁盘所采用的
in-place(合适的) 更新方式。JFFS2提供的垃圾回收机制,使得不需要马上对擦写越界的块进行擦写,而只需要对其设置一个标志,标明为“脏”块。当可用的块数不足时,垃圾回收机制才开始回收这些节点。同时,由于 JFFS2 基于日志结构,在意外掉电后仍然可以保持数据的完整性,而不会丢失数据。因此,JFFS2 成为目前 Flash 上应用最广泛的文件系统。
JFFS2 挂载时需要扫描整块 Flash 以确定节点的合法性以及建立必要的数据结构,这使得 JFFS2 挂载时间比较长。又由于 JFFS2 将节点信息保存在内存中,使得 JFFS2 所占用的内存量和节点数目成正比。再者,由于 JFFS2 通过随机方式来实现磨损平衡,JFFS2 不能保证磨损平衡的确定性。于是提出JFFS3,为了解决 JFFS2 的这些缺陷而设计。
制作 JFFS2 文件系统的工具 mkfs.jffs2(包含在 mtd-utils 中),执行如下命令即可生成所要的映象:
./mkfs.jffs2 -d my_jffs2/ -o jffs2.img (my_jffs2 是要制作映像的目录)
使用 mkfs.jffs2 制作映像时,要注意指定正确的擦除块大小和页面大小,对于 NANDFlash,应使用
“-n”去掉生成映像中的 clean marker。
接下来将 jffs2.img 复制到 Flash 第 1 个分区(复制到 MTD 字符设备),如下所示:
cp jffs2.img /dev/mtd1
之后,就可以将对应的块设备 mount 到 Linux 的目录了,如下所示:
mount -t jffs2 /dev/mtdblock1 /mnt/nor
对于 NAND Flash,应使用 mtd-utils 中的 nandwrite 工具进行 jffs2 映像向 NAND Flash 的烧录,烧录前可以使用 flash_eraseall 擦除 Flash,并在 OOB 区域加上 JFFS2 需要的 clean marker。
19.6.4 YAFFS/YAFFS2
YAFFS(Yet Another Flash File System)文件系统是专门针对 NAND闪存设计的嵌入式文件系统,有 YAFFS 和 YAFFS2 两个版本,两个版本的主要区别之一在于 YAFFS2 能够更好地支持大容量的 NAND Flash 芯片,YAFFS 只针对页大小为 512 字节的NAND。
YAFFS 文件系统有些类似于 JFFS/JFFS2 文件系统,与之不同的是 JFFS1/2 文件系统最初是针对 NOR Flash 的应用场合设计的,而 NOR Flash 和 NAND Flash 本质上有较大的区别,所以尽管 JFFS1/2 文件系统也能应用于 NAND Flash,但由于它在内存占用和启动时间方面针对NOR 的特性做了一些取舍,所以对 NAND 来说通常并不是最优的方案。NAND 上的每一页数据都有额外的空间用来存储附加信息,YAFFS 正好利用了该空间中一部分来存储文件系统相关的内容。
YAFFS 和 JFFS 都提供写均衡、垃圾收集等底层操作,它们的不同之处如下。
JFFS 是一种日志文件系统,通过日志机制保证文件系统的稳定性。YAFFS 仅仅借鉴日志系统的思想,不提供日志机能,所以稳定性不如 JFFS,但资源占用少。
JFFS 中使用多级链表管理需要回收的脏块,并且使用系统生成伪随机变量决定要回收的块,通过这种方法能提供较好的写均衡,在 YAFFS 中是从头到尾对块搜索,所以在垃圾收集上 JFFS 的速度慢,但是能延长 NAND 的寿命。
JFFS 支持文件压缩,适合存储容量较小的系统;YAFFS 不支持压缩,更适合存储容量较大的系统。
YAFFS 还带有 NAND 芯片驱动,并为嵌入式系统提供了直接访问文件系统的 API,用户可以不使用 Linux 中的 MTD 和 VFS,直接对文件进行操作(如图 19.8)。尽管如此,NAND Flash 大多采用 MTD+YAFFS 的模式。
YAFFS 使用 OOB 组织文件结构信息,而 JFFS 直接将节点信息保存在 NAND 数据区域里面,因此 YAFFS 在 mount 时只需读取 OOB,其 mount 时间远小于 JFFS。
在 Linux 2.6 内核,YAFFS 文件系统的移植,主要包括以下工作。
(1)复制 YAFFS 源代码到 Linux 源码树。
只需要在内核中建立 YAFFS 目录 fs/yaffs,并把下载的 YAFFS 代码复制到该目录下面,下载的YAFFS 源代码已包含Kconfig 和 Makefile,只需要修改 fs 目录下的 Kconfig 和 Makefile 并引用fs/yaffs 中的对应文件即可,方法是:在 fs/Kconfig 中增加 source "fs/yaffs/Kconfig";在 fs/Makefile 中增加 obj-$(CONFIG_YAFFS_FS) += yaffs/。
(2)配置内核编译选项。
在采用 make menuconfig 等方式配置内核编译选项时,除了应该选中 MTD 系统及目标板上Flash 的驱动以外,也必须选中对 YAFFS 的支持,如下所示:
File systems --→
Miscellaneous filesystems --→
<*> Yet Another Flash Filing System(YAFFS) file system support
[*] NAND mtd support
[*] Use ECC functions of the generic MTD-NAND driver
[*] Use Linux file caching layer
[*] Turn off debug chunk erase check
[*] Cache short names in RAM
(3)挂载 YAFFS。
YAFFS 源代码包的 utils 目录下包含mkyaffs 工具,可以用它来格式化 Flash,
例如运行 mkyaffs /dev/mtd3 将用 YAFFS 文件系统格式化 NAND 的第 3 个分区,之后可以运行如下命令挂载 YAFFS:
mount –t yaffs(type) /dev/mtdblock3(设备) /mnt/nand(挂载点)
此后,对/mnt/nand 的操作就是对/dev/mtdblock3 的操作。
如果运行如下命令将根文件系统复制到/mnt/Flash0:
mount –t yaffs /dev/mtdblock3 /mnt/nand
cp our_rootfs /mnt/Flash0
umount /mnt/nand
则重新启动,并修改 Linux 启动参数中 root 为:
param set linux_cmd_line "noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttyS0"
之后就可以直接以/dev/mtdblock3 中的根文件系统启动了。
19.6.5 UBI/UBIFS
UBIFS 是由 Thomas Gleixner,Artem Bityutskiy 等人于 2006 年发起,致力于开发性能卓越、扩展性高的 Flash 专用文件系统。UBI(unsorted block images)是一种类似于 LVM(逻辑卷管理器) 的逻辑卷管理层,主要实现损益均衡,逻辑擦除块、卷管理和坏块管理等,而 UBIFS 则是基于 UBI的 Flash 日志文件系统。UBIFS 而是工作于 UBI 卷之上,这是UBIFS 与JFFS2、YAFFS2 的一个显著区别。
使用 UBIFS,需要在配置内核时使能如下选项:
下面给出一个使用制作、烧录和使用 UBIFS 的过程。
(1)在 PC 上通过 mtd-utils 制作 UBI 映像:
mkfs.ubifs -r rootfs -m 2048 -e 129024 -c 4096 -o ubifs.img
ubinize -o ubi.img -m 2048 -s 512 -p 128KiB ubifs.conf
rootfs 为要制作的根文件系统的目录。
(2)在目标机上烧录映像:
ubiformat /dev/mtd1 -s 512 -f ubi.img
(3)通过 ubiattach 关联 MTD UBI:
ubiattach /dev/ubi_ctrl -m 1
UBI: attaching mtd1 to ubi0
UBI: physical eraseblock size: 131072 bytes (128 KiB)
UBI: logical eraseblock size: 129024 bytes
UBI: smallest flash I/O unit: 2048
UBI: sub-page size: 512
UBI: VID header offset: 512 (aligned 512)
UBI: data offset: 2048
UBI: volume 0 ("rootfs") re-sized from 17 to 979 LEBs
UBI: attached mtd1 to ubi0
UBI: MTD device name: "file system(nand)"
UBI: MTD device size: 124 MiB
UBI: number of good PEBs: 992
UBI: number of bad PEBs: 0
UBI: max. allowed volumes: 128
UBI: wear-leveling threshold: 4096
UBI: number of internal volumes: 1
UBI: number of user volumes: 1
UBI: available PEBs: 0
UBI: total number of reserved PEBs: 992
UBI: number of PEBs reserved for bad PEB handling: 9
UBI: max/mean erase counter: 0/0
UBI: image sequence number: 0
UBI: background thread "ubi_bgt0d" started, PID 179
UBI device number 0, total 992 LEBs (127991808 bytes, 122.1 MiB), available 0 LEBs (0bytes), LEB size 129024 bytes (126.0 KiB)
(4)挂载 UBIFS:
mount -t ubifs ubi0:rootfs /mnt
UBIFS: mounted UBI device 0, volume 0, name "rootfs"
UBIFS: file system size: 124895232 bytes (121968 KiB, 119 MiB, 968 LEBs)
UBIFS: journal size: 9033728 bytes (8822 KiB, 8 MiB, 71 LEBs)
UBIFS: media format: w4/r0 (latest is w4/r0)
UBIFS: default compressor: lzo
UBIFS: reserved for root: 0 bytes (0 KiB)
(5)通过 mount 和 ubinfo 命令查看结果: