yaffs2文件系统分析.docx

上传人:b****6 文档编号:6153825 上传时间:2023-01-04 格式:DOCX 页数:23 大小:29.97KB
下载 相关 举报
yaffs2文件系统分析.docx_第1页
第1页 / 共23页
yaffs2文件系统分析.docx_第2页
第2页 / 共23页
yaffs2文件系统分析.docx_第3页
第3页 / 共23页
yaffs2文件系统分析.docx_第4页
第4页 / 共23页
yaffs2文件系统分析.docx_第5页
第5页 / 共23页
点击查看更多>>
下载资源
资源描述

yaffs2文件系统分析.docx

《yaffs2文件系统分析.docx》由会员分享,可在线阅读,更多相关《yaffs2文件系统分析.docx(23页珍藏版)》请在冰豆网上搜索。

yaffs2文件系统分析.docx

yaffs2文件系统分析

yaffs2文件系统分析

作者:

dreamice

1.前言

略。

2.yaffs文件系统简介

按理说这里应该出现一些诸如“yaffs是一种适合于NANDFlash的文件系统XXXXX”之类

的字眼,不过考虑到网络上关于yaffs/yaffs2的介绍已经多如牛毛,所以同上,略。

3.本文内容组织

本文将模仿《linux内核源代码情景分析》一书,以情景分析的方式对yaffs2文件系统的

源代码进行分析。

首先将分析几组底层函数,如存储空间的分配和释放等;其次分析文件

逻辑地址映射;然后是垃圾收集机制;接下来……Sorry,本人还没想好。

:

-)

4.说明

因为yaffs2貌似还在持续更新中,所以本文所列代码可能和读者手中的代码不完全一致。

另外,本文读者应熟悉C语言,熟悉NANDFlash的基本概念(如block和page)。

Ok,步入正题。

首先分析存储空间的分配。

5.NANDFlash存储空间分配和释放

我们知道,NANDFlash的基本擦除单位是Block,而基本写入单位是page。

yaffs2在分配

存储空间的时候是以page为单位的,不过在yaffs2中把基本存储单位称为chunk,和

page是一样的大小,在大多数情况下和page是一个意思。

在下文中我们使用chunk这个

词,以保持和yaffs2的源代码一致。

我们先看存储空间的分配(在yaffs_guts.c中。

这个文件也是yaffs2文件系统的核心部分):

Yaffs2中将该函数更名为yaffs_alloc_chunk。

staticintyaffs_AllocateChunk(yaffs_Device*dev,intuseReserve,

yaffs_BlockInfo**blockUsedPtr)

{

intretVal;

yaffs_BlockInfo*bi;

if(dev->allocationBlock<0){

/*Getnextblocktoallocateoff*/

dev->allocationBlock=yaffs_FindBlockForAllocation(dev);

dev->allocationPage=0;

}

函数有三个参数,dev是yaffs_Device结构的指针,yaffs2用这个结构来记录一个NAND

器件的属性(如block和page的大小)和系统运行过程中的一些统计值(如器件中可用chunk的总数),还用这个结构维护着一组NAND操作函数(如读、写、删除)的指针。

整个结构体比较大,我们会按情景的不同分别分析。

useReserve表示是否使用保留空间。

yaffs2文件系统并不会将所有的存储空间全部用于存储文件系统数据,而要空出部分

block用于垃圾收集时使用。

一般情况下这个参数都是0,只有在垃圾收集时需要分配存

储空间的情况下将该参数置1。

yaffs_BlockInfo是描述block属性的结构,主要由一些统

计变量组成,比如该block内还剩多少空闲page等。

我们同样在具体情景中再分析这个结

构中的字段含义。

函数首先判断dev->allocationBlock的值是否小于0。

yaffs_Device结构内的

allocationBlock字段用于记录当前从中分配chunk(page)的那个block的序号。

当一

个block内的所有page全部分配完毕时,就将这个字段置为-1,下次进入该函数时就会

重新挑选空闲的block。

这里我们假定需要重新挑选空闲block,因此进入

yaffs_FindBlockForAllocation函数:

[yaffs_AllocateChunk()=>yaffs_FindBlockForAllocation()]

staticintyaffs_FindBlockForAllocation(yaffs_Device*dev)

{

inti;

yaffs_BlockInfo*bi;

if(dev->nErasedBlocks<1){

/*Hoostermanwe'vegotaproblem.

*Can'tgetspacetogc

/*

T(YAFFS_TRACE_ERROR,

(TSTR("yaffstragedy:

nomoreeraasedblocks"TENDSTR)));

return-1;

}

dev->nErasedBlocks记录着器件内所有可供分配的block的数量。

如果该值小于1,那显

然是有问题了。

不但正常的分配请求无法完成,就连垃圾收集都办不到了。

for(i=dev->internalStartBlock;i<=dev->internalEndBlock;i++){

dev->allocationBlockFinder++;

if(dev->allocationBlockFinderinternalStartBlock

||dev->allocationBlockFinder>dev->internalEndBlock){

dev->allocationBlockFinder=dev->internalStartBlock;

internalStartBlock和internalEndBlock分别是yaffs2使用的block的起始序号和结束

序号。

也就是说yaffs2文件系统不一定要占据整个Flash,可以只占用其中的一部分。

dev->allocationBlockFinder记录着上次分配的块的序号。

如果已经分配到系统尾部,就

从头重新开始搜索可用块。

bi=yaffs_get_block_info(dev,dev->alloc_block_finder);

if(bi->block_state==YAFFS_BLOCK_STATE_EMPTY){

bi->block_state=YAFFS_BLOCK_STATE_ALLOCATING;

dev->seq_number++;

bi->seq_number=dev->seq_number;

dev->n_erased_blocks--;

yaffs_trace(YAFFS_TRACE_ALLOCATE,

"Allocatedblock%d,seq%d,%dleft",

dev->alloc_block_finder,dev->seq_number,

dev->n_erased_blocks);

returndev->alloc_block_finder;

}

yaffs_GetBlockInfo函数获取指向block信息结构的指针,该函数比较简单,就不详细介

绍了。

yaffs_BlockInfo结构中的blockState成员描述该block的状态,比如空,满,已

损坏,当前分配中,等等。

因为是要分配空闲块,所以块状态必须是

YAFFS_BLOCK_STATE_EMPTY,如果不是,就继续测试下一个block。

找到以后将block状

态修改为YAFFS_BLOCK_STATE_ALLOCATING,表示当前正从该block中分配存储空间。

常情况下,系统中只会有一个block处于该状态。

另外还要更新统计量ErasedBlocks和

sequenceNumber。

这个sequenceNumber记录着各block被分配出去的先后顺序,以后在

垃圾收集的时候会以此作为判断该block是否适合回收的依据。

现在让我们返回到函数yaffs_AllocateChunk中。

yaffs_CheckSpaceForAllocation()函数

检查Flash上是否有足够的可用空间,通过检查后,就从当前供分配的block上切下一个

if(dev->alloc_block>=0){

bi=yaffs_get_block_info(dev,dev->alloc_block);

ret_val=(dev->alloc_block*dev->param.chunks_per_block)+

dev->alloc_page;

bi->pages_in_use++;

yaffs_set_chunk_bit(dev,dev->alloc_block,dev->alloc_page);

dev->alloc_page++;

dev->n_free_chunks--;

/*Iftheblockisfullsetthestatetofull*/

if(dev->alloc_page>=dev->param.chunks_per_block){

bi->block_state=YAFFS_BLOCK_STATE_FULL;

dev->alloc_block=-1;

}

if(block_ptr)

*block_ptr=bi;

returnret_val;

dev->allocationPage记录着上次分配的chunk在block中的序号,每分配一次加1。

从这

里我们可以看出,系统在分配chunk的时候是从block的开头到结尾按序分配的,直到一

个block内的所有chunk全部分配完毕为止。

retVal是该chunk在整个device内的总序

号。

PagesInUse记录着该block中已分配使用的page的数量。

系统在设备描述结构yaffs_Device中维护着一张位图,该位图的每一位都代表着Flash

上的一个chunk的状态。

yaffs_SetChunkBit()将刚分配得到的chunk在位图中的对应位置

1,表明该块已被使用。

更新一些统计量后,就可以返回了。

看过chunk分配以后,我们再来chunk的释放。

和chunk分配不同的是,chunk的

释放在大多数情况下并不释放对应的物理介质,这是因为NAND虽然可以按page写,但

只能按block擦除,所以物理介质的释放要留到垃圾收集或一个block上的所有page全部

变成空闲的时候才进行。

根据应用场合的不同,chunk的释放方式并不唯一,分别由

yaffs_DeleteChunk函数和yaffs_SoftDeleteChunk函数完成。

我们先看

yaffs_DeleteChunk:

(该函数在后续版本中被更名为yaffs_chunk_del())

voidyaffs_DeleteChunk(yaffs_Device*dev,intchunkId,intmarkNAND,intlyn)

chunkId就是要删除的chunk的序号,markNand参数用于yaffs一代的代码中,yaffs2不

使用该参数。

参数lyn在调用该函数时置成当前行号(__LINE__),用于调试。

首先通过yaffs_GetBlockInfo获得chunk所在block的信息描述结构指针,然后就跑到

else里面去了。

if语句的判断条件中有一条!

dev->isYaffs2,所以对于yaffs2而言是不

会执行if分支的。

在else分支里面只是递增一下统计计数就出来了,我们接着往下看。

if(bi->blockState==YAFFS_BLOCK_STATE_ALLOCATING||

bi->blockState==YAFFS_BLOCK_STATE_FULL||

bi->blockState==YAFFS_BLOCK_STATE_NEEDS_SCANNING||

bi->blockState==YAFFS_BLOCK_STATE_COLLECTING){

dev->nFreeChunks++;

yaffs_ClearChunkBit(dev,block,page);

bi->pagesInUse--;

if(bi->pagesInUse==0&&

!

bi->hasShrinkHeader&&

bi->blockState!

=YAFFS_BLOCK_STATE_ALLOCATING&&

bi->blockState!

=YAFFS_BLOCK_STATE_NEEDS_SCANNING){

yaffs_BlockBecameDirty(dev,block);

首先要判断一下该block上是否确实存在着可释放的chunk。

block不能为空,不能是坏块。

YAFFS_BLOCK_STATE_NEEDS_SCANNING表明正对该块进行垃圾回收,我们后面会分析;

YAFFS_BLOCK_STATE_NEEDS_SCANNING在我手上的源代码中似乎没有用到。

通过判断以后,所做的工作和chunk分配函数类似,只是一个递增统计值,一个递减。

减统计值以后还要判断该block上的page是否已全部释放,如果已全部释放,并且不是当前分配块,就通过yaffs_BlockBecameDirty函数删除该block,只要能通过删除操作

(不是坏块),该block就又可以用于分配了。

相比较来说,yaffs_SoftDeleteChunk所做的工作就简单多了。

关键的代码只有两行:

staticvoidyaffs_SoftDeleteChunk(yaffs_Device*dev,intchunk)

{

……

theBlock->softDeletions++;

dev->nFreeChunks++;

……

}

这里递增的是yaffs_blockInfo结构中的另一个统计量softDeletions,而没有修改

pagesInUse成员,也没有修改chunk状态位图。

那么,这两个函数的应用场合有什么区别

呢?

一般来说,yaffs_DeleteChunk用于文件内容的更新。

比如我们要修改文件中的部分内容,

这时候yaffs2会分配新的chunk,将更改后的内容写入新chunk中,原chunk的内容自然

就没有用了,所以要将pageInUse减1,并修改位图;

yaffs_SoftDeleteChunk用于文件的删除。

yaffs2在删除文件的时候只是删除该文件在内

存中的一些描述结构,而被删除的文件所占用的chunk不会立即释放,也就是不会删除

文件内容,在后续的文件系统操作中一般也不会把这些chunk分配出去,直到系统进行垃

圾收集的时候才有选择地释放这些chunk。

熟悉DOS的朋友可能还记得,DOS在删除的文

件的时候也不会立即删除文件内容,只是将文件名的第一个字符修改为0xA5,事后还可

以恢复文件内容。

yaffs2在这点上是类似的。

1.文件地址映射

上面说到,yaffs文件系统在更新文件数据的时候,会分配一块新的chunk,也就是说,

同样的文件偏移地址,在该地址上的数据更新前和更新后,其对应的flash上的存储地

址是不一样的。

那么,如何根据文件内偏移地址确定flash存储地址呢?

最容易想到的办

法,就是在内存中维护一张映射表。

由于flash基本存储单位是chunk,因此,只要将以

chunk描述的文件偏移量作为表索引,将flashchunk序号作为表内容,就可以解决该问

题了。

但是这个方法有几个问题,首先就是在做seek操作的时候,要从表项0开始按序搜

索,对于大文件会消耗很多时间;其次是在建立映射表的时候,无法预计文件大小的变

化,于是就可能在后来的操作中频繁释放分配内存以改变表长,造成内存碎片。

yaffs的

解决方法是将这张大的映射表拆分成若干个等长的小表,并将这些小表组织成树的结构,

方便管理。

我们先看小表的定义:

structyaffs_tnode{

structyaffs_tnode*internal[YAFFS_NTNODES_INTERNAL];

};

YAFFS_NTNODES_INTERNAL定义为(YAFFS_NTNODES_LEVEL0/2),而

YAFFS_NTNODES_LEVEL0定义为16,所以这实际上是一个长度为8的指针数组。

不管是叶

子节点还是非叶节点,都是这个结构。

当节点为非叶节点时,数组中的每个元素都指向

下一层子节点;当节点为叶子节点时,该数组拆分为16个16位长的短整数(也有例外,后面会说到),该短整数就是文件内容在flash上的存储位置(即chunk序号)。

至于如何

通过文件内偏移找到对应的flash存储位置,源代码所附文档

(Development/yaffs/Documentation/yaffs-notes2.html)已经有说明,俺就不在此处饶

舌了。

下面看具体函数。

为了行文方便,后文中将yaffs_Tnode这个指针数组称为“一组”Tnode,而将数组

中的每个元素称为“一个”Tnode。

树中的每个节点,都是“一组”Tnode。

先看映射树的节点的分配。

structyaffs_tnode*yaffs_get_tnode(structyaffs_dev*dev)

{

structyaffs_tnode*tn=yaffs_alloc_raw_tnode(dev);

if(tn){

memset(tn,0,dev->tnode_size);

dev->n_tnodes++;

}

dev->checkpoint_blocks_required=0;/*forcerecalculation*/

returntn;

}

调用yaffs_GetTnodeRaw分配节点,然后将得到的节点初始化为零。

staticyaffs_Tnode*yaffs_GetTnodeRaw(yaffs_Device*dev)

{

yaffs_Tnode*tn=NULL;

/*Iftherearenoneleftmakemore*/

if(!

dev->freeTnodes){

yaffs_CreateTnodes(dev,YAFFS_ALLOCATION_NTNODES);

}

当前所有空闲节点组成一个链表,dev->freeTnodes是这个链表的表头。

我们假定已经没

有空闲节点可用,需通过yaffs_CreateTnodes创建一批新的节点。

staticintyaffs_CreateTnodes(yaffs_Device*dev,intnTnodes)

{

......

tnodeSize=(dev->tnodeWidth*YAFFS_NTNODES_LEVEL0)/8;

newTnodes=YMALLOC(nTnodes*tnodeSize);

mem=(__u8*)newTnodes;

}

(其实在最新版本的yaffs中已经加入了slab缓冲区,这样提高了效率)

上面说过,叶节点中一个Tnode的位宽默认为16位,也就是可以表示65536个chunk。

于时下的大容量flash,chunk的大小为2K,因此在默认情况下yaffs2所能寻址的最大

flash空间就是128M。

为了能将yaffs2用于大容量flash上,代码作者试图通过两种手段

解决这个问题。

第一种手段就是这里的dev->tnodeWidth,通过增加单个Tnode的位宽,

就可以增加其所能表示的最大chunkId;另一种手段是我们后面将看到的chunkgroup,

通过将若干个chunk合成一组用同一个id来表示,也可以增加系统所能寻址的chunk范围。

俺为了简单,分析的时候不考虑这两种情况,因此tnodeWidth取默认值16,也不考虑将

多个chunk合成一组的情况,只在遇到跟这两种情况有关的代码时作简单说明。

在32位的系统中,指针的宽度为32位,而chunkid的宽度为16位,因此相同大小的

Tnode组,可以用来表示N个非叶Tnode(作为指针使用),也可以用来表示N*2个叶子

Tnode(作为chunkid使用)。

代码中分别用YAFFS_NTNODES_INTERNAL和

YAFFS_NTNODES_LEVEL0来表示。

前者取值为8,后者取值为16。

从这里我们也可以看出

若将yaffs2用于64位系统需要作哪些修改。

针对上一段叙述的问题,俺以为在内存不紧

张的情况下,不如将叶节点Tnode和非叶节点Tnode都设为一个指针的长度。

分配得到所需的内存后,就将这些空闲空间组成Tnode链表:

for(i=0;i

curr=(yaffs_Tnode*)&mem[i*tnodeSize];

next=(yaffs_Tnode*)&mem[(i+1)*tnodeSize];

curr->internal[0]=next;

}

每组Tnode的第一个元素作为指针指向下一组Tnode。

完成链表构造后,还要递增统计量,

并将新得到的Tnodes挂入一个全局管理链表yaffs_TnodeList:

dev->nFreeTnodes+=nTnodes;

dev->nTnodesCreated+=nTnodes;

tnl=YMALLOC(sizeof(yaffs_TnodeList));

if(!

tnl){

T(YAFFS_TRACE_ERROR,

(TSTR

("yaffs:

Couldnotaddtnodestomanagementlist"TENDSTR)));

}else{

tnl->tnodes=newTnodes;

tnl->next=dev->allocatedTnodeList;

dev->allocatedTnodeList=tnl;

}

回到yaffs_GetTnodeRaw,创建了若干组新的Tnode以后,从中切下所需的Tnode,并修

改空闲链表表头指针:

if(dev->freeTnodes){

tn=dev->freeTnodes;

dev->freeTnodes=dev->freeTnodes->internal[0];

dev->nFreeTnodes--;

}

至此,分配工作就完成了。

相比较来说,释放Tnodes的工作就简单多了,简单的链表和统计值操作:

staticvoidyaffs_FreeTnode(yaffs_Device*dev,yaffs_Tnode*tn)

{

if(tn){

tn->internal[0]=dev->freeTnodes;

dev->freeTnodes=tn;

dev->nFreeTnodes++;

}

}

看过Tnode的分配和释放,我们再来看看这些Tnode是如何使用的。

在后文中,我

们把以chunk为单位的文件

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 自然科学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1