移植 YAFFS 文件系统到 MQX(Migrate YAFFS File System to MQX)

本文详细介绍YAFFS文件系统的设计原理与应用实践,包括针对NAND闪存优化的存储机制、缓存机制及直接接口调用方法。适用于嵌入式系统开发者了解如何在资源受限的环境下高效使用NAND闪存。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【提示:本文资料由飞思卡尔原厂提供,非原创】

YAFFS(Yet Another Flash File System)文件系统是专门针对 NAND 闪存设计的嵌入式文件系统。在 YAFFS 中,文件是以固定大小的数据块进行存储的,块的大小可以是 512 字节、1024 字节或者 2048 字节。这种实现依赖于它能够将一个数据块头和每个数据块关联起来。每个文件(包括目录)都有一个数据块头与之相对应,数据块头中保存了 ECC(Error Correction Code)和文件系统的组织信息,用于错误检测和坏块处理。

YAFFS 在文件进行改写时总是先写入新的数据块,然后删除旧的数据块,这样即使意外掉电,丢失的也只是这一次修改数据的最小写入单位,从而实现了掉电保护,保证了数据完整性。

YAFFS 是为 NAND FLASH 设计的,它作了以下的假设或定义。

NAND Flash 是基于块(block)的,每一个 Block 大小相同,由整数个 chunk 组成。每一个 Block 可单独擦除。一页(page,或 chunk)为 Flash 的分配单元。所有的访问(读或者写)都是基于页(或 chunk)的。

当对 NAND Flash 编程时,只有二进制中的 0 被编程,而 1 则“不关心”。比如,一个字节包含的二进制数为 1010,那么当编程 1001,会导致这两个数的位与操作。结果为 1000.这和 NOR FLASH 不同。

YAFFS 分别用块号和 chunk id 标示块和 chunk 。它将空块(填满 0xFF)当作空闲块或者已擦除块。这样,格式化一个 YAFFS 分区等价于擦除所有未损坏的块。 因此 YAFFS 最少需要一些函数能够擦除块,读一个页,写一个页。

  1. 直接接口(Direct Interface)的相关文件

仅需要提取少量文件。使用 yaffs 的直接接口,你不需要所有的文件。你实际需要的文件列在下面,其余文件不需要编译。

direct/yaffsfs.c

yaffs_guts.c

direct/yaffscfg.c

yaffs_nand.c

yaffs_tagsvalidity.c

yaffs_checkptrw.c

yaffs_qsort.c

yaffs_tagscompat.c

yaffs_ecc.c

yaffs_packedtags2.c

  1. YAFFS 存储设备

YAFFS 对文件系统上的所有内容(比如正常文件,目录,链接,设备文件等等)都统一当作文件来处理,每个文件都有一个页面专门存放文件头,文件头保存了文件的模式、所有者 id、组 id、长度、文件名、 Parent Object ID 等信息。因为需要在一页内放下这些内容,所以对文件名的长度,符号链接对象的路径名等长度都有限制。前面说到对于 NAND FLASH 上的每一页数据,都有额外的空间用来存储附加信息,通常 NAND 驱动只使用了这些空间的一部分,YAFFS 正是利用了这部分空间中剩余的部分来存储文件系统相关的内容。以

512+16B 为一个 PAGE 的 NAND FLASH 芯片为例,Yaffs 文件系统数据的存储布局如下所示:

0 to 511 数据区域

512 to 515 YAFFS TAG

516 Data status byte

517 Block status byte 坏块标志位

518 to 519 YAFFS TAG

520 to 522 后 256 字节数据的 ECC 校验结果

523 to 524 YAFFS TAG

525 to 527 前 256 字节数据的 ECC 校验结果

可以看到在这里 YAFFS 一共使用了 8 个 BYTE 用来存放文件系统相关的信息(yaffs_Tags)。这 8 个 Byte 的具体使用情况按顺序如下:

Bits Content

20 ChunkID,该 page 在一个文件内的索引号,所以文件大小被限制在 2^20 PAGE 即 512Mb

2 2 bits serial number

10 ByteCount 该 page 内的有效字节数

18 ObjectID 对象 ID 号,用来唯一标示一个文件

