您的位置:首页 > 编程语言 > PHP开发

yaffs2源代码分析(二)

2012-11-15 19:43 495 查看

yaffs2源代码分析(二)(转linuxforum 精华)

下面我们看另一个情景,看看当文件长度增加的时候,映射树是如何扩展的。主要函数为

static yaffs_Tnode *yaffs_AddOrFindLevel0Tnode(yaffs_Device * dev,

yaffs_FileStructure * fStruct,

__u32 chunkId,

yaffs_Tnode *passedTn)

函数的前几行和yaffs_FindLevel0Tnode一样,对函数参数作一些检查。通过检查之后,首先看原映射树是否有足够的高度,如果高度不够,就先将其“拔高”:

if (requiredTallness > fStruct->topLevel) {

/* Not tall enough,gotta make the tree taller */

for (i = fStruct->topLevel; i < requiredTallness; i++) {

tn = yaffs_GetTnode(dev);

if (tn) {

tn->internal[0] = fStruct->top;

fStruct->top = tn;

} else {

T(YAFFS_TRACE_ERROR,

(TSTR("yaffs: no more tnodes" TENDSTR)));

}

}

fStruct->topLevel = requiredTallness;

}

for循环完成增加新层的功能。新增的每一层都只有一个节点(即一组Tnode),fStruct->top始终指向最新分配的节点。将映射树扩展到所需的高度之后,再根据需要将其“增肥”,扩展其“宽度”:

l = fStruct->topLevel;

tn = fStruct->top;

if(l > 0) {

while (l > 0 && tn) {

x = (chunkId >>

( YAFFS_TNODES_LEVEL0_BITS +

(l - 1) * YAFFS_TNODES_INTERNAL_BITS)) &

YAFFS_TNODES_INTERNAL_MASK;

if((l>1) && !tn->internal[x]){

/* Add missing non-level-zero tnode */

tn->internal[x] = yaffs_GetTnode(dev);

} else if(l == 1) {

/* Looking from level 1 at level 0 */

if (passedTn) {

/* If we already have one, then release it.*/

if(tn->internal[x])

yaffs_FreeTnode(dev,tn->internal[x]);

tn->internal[x] = passedTn;

} else if(!tn->internal[x]) {

/* Don't have one, none passed in */

tn->internal[x] = yaffs_GetTnode(dev);

}

}

tn = tn->internal[x];

l--;

}

}

上面“拔高”的时候是从下往上“盖楼”,这里“增肥”的时候是从上往下“扩展”。

tn->internal[x]为空表示下层节点尚未创建,需要通过yaffs_GetTnode分配之,就是“增肥”了。如果函数参数passedTn有效,就用该组Tnode代替level0上原先的那组Tnode;否则按需分配新的Tnode组。所以这里的函数名似乎应该取作 yaffs_AddOrFindOrReplaceLevel0Tnode更加恰当。不过这个新名字也太长了些……

树的创建、搜索和扩展说完了,下面该说什么?……对了,收缩和删除。不过看过创建搜索扩展之后,收缩和删除已经没什么味道了。主要函数有:

yaffs_DeleteWorker()

yaffs_SoftDeleteWorker()

yaffs_PruneWorker()

前两者用于删除,第三个用于收缩。都是从level0开始,以递归的方式从叶节点向上删,并释放被删除Tnode所对应的物理chunk。递归,伟大的递归啊……俺不想把这篇文章做成递归算法教程,除了递归这三个函数也就不剩啥了,所以一概从略。唯一要说的就是yaffs_DeleteWorker和yaffs_SoftDeleteWorker的区别,这两个函数非常类似,只是在释放物理chunk的时候分别调用yaffs_DeleteChunk和yaffs_SoftDeleteChunk。其中函数yaffs_DeleteWorker在yaffs2中似乎是不用的,而yaffs_SoftDeleteWorker主要用于在删除文件时资源的释放。

p.s. 这个星期犯头疼,没力气写东西,所以短一些;希望下星期能好起来。接下来的几篇打算分析文件对象管理,大家捧个场 :-)

7.文件系统对象

在yaffs2中,不管是文件还是目录或者是链接,在内存都用一个结构体yaffs_ObjectStruct来描述。我们先简要介绍一下这个结构体中的几个关键字段,然后再来看代码。在后文中提到“文件”或“文件对象”,若不加特别说明,都指广义的“文件”,既可以是文件,也可以是目录。

__u8 deleted:1; /* This should only apply to unlinked files. */

__u8 softDeleted:1; /* it has also been soft deleted */

__u8 unlinked:1; /* An unlinked file. The file should be in the unlinked directory.*/

这三个字段用于描述该文件对象在删除过程中所处的阶段。在删除文件时,首先要将文件从原目录移至一个特殊的系统目录/unlinked,以此拒绝应用程序对该文件的访问,此时将unlinked置1;然后判断该文件长度是否为0,如果为0,该文件就可以直接删除,此时将deleted置1;如果不为0,就将deleted和softDelted都置1,表明该文件数据所占据的chunk还没有释放,要留待后继处理。

struct yaffs_ObjectStruct *parent;

看名字就知道,该指针指向上层目录。

int chunkId;

每个文件在flash上都有一个文件头,存储着该文件的大小、所有者、创建修改时间等信息。chunkId就是该文件头在flash上的chunk序号。

__u32 objectId; /* the object id value */

每一个文件系统对象都被赋予一个唯一的编号,作为对象标识,也用于将该对象挂入一个散列表,加快对象的搜索速度。

yaffs_ObjectType variantType;

yaffs_ObjectVariant variant;

前者表示该对象的类型,是目录、普通文件还是链接文件。后者是一个联合体,根据对象类型的不同有不同的解释。

其余的成员变量,我们在后面结合函数一起分析。

下面我们来看相关的函数。先看一个简单的:

static yaffs_Object *yaffs_CreateFakeDirectory(yaffs_Device * dev, int number,

__u32 mode)

所谓Fake Directory,就是仅存在于内存中,用于管理目的的目录对象,比如我们上面提到的unlinked目录。这种类型的目录有一些特别的地方,如禁止改名、禁止删除等。由于对象仅存在于内存中,因此不涉及对硬件的操作,所以函数体很简单。首先通过yaffs_CreateNewObject获得一个新对象,然后对其中的一些字段初始化。先把字段初始化看一下,顺便再介绍一些字段:

renameAllowed表示是否允许改名,对于fake对象为0;

unlinkAllowed表示是否允许删除,对于fake对象同样为0;

yst_mode就是linux中的访问权限位;

chunkId是对象头所在chunk,由于fake对象不占flash存储空间,所以置0。

回过头来看yaffs_CreateNewObject:

[yaffs_CreateFakeDirectory --> yaffs_CreateNewObject]

yaffs_Object *yaffs_CreateNewObject(yaffs_Device * dev, int number,

yaffs_ObjectType type)

{

yaffs_Object *theObject;

if (number < 0) {

number = yaffs_CreateNewObjectNumber(dev);

}

theObject = yaffs_AllocateEmptyObject(dev);

前面说过,每个yaffs_Object都有一个唯一的序列号,这个序号既可以在创建对象的时候由上层函数指定,也可以由系统分配。如果number < 0,那就表示由系统分配。序列号分配函数是 yaffs_CreateNewObjectNumber。我们就不深入到这个函数内部了,只说明一下该函数做了些什么:

系统为了方便根据对象id找到对象本身,将每个对象都通过指针hashLink挂入了一个散列表,散列函数是number % 256,所以这个散列表有256个表项。yaffs_CreateNewObjectNumber函数每次搜索10个表项,从中选取挂接链表长度最短的那一项,再根据表索引试图计算出一个和该索引上挂接对象的id号不重复的id。

分配到了id号和空闲对象后,再根据对象类型的不同作不同的处理。我们主要关心两种情况,就是对象类型分别为文件和目录的时候:

case YAFFS_OBJECT_TYPE_FILE:

theObject->variant.fileVariant.fileSize = 0;

theObject->variant.fileVariant.scannedFileSize = 0;

theObject->variant.fileVariant.shrinkSize = 0xFFFFFFFF; /* max __u32 */

theObject->variant.fileVariant.topLevel = 0;

theObject->variant.fileVariant.top = yaffs_GetTnode(dev);

break;

case YAFFS_OBJECT_TYPE_DIRECTORY:

INIT_LIST_HEAD(&theObject->variant.directoryVariant.children);

break;

fileSize很好理解;topLevel就是映射树层高,新建的文件层高为0。还要预先分配一组Tnode供该对象使用。scannedFileSize和shrinkSize用于yaffs2初始化时的flash扫描阶段,这里先跳过。如果该对象是目录,那么所做的工作只是初始化子对象(就是该目录下的文件或子目录)双向链表指针,前后指针都指向链表头自身。

看过Fake对象创建,我们再看看普通对象的创建。按对象类型的不同,有四个函数分别用于创建普通文件、目录、设备文件、符号链接和硬链接,它们分别是:

yaffs_MknodFile;

yaffs_MknodDirectory;

yaffs_MknodSpecial;

yaffs_MknodSymLink;

yaffs_Link

这四个函数最终都调用yaffs_MknodObject来完成创建对象的工作,只是调用参数不一样。

static yaffs_Object *yaffs_MknodObject(yaffs_ObjectType type,

yaffs_Object * parent,

const YCHAR * name,

__u32 mode,

__u32 uid,

__u32 gid,

yaffs_Object * equivalentObject,

const YCHAR * aliasString, __u32 rdev)

函数参数中,前面几个都很好理解,分别是对象类型,上级目录对象,文件名,访问权限,文件所属user id和group id; equivalentObject是创建硬链接时的原始文件对象;aliasString是symLink名称;rdev是设备文件的设备号。

函数首先检查在父目录中是否已存在同名文件,然后同样调用yaffs_CreateNewObject创建新对象。参数-1表示由系统自行选择对象id。

if (in) {

in->chunkId = -1;

in->valid = 1;

in->variantType = type;

in->yst_mode = mode;

in->yst_atime = in->yst_mtime = in->yst_ctime = Y_CURRENT_TIME;

in->yst_rdev = rdev;

in->yst_uid = uid;

in->yst_gid = gid;

in->nDataChunks = 0;

yaffs_SetObjectName(in, name);

in->dirty = 1;

yaffs_AddObjectToDirectory(parent, in);

in->myDev = parent->myDev;

这里列出的代码省略了和wince相关的条件编译部分。chunkId是对象头所在chunk,现在还没有将对象写入flash,所以置为-1;该新对象暂时还没有数据,所以nDataChunks是0。in->dirty = 1表示该新对象信息还没有写入flash。然后通过yaffs_AddObjectToDirectory将新对象挂入父对象的子对象链表。接下来根据对象类型作不同处理:

switch (type) {

case YAFFS_OBJECT_TYPE_SYMLINK:

in->variant.symLinkVariant.alias =

yaffs_CloneString(aliasString);

break;

case YAFFS_OBJECT_TYPE_HARDLINK:

in->variant.hardLinkVariant.equivalentObject =

equivalentObject;

in->variant.hardLinkVariant.equivalentObjectId =

equivalentObject->objectId;

list_add(&in->hardLinks, &equivalentObject->hardLinks);

break;

case YAFFS_OBJECT_TYPE_FILE:

case YAFFS_OBJECT_TYPE_DIRECTORY:

case YAFFS_OBJECT_TYPE_SPECIAL:

case YAFFS_OBJECT_TYPE_UNKNOWN:

/* do nothing */

break;

}

对于最常用的文件对象和目录对象不做任何处理;如果是hardlink,就将新对象挂入原对象的 hardLinks链表。从这里我们可以看出,yaffs2在内存中是以链表的形式处理hardlink的。在将hardlink存储到flash上的时候,则是通过objectId将两者关联起来。Hardlink本身占用一个chunk存储对象头。

最后,通过yaffs_UpdateObjectHeader将新对象头写入flash。

8. Yaffs2的垃圾收集机制

yaffs2的垃圾收集过程实际上包括两个方面:

·一是对那些不再使用的page作物理上的删除。我们在前面介绍chunk释放函数的时候曾经看到,yaffs2在删除chunk的时候仅仅是修改内存中的统计量,而真正的删除工作要留到垃圾收集的时候做。

·二是处理坏块。在对flash进行写操作的时候,我们也许要使用过一个block上的若干page之后才发现这是一个坏块,此时该块上已经有部分有用数据了,在垃圾收集的时候要对这种情况进行处理。

flash在使用过一段时间之后,满足以上两种情况的block也许不止一个,那么,yaffs2按照什么样的原则挑选合适的块进行回收呢?我们看下面的函数:

static int yaffs_BlockNotDisqualifiedFromGC(yaffs_Device * dev,

yaffs_BlockInfo * bi)

这个函数用来判定给定的块bi是否可以回收。

if (!dev->isYaffs2)

return 1; /* disqualification only applies to yaffs2. */

if (!bi->hasShrinkHeader)

return 1; /* can gc */

我们主要关心yaffs2。首先介绍一下什么是 hasShrinkHeader。

还是要提到yaffs2的“软”删除机制。假定我们现在需要减小一个文件的长度,比如从128K缩减到64K,在执行close()系统调用之后,yaffs2会将新的大小写入文件头,而这个文件头是会立即写入flash的,但是由于yaffs2使用软删除机制,原先那后面64K数据仍然残留在flash上,也就是说,出现了文件头和文件内容不一致的情况。此时就将文件头所在block的描述信息中的一个字段hasShrinkHeader置1,表明在垃圾回收时需要特别的处理。如果hasShrinkHeader=0,那么该块是不需要特别的处理,是可以回收的;但是如果hasShrinkHeader=1,那就需要注意了:如果我们所做的不仅仅是文件尺寸的收缩,而是文件的删除,并且在物理删除文件内容之前通过垃圾收集机制将文件头删掉了,那么残留的文件内容就成了“没娘要的孩子”,难以处理了。所以,我们必须先处理文件的残留内容,然后处理文件头。下面我们来看看yaffs2是如何实现处理这个目标的:

/* Find the oldest dirty sequence number if we don't know it and save it

* so we don't have to keep recomputing it.

*/

if (!dev->oldestDirtySequence) {

seq = dev->sequenceNumber;

for (i = dev->internalStartBlock; i <= dev->internalEndBlock;

i++) {

b = yaffs_GetBlockInfo(dev, i);

if (b->blockState == YAFFS_BLOCK_STATE_FULL &&

(b->pagesInUse - b->softDeletions) <

dev->nChunksPerBlock && b->sequenceNumber < seq) {

seq = b->sequenceNumber;

}

}

dev->oldestDirtySequence = seq;

}

/* Can't do gc of this block if there are any blocks older than this one that have

* discarded pages.

*/

return (bi->sequenceNumber <= dev->oldestDirtySequence);

在分析这段代码之前,我们再来回顾一下yaffs2的chunk分配过程和特点。如前文所述,yaffs2在分配chunk的时候遵循两个原则:一是在block内部严格从低地址的chunk向高地址的chunk按次序分配,二是一定要将一个block内的page全部分配完毕后才另行选择block进行分配。而且在分配的时候每挑选一个block就会递增一个序号。这样我们从block的序号就可以推断出该block的分配顺序。

除此之外,yaffs2会在应用程序作clsoe()系统调用的时候将新的文件头写入flash。因此,我们可以作出这样的结论:文件头所在block的序号,一定大于等于文件内容所在block的序号。这样,如果一个block信息结构内的hasShrinkHeader字段为1,并且该block的序号在系统中最小,我们就可以认为该block上的所有文件头对应的文件已经没有残余信息留在flash上了——这些残余信息如果存在,它们所在block的序号一定更小。有了这个结论,上面的代码就不难理解了,所以就不作解释了。

这个函数返回之后,我们就知道函数参数所指向的block是否可以回收了。但是,一个flash上可以回收的block大多数情况下不止一个,如何挑选出最合适的一个呢?——且听下回分解:-)

p.s. 最近厂里比较忙,可能更新会慢下来。私人企业,大家都知道的,时间没办法保证啊。

这阵子开始重读yaffs2,发现上次看的时候有很多地方理解上还是有问题的,写出来的东西也不严谨,最主要的是,文章不是很有条理。俺打算写完垃圾收集就暂停,待俺第二遍(也许还有第三遍,第四遍……)看结束的时候,整理一下思路,然后重新排标题,写分析。不知诸位是否有兴趣?
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: