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

Document created by Weihua Liang Employee on Oct 25, 2013Last modified by ebiz_ws_prod on Dec 13, 2017
Version 4Show Document
  • View in full screen mode

    作者 Shaozhong Liang

    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

2. YAFFS  存储设备

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

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,以后每次写具有同一ObjectIDChunkIDpage的时候都加一,因为YAFFS在更新一个PAGE的时候总是在一个新的物理Page上写入数据,再将原先的物理Page删除,所以该serial number可以在断电等特殊情况下,当新的page已经写入但老的page还没有被删除的时候用来识别正确的Page,保证数据的正确性。 ObjectID号为18bit,所以文件的总数限制在256K26万个左右。
   
由于文件系统的基本组织信息保存在页面的备份空间中,因此,在文件系统加载时只需要扫描各个页面的备份空间,即可建立起整个文件系统的结构,而不需要像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,那么数值nDataBytesPerChunk2048。

int nChunksPerBlock

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

int spareBytesPerChunk

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

int startBlock

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

int endBlock

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

int nReservedBlocks

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

int nShortOpCaches

    配置当前设备YAFFS Cache项的数目。0值表示不使用cache。对于大多数系统,推荐使用1020之间的一个数值。不能大于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

4. 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就会将它需要状态写入磁盘。 如果读写文件的应用层接口已经存在,你可以根据相关的操作系统调用来封装相关的函数调用。

1 yaffs_mount( )
功能说明:加载指定器件。
输入参数:path    要加载的器件。
输出参数:无。
返回值: 表明加载的状态。
调用的函数:yaffsfs_FindDevice( )、yaffs_GutsInitialise( )。


2 yaffs_open( )
功能说明:按照指定方式打开文件。
输入参数:path    文件的绝对路径;
          Oflag   打开的方式;
          Mode    文件许可模式。
输出参数:无。
返回值:句柄。
调用的函数:yaffsfs_GetHandle( )、yaffsfs_FindDirectory( )、yaffs_MknodFile( )、yaffsfs_PutHandle( )、yaffs_ResizeFile( )、yaffsfs_FindObject( )。


3 yaffs_write( )
功能说明:根据打开文件的句柄,从指定数组处读指定字节写入文件中。
输入参数:fd      要写入的文件的句柄;
          Buf     要写入的数据的首地址;
          Nbyte   要写入的字节数。
输出参数:无。
返回值: 写入了的字节数。
调用的函数:yaffs_WriteDataToFile( )、yaffsfs_GetHandlePointer( )、yaffsfs_GetHandleObject( )


4 yaffs_read( )
功能说明:根据打开文件的句柄,从文件中读出指定字节数据存入指定地址。
输入参数:fd       要读出的文件的句柄;
          Buf      读出文件后要存入的数据的首地址;
          Nbyte    要读出的字节数。
输出参数:无。
返回值: 读出了的字节数。
调用的函数:yaffs_ReadDataFromFile( )、yaffsfs_GetHandlePointer( )

 

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

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

输出参数:无。

返回值:无。

 

6. YAFFSMQX的应用案例

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

    初步的测试表明YAFFS工作正常,能够完成创建目录,创建/删除文件,读/写文件操作等

YAFFS.bmp

YAFFS非常适合none-OS或者是light-weighted OS,使用YAFFS需要关注的是RAM的消耗,适合小量文件(<20)。
如果不想使用MQX默认的MFS(FAT32文件系统),YAFFS可以作为一个文件系统的备选方案。

Original Attachment has been moved to: fs.rar

Attachments

    Outcomes