liwen01 2024.07.07
yaffs 是专为nand flash 设计的一款文件系统,与jffs 类似,都是属于日志结构文件系统。与jffs 不同的是,yaffs 文件系统利用了nand flash 一些特有属性,所以在数据读写擦除和回收上都有较大的差异。
关于jffs2文件系统的介绍可以查看《文件系统(八):Linux JFFS2文件系统工作原理、优势与局限》
这里先介绍一下nand flash的一些基础知识,有助于后面理解yaffs的设计原理。
flash分为nor flash和nand flash两类:
nor flash: 成本较高,容量较小,优点是读写数据不容易出错,比较适用于存储关键数据,比如程序固件、配置参数等。
nand flash :成本较低,相对便宜,容量较大,但是数据比较容易出错,所以一般都需要有对应的软件或者硬件的校验算法(ECC),比较适合用来储存大容量且数据安全要求不是非常严格的数据,比如照片、视频等。
nand flash数据存储单元从概念上来说,由大到小有:
Nand Flash(Package) -> Chip(Die) -> Plane -> Block -> Page(Chunk) -> OOB(Spare data)
其中有些存储单元,在一些不同的资料上它们的叫法不太一样,比如page(页),、有些资料上介绍的是Chunk,在有些软件编程中,也有可能被介绍为扇区sector
Nand Flash:也叫Package,这是我们在PCBA上看到的已经封装好的整科芯片,带有封装有IO引脚,可以直接焊接到PCB上使用。
Chip:也叫Die(裸片),这是独立的硅片,包含存储单元和控制电路,一个Package 中可以包含多个Die。
Plane : Plane是die内部的一个逻辑分区。每个die通常被划分为多个plane,以实现并行操作。每个plane有独立的寄存器和数据缓存,因此可以同时进行多个操作(如读取、写入、擦除),从而提高性能。
Block :NAND Flash存储的基本单位。
Page :也叫chunk,NAND Flash中最小的可编程单元。
OOB(Out-Of-Band) :也叫Spare data,OOB区域是每个page中额外的存储空间,用于存储元数据,例如错误校正码(ECC)、坏块标记和其他管理信息.
nand flash 有一些特殊的属性,也是因为这些特殊的属性才有了yaffs文件系统的特殊设计
结合nand flash的特性,从应用软件编程的角度来看,整个nand flash空间是由各page(chunk)组成,每个page(chunk)后面跟随一个与之对应的oob.
不同型号不同厂家生产的nand flash,它的block、page、oob等大小有可能不一样,在软件开发或是制作yaffs文件系统时,首先需要确认nand flash的参数。
yaffs 有两个版本,yaffs1与yaffs2,主要区别是yaffs2可以支持比512Byte更大的chunk。它发布于2003年,比jffs2晚一两年被设计,但距今也二十多年了。
下面内容,yaffs 是代指yaffs1和yaffs2。关于yaffs文件系统的详细介绍,可以从官方网站下载到最新的代码和说明文档:https://www.aleph1.co.uk/gitweb/
biao@ubuntu:~/test/yaffs/yaffs2_fs$ tree
.
├── test1
│ └── file1
├── test2
│ └── file2
├── test3
│ └── file3
└── test4
└── file4
4 directories, 4 files
在制作成yaffs2镜像文件之前,4个目录和文件的大小如下:
biao@ubuntu:~/test/yaffs$ du yaffs2_fs
8 yaffs2_fs/test3
8 yaffs2_fs/test2
8 yaffs2_fs/test1
8 yaffs2_fs/test4
36 yaffs2_fs
biao@ubuntu:~/test/yaffs$
biao@ubuntu:~/test/yaffs$ ./mkyaffs2image yaffs2_fs yaffs2_fs.img
mkyaffs2image: image building tool for YAFFS2 built Jul 7 2024
Processing directory yaffs2_fs into image file yaffs2_fs.img
Object 257, yaffs2_fs/test3 is a directory
Object 258, yaffs2_fs/test3/file3 is a file, 1 data chunks written
Object 259, yaffs2_fs/test2 is a directory
Object 260, yaffs2_fs/test2/file2 is a file, 1 data chunks written
Object 261, yaffs2_fs/test1 is a directory
Object 262, yaffs2_fs/test1/file1 is a file, 1 data chunks written
Object 263, yaffs2_fs/test4 is a directory
Object 264, yaffs2_fs/test4/file4 is a file, 1 data chunks written
Operation complete.
16 objects in 5 directories
12 NAND pages
biao@ubuntu:~/test/yaffs$
查看yaffs2_fs.img镜像文件信息:
biao@ubuntu:~/test/yaffs$ stat yaffs2_fs.img
File: yaffs2_fs.img
Size: 135168 Blocks: 264 IO Block: 4096 regular file
Device: 801h/2049d Inode: 7874075 Links: 1
Access: (0600/-rw-------) Uid: ( 1000/ biao) Gid: ( 1000/ biao)
Access: 2024-07-07 23:12:18.195919283 +0800
Modify: 2024-07-07 23:10:19.798582920 +0800
Change: 2024-07-07 23:10:19.798582920 +0800
Birth: -
biao@ubuntu:~/test/yaffs$
从yaffs2_fs.img镜像文件中我们看到,打包后的镜像文件比我们原来的目录文件要大很多,打包前是36KByte,打包后是132KByte,这是为什么呢?
使用hexdunp命令直接查看yaffs2_fs.img镜像文件数据:
biao@ubuntu:~/test/yaffs$ hexdump -C yaffs2_fs.img
00000000 03 00 00 00 01 00 00 00 ff ff 74 65 73 74 33 00 |..........test3.|
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
.........
.........
*
00000840 01 00 00 00 01 01 00 00 ff ff 66 69 6c 65 33 00 |..........file3.|
00000850 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000940 00 00 00 00 00 00 00 00 00 00 ff ff b4 81 00 00 |................|
00000950 e8 03 00 00 e8 03 00 00 f4 45 85 66 7e e5 70 66 |.........E.f~.pf|
00000960 43 45 85 66 1d 00 00 00 ff ff ff ff ff ff ff ff |CE.f............|
00000970 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
*
.........
.........
*
00001080 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 |cccccccccccccccc|
00001090 63 63 63 63 63 63 63 63 63 63 63 63 0a ff ff ff |cccccccccccc....|
000010a0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
*
.........
.........
从hex数据中我们可以直观的看到文件名信息和文件里面的数据,也就是说文件名和文件里面的数据都是未压缩的。
我们对mkyaffsimage.c的源码进行分析,在默认参数下mkyaffsimage打包的镜像文件,它的chunk、spare、block大小信息如下:
#define chunkSize 2048
#define spareSize 64
#define pagesPerBlock 64
yaffs2的镜像文件是由object_header、data、yaffs_spare 三个部分组成,每个object_header、data 至少占用一个chunk,yaffs_spare 实际上也就是oob数据,是存储在spare空间。
我们对上面yaffs2_fs.img的镜像文件进行分析,先看最开始的数据,是test3目录obj
00000000 03 00 00 00 01 00 00 00 ff ff 74 65 73 74 33 00 |..........test3.|
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000100 00 00 00 00 00 00 00 00 00 00 ff ff fd 41 00 00 |.............A..|
00000110 e8 03 00 00 e8 03 00 00 f4 45 85 66 7e e5 70 66 |.........E.f~.pf|
00000120 43 45 85 66 ff ff ff ff ff ff ff ff ff ff ff ff |CE.f............|
00000130 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
*
000001c0 ff ff ff ff ff ff ff ff ff ff ff ff 00 00 00 00 |................|
000001d0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
*
00000800 00 10 00 00 01 01 00 00 00 00 00 00 ff ff 00 00 |................|
00000810 25 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff |%...............|
00000820 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
*
0~0x800 地址的数据是object_header数据结构,后面是oob的数据结构,详细解析数据如下:
下面数据是file3的数据结构
00000840 01 00 00 00 01 01 00 00 ff ff 66 69 6c 65 33 00 |..........file3.|
00000850 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000940 00 00 00 00 00 00 00 00 00 00 ff ff b4 81 00 00 |................|
00000950 e8 03 00 00 e8 03 00 00 f4 45 85 66 7e e5 70 66 |.........E.f~.pf|
00000960 43 45 85 66 1d 00 00 00 ff ff ff ff ff ff ff ff |CE.f............|
00000970 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
*
00000a00 ff ff ff ff ff ff ff ff ff ff ff ff 00 00 00 00 |................|
00000a10 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
*
00000a30 00 00 00 00 ff ff ff ff ff ff ff ff ff ff ff ff |................|
00000a40 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
*
00001040 00 10 00 00 02 01 00 00 00 00 00 00 ff ff 00 00 |................|
00001050 26 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff |&...............|
00001060 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
*
00001080 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 |cccccccccccccccc|
00001090 63 63 63 63 63 63 63 63 63 63 63 63 0a ff ff ff |cccccccccccc....|
000010a0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
*
00001880 00 10 00 00 02 01 00 00 01 00 00 00 1d 00 00 00 |................|
00001890 00 00 00 00 08 00 00 00 08 00 00 00 ff ff ff ff |................|
000018a0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
*
file3是一个文件,其中包括2个chunk:一个是Object,另外一个是data,其中每个chunk后面有一个与之对应的oob
与目录相比,文件有file_size_low,chunk_id,还有data chunk。我们看file3实际数据:
biao@ubuntu:~/test/yaffs$ stat yaffs2_fs/test3/file3
File: yaffs2_fs/test3/file3
Size: 29 Blocks: 8 IO Block: 4096 regular file
Device: 801h/2049d Inode: 7874095 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1000/ biao) Gid: ( 1000/ biao)
Access: 2024-07-07 23:57:37.355671911 +0800
Modify: 2024-07-07 23:40:14.962499985 +0800
Change: 2024-07-07 23:34:11.067767029 +0800
Birth: -
biao@ubuntu:~/test/yaffs$ cat yaffs2_fs/test3/file3
cccccccccccccccccccccccccccc
biao@ubuntu:~/test/yaffs$
对比发现data chunk中存储的数据,就是file3文件里面的实际数据。
上面我们分析了目录和文件obj的数据结构,实际yaffs还支持其它的文件类型:
enum yaffs_obj_type {
YAFFS_OBJECT_TYPE_UNKNOWN,
YAFFS_OBJECT_TYPE_FILE,
YAFFS_OBJECT_TYPE_SYMLINK,
YAFFS_OBJECT_TYPE_DIRECTORY,
YAFFS_OBJECT_TYPE_HARDLINK,
YAFFS_OBJECT_TYPE_SPECIAL
};
从obj 类型结构体中我们可以看到,还支持软连接、硬连接和特殊文件类型。它们与常规的文件、目录一样,都有object_header 结构,关键的元数据信息都是存储在oob中。
实际yaffs文件系统在挂载的时候,并不需要像jffs2一样扫描整个flash空间。在yaffs文件系统中,只需要先扫描oob里面的数据就可以构建出文件、目录与chunk之间的关系,再结合object_header信息就可以构建出整个文件系统的信息。所以yaffs2在同等大小的文件系统中,挂载速度是会比jffs2快的。
回顾我们前面介绍的nand flash特性:
对于我们上面介绍的file3文件,如果我们要对它进行修改或是删除,在flash中是需要怎么操作的呢?
oob的数据是随着chunk的数据写入flash中的,但是nand flash 的擦除又是按block进行擦除,如果不擦除,数据又不能重新被写入,那要怎么标记file3 存储原来数据的chunk为无效呢?
在yaffs2中,它是通过oob中的obj id来标记是否同一个数据chunk,通过seq_number来标记哪个chunk的数据是最新的,如果不是最新的,那就是无效的了。
比如在文件系统中,有多个chunk它们有相同的obj id,说明这些chunk都是这个obj id 的不同修改版本的同一组数据,seq_number值最大的是最新的数据,其它的则都是无效数据。每一次修改,seq_number就会增加1。
这里是通过软件方法来标记数据无效,实际物理数据是没有做无效标记的,数据也没有被清除。物理上的标记无效和数据擦除,是需要等到垃圾回收的时候再对整个block进行擦除操作,这个时候标记的其实不是数据无效,而是chunk未使用.
在数据更新的操作中,核心的参数是obj id 和seq_number。
从上面数据更新原理上我们知道,一个旧的数据,或是数据结构,在yaffs2文件系统中并不会标记它为无效,因为写入标志同样需要擦除再写入。在yaffs2文件系统中,是通过seq_number来标记数据版本的新旧,旧的则为无效数据。
在yaffs2的垃圾回收中,有两种方式:主动回收和被动回收:
主动回收:一个block中的绝大部分chunk数据都是无效的,文件系统会触发主动回收
被动回收:flash 已经没有干净的chunk可以继续使用,此时需要立即执行垃圾回收以释放空间。这里会把几个block中的有效数据合并到一块,腾出至少一个无效数据block以便进行整块擦除回收。
yaffs2文件系统中,为了平衡性能与回收功能,它的垃圾回收有两个特性:
yaffs2 文件系统与 jffs2 文件系统非常相似,都是基于裸flash设计的文件系统,jffs2 更常用于nor flash ,而yaffs2 是专为nand flash 而设计。它们都是日志结构文件系统,都有磨损平衡功能,但也都是随机磨损平衡。
它们都适合比较小容量的存储设备,因为jffs2挂载的时候需要全盘扫描flash查找元数据构建文件目录结构,所以jffs2在大容量存储设备中数据存储比较多时,挂载所需要的时间会比较长,耗用的内存也会比较多。
yaff2 是将关键元数据存储在oob中,nand flash的oob区域是固定的。挂载的时候只需要扫描oob区域数据就可以了,所以相比较jffs2,yaffs2的挂载启动速度会比较快一些。
jffs2的数据和元数据都是压缩的,并且支持多种压缩算法,这些yaffs2都没有,所以空间利用率yaffs2并没有jffs2高。
在产品功能没有明显优势的前提下,能把产品价格做低其实也是一个非常大的优势,所以nand flash的应用也越发的普及。但目前nand flash 使用比较多的是集成到FTL(Flash Translation Layer)设备中,比如TF卡,SD卡、SSD、U盘等。
jffs2和yaffs2文件系统,都是基于裸的flash来使用,它们并不适用于FTL设备,FTL设备使用比较多的文件系统是:FAT32,exFAT、NTFS、ext3、ext4等
关于存储介质和其它文件系统原理的介绍,可以查看前面文章:
yaffs2目前在嵌入式设备中使用率还是比较高,了解它的工作原理,有助于更好地使用它。另外从官方资料上看,yaffs 是需要授权收费的,如果有使用yaffs2文件系统的设备,需要考虑是否存在版权法律风险。
【如果你觉得文章内容对你有帮助,那就点个赞、关注一下吧】