12 Ecc, Yaffs_Tags 本身的 ECC 校验和

2 Unused

其中 Serial Number 在文件系统创建时都为 0,以后每次写具有同一 ObjectID 和 ChunkID 的 page 的时候都加一,因为 YAFFS 在更新一个 PAGE 的时候总是在一个新的物理 Page 上写入数据,再将原先的物理 Page 删除,所以该 serial number 可以在断电等特殊情况下,当新的 page 已经写入但老的 page 还没有被删除的时候用来识别正确的 Page,保证数据的正确性。 ObjectID 号为 18bit,所以文件的总数限制在 256K 即 26 万个左右。

由于文件系统的基本组织信息保存在页面的备份空间中,因此,在文件系统加载时只需要扫描各个页面的备份空间,即可建立起整个文件系统的结构,而不需要像 JFFS1/2 那样扫描整个介质,从而大大加快了文件系统的加载速度。

一个 YAFFS 设备是一个逻辑设备,它代表了一个物理设备的部分或整体。你可以认为它是一个 Nand 上的一个“分区”。比如,该分区可能覆盖整个 NAND,也许只是一半,而另外一半就是另一个 Yaffs_Device.它也可以用于你使用一个非 flash 设备(比如 RAM)来测试的情况下。 一个 Yaffs_Device 记录了起始和结束块。通过改变它的起始和结束块,你就可以在同一个物理设备上使用不止一个的 Yaffs_Device。 这里将需要你自己建立的 Yaffs_Device 结构的数据域列出,其他数据域由 Yaffs 自动创建。

int nDataBytesPerChunk

如其名,这是每一个 chunk 的字节数,还记得吧,在 yaffs 术语中,一个页就是一个 chunk,因而它也是一页的字节数。它是数据字节数,即不包含 OOB 的数据。比如一页时 2048 字节+64 字节的 OOB,那么数值 nDataBytesPerChunk 为 2048。

int nChunksPerBlock

物理 Nand 设备中每页包含的 chunk(就是 Nand 上的页)的数目,最少是 2。

int spareBytesPerChunk

空闲域(spare area)大小,比如:每个 chunk(页)的 OOB 字节数。

int startBlock

该逻辑 Yaffs_Device 设备第一个块的块号(而字节地址),注意,yaffs 需要第一个块是空闲的,因此你不可以设置该变量为 0,如果设置为 0,Yaffs 会给它加 1,并且会在结束块号上也加 1,在你设置设备从块 0 开始,到最后一个块结束,这意味着 yaffs 试图写一个不存在的块,从而出现错误。

int endBlock

该逻辑 Yaffs_Device 设备的最后一个块号。如果 startBlock 为 0,那么 yaffs 会使用 endBlock+1,至少使 startBlock+nReservedBlocks+2

int nReservedBlocks

这是 YAFFS 必须保留,用于垃圾回收和块错误恢复的可擦除块的数目。至少是 2,但是 5 更好。如果你使用一个不会损坏的介质,比如 RAM 或者 RAM 盘,或者主机文件系统模拟,那么可以是 2。

int nShortOpCaches

配置当前设备 YAFFS Cache 项的数目。0 值表示不使用 cache。对于大多数系统,推荐使用 10 到 20 之间的一个数值。不能大于 YAFFS_MAX_SHORT_OP_CACHES 定义的数值。

int useNANDECC

这是一个标志,用于指示是由 yaffs 执行 ECC 计算,还是由 NAND 驱动程序来执行 ECC 计算。(译者注:此数值取 0,则使用 yaffs 来执行 ECC 计算,软件 ECC 计算。如果想要使用硬件 ECC 校验时,应该设置为 1,并且在 NAND 驱动程序中加入硬件 ECC 校验的代码。)

void *genericDevice

这是一个指针,它应该指向任何数据,底层 NAND 驱动程序需要知道以从物理设备读、写。

int isYaffs2

我们使用的是否 YAFFS2 版本?

int inbandTags

是否为 OOB 区,如果不是,那么它为真,仅用于 yaffs2

u32 totalBytesPerChunk

这个名字可能有点误导人,它应该等于 nDataBytesPerChunk ,而非其名字暗示的 nDataBytesPerChunk + spareBytesPerChunk。如果 inbandTags 为真,那么 yaffs 设置 nDataBytesPerChunk,因此有足够的空闲空间存储数据,yaffs 会在空闲域中正常存储。

write/readChunkWithTagsFromNAND,

markNANDBlockBad

queryNANDBlock

这些都是函数指针,你需要提供这些函数来给 YAFFS,读写 nand flash。

3.NAND Flash 访问函数

int (*writeChunkWithTagsToNAND) (struct yaffs_DeviceStruct * dev, int chunkInNAND, const u8 * data, const yaffs_ExtendedTags * tags);

dev: 要写入的 yaffs_Device 逻辑设备.

chunkInNAND: 将 chunk 写入的页

Data: 指向需要写入的数据的指针

tags: 未压缩(打包)的 OOB 数据

Return: YAFFS_OK / YAFFS_FAIL

该函数将页(chunk)写入 nand 中,向 nand 中写入数据。数据和标签(tags)永远不应为 NULL. chunkInNand 是将要写入的页的页号,而不是需要转换的地址。

int (*readChunkWithTagsFromNAND) (struct yaffs_DeviceStruct * dev, int chunkInNAND, u8 * data, yaffs_ExtendedTags * tags);

dev: 要写入的 yaffs_Device 逻辑设备.

chunkInNAND: 将 chunk 读入的页

Data: 指向需要读入的数据的缓冲区指针

tags: 指向未压缩(打包)的 OOB 数据的缓冲区指针

Return: YAFFS_OK / YAFFS_FAIL

该函数执行上一个函数的相反的功能,首先,读取数据和 OOB 字节,接着将这些输入放在一个由参数 data 指向的缓冲区中

int (*markNANDBlockBad) (struct yaffs_DeviceStruct * dev, int blockNo);

dev: 要写入的 yaffs_Device 逻辑设备.

blockNo: 要标记的块.

Return: YAFFS_OK / YAFFS_FAIL

int (*queryNANDBlock) (struct yaffs_DeviceStruct * dev, int blockNo, yaffs_BlockState * state, u32 *sequenceNumber);

dev: 要写入的 Yaffs_Device 逻辑设备.

blockNo: 要标记的块.

state: Upon returning this should be set to the relevant state of this particular block.

Sequance number: 该块的顺序号(The Sequence number),为 0 表示此块未使用

Return: YAFFS_OK / YAFFS_FAIL

它应检查一个块是否是有效的。如果在 OOB 中设置了坏块标记,那么*state 应该被赋值为YAFFS_BLOCK_STATE_DEAD,*sequenceNumber 赋值为 0,然后返回 YAFFS_FAIL。

如果该块没坏,那么应解压缩标签。标签解压缩后,若发现 chunk 已使用(查看 tags.chunkUsed),则 *sequenceNumber 应赋值为 tags.sequenceNumber,state 赋值为 YAFFS_BLOCK_STATE_NEEDS_SCANNING,否则该块未使用,则sequenceNumber 赋值为 0,*state 赋值为 YAFFS_BLOCK_STATE_EMPTY

  1. YAFFS 的缓存机制

由于 NandFlash 是有一定的读写次数的,所以在对一个文件进行操作的时候往往是先通过缓冲进行,对最后一次性写入 NandFlash,这有效的减少了用户对 NandFlash 的频繁操作,延长了 NandFlash 的寿命。下面大致说一下 YAFFS 的缓存机制:

4.1.首先在 yaffs_mount 的时候会对 yaffs_dev 这个结构体进行注册,和缓冲部分相关的有:dev->nShortOpCaches//这个变量决定了有多少个缓冲,因为缓冲会大量的占用堆栈的空间,所以在 yaffs 不建议缓冲的数量很大,即使你填一个很大的数,系统也不会超过 YAFFS_MAX_SHORT_OP_CACHES 的总数。 yaffs_ChunkCache *srCache;//缓冲区的首地址,dev->srCache = YMALLOC( dev->nShortOpCaches * sizeof(yaffs_ChunkCache));下面介绍一下缓冲区这个结构体的组成: typedef struct

{

struct yaffs_ObjectStruct *object;//一个缓冲区对应一个文件 int chunkId;

int lastUse; //通过 lastUse 来

int dirty; //标志了这一个缓冲区是否被使用

int nBytes;

__u8 data[YAFFS_BYTES_PER_CHUNK];//数据区

} yaffs_ChunkCache;

4.2.什么时候用到缓冲区?

用到缓冲区最多的地方显而易见是对已经创建的文件进行写操作。而且是需要写的大小和 512 不一致的时

候,这是因为如果是刚好 512 的话,系统会直接写入 NandFlash 中。对于小于 512 的系统首先会调用

yaffs_FindChunkCache(in,chunk)这个函数来判断 in 这个 object 是否在缓冲区中存在。如果存在会调用已有

缓冲区进行操作。当然如果是第一次对一个 object 进行操作,肯定在缓冲区中是不存在对应它的空间的,因

此系统会调用 yaffs_GrabChunkCache 函数为此次操作分配一个缓冲区。

5.应用层接口

YAFFS 为连接的应用程序提供了一组函数。大部分跟标准 C 库函数,如 open/close 一致,只是增加了yaffs_前缀,如 yaffs_open. 这些函数定义在 direct/yaffsfs.h 中。

初始化 yaffs 来完成读写,你必须在每个你要使用的 yaffs 设备上调用 yaffs_mount。比如 yaffs_mount(” /boot”)。这可以在系统启动的时候执行,如果存在一个操作系统,那么程序需要考虑这点。在你完成使用的时候,你也需要调用 yaffs_umount 函数,这样 yaffs 就会将它需要状态写入磁盘。 如果读写文件的应用层接口已经存在,你可以根据相关的操作系统调用来封装相关的函数调用。

1yaffs_mount( )

功能说明:加载指定器件。

输入参数:path 要加载的器件。输出参数:无。

返回值: 表明加载的状态。

调用的函数:yaffsfs_FindDevice( )、yaffs_GutsInitialise( )。

2yaffs_open( )

功能说明:按照指定方式打开文件。

输入参数:path 文件的绝对路径;

Oflag 打开的方式;

Mode 文件许可模式。

输出参数:无。

返回值:句柄。

调用的函数:yaffsfs_GetHandle( )、yaffsfs_FindDirectory( )、yaffs_MknodFile( )、 yaffsfs_PutHandle( )、yaffs_ResizeFile( )、yaffsfs_FindObject( )。

3yaffs_write( )

功能说明:根据打开文件的句柄,从指定数组处读指定字节写入文件中。

输入参数:fd要写入的文件的句柄;

Buf 要写入的数据的首地址; Nbyte 要写入的字节数。

输出参数:无。

返回值: 写入了的字节数。

调用的函数:yaffs_WriteDataToFile( )、yaffsfs_GetHandlePointer( )、yaffsfs_GetHandleObject( )

4yaffs_read( )

功能说明:根据打开文件的句柄,从文件中读出指定字节数据存入指定地址。

输入参数:fd 要读出的文件的句柄;

Buf 读出文件后要存入的数据的首地址;

Nbyte 要读出的字节数。

输出参数:无。

返回值: 读出了的字节数。

调用的函数:yaffs_ReadDataFromFile( )、yaffsfs_GetHandlePointer( )

5 yaffs_close( )

功能说明:关闭已经打开的文件句柄,yaffs 有缓冲机制,当调用 yaffs_close()关闭文件之后能够保证将内容写入 nandflash。

输入参数:fd 需要关闭的文件的句柄;

输出参数:无。

返回值:无。

  1. YAFFS 在 MQX 的应用案例

YAFFS 提供了直接调用模式,可以方便移植到 none-OS 或者 light-weighted OS 中。附件是将 YAFFS 移植到 Freescale MQX 实时操作系统的源代码和工程,可以在 II 型集中器的 Demo Board 上运行。

初步的测试表明 YAFFS 工作正常,能够完成创建目录,创建/删除文件,读/写文件操作等。
在这里插入图片描述
YAFFS 非常适合 none-OS 或者是 light-weighted OS,使用 YAFFS 需要关注的是 RAM 的消耗,适合小量文件(<20)。如果不想使用 MQX 默认的 MFS(FAT32 文件系统),YAFFS 可以作为一个文件系统的备选方案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值