http://blog.chinaunix.net/uid-25314474-id-343665.htmlcss
1.前言
略。
2.yaffs 文件系統簡介
按理說這裏應該出現一些諸如「yaffs 是一種適合於 NAND Flash 的文件系統 XXXXX」之類
的字眼,不過考慮到網絡上關於 yaffs/yaffs2 的介紹已經多如牛毛,因此同上,略。
3.本文內容組織
本文將模仿《linux 內核源代碼情景分析》一書,以情景分析的方式對 yaffs2 文件系統的
源代碼進行分析。首先將分析幾組底層函數,如存儲空間的分配和釋放等;其次分析文件
邏輯地址映射;而後是垃圾收集機制;接下來......Sorry,本人還沒想好。:-)
4.說明
由於 yaffs2 貌似還在持續更新中,因此本文所列代碼可能和讀者手中的代碼不徹底一致。
另外,本文讀者應熟悉 C 語言,熟悉 NAND Flash 的基本概念(如 block 和 page)。
Ok,步入正題。首先分析存儲空間的分配。
5.NAND Flash 存儲空間分配和釋放
咱們知道,NAND Flash 的基本擦除單位是 Block,而基本寫入單位是 page。yaffs2 在分配
存儲空間的時候是以 page 爲單位的,不過在 yaffs2 中把基本 存儲單位稱爲 chunk,和
page 是同樣的大小,在大多數狀況下和 page 是一個意思。在下文中咱們使用 chunk 這個
詞,以保持和 yaffs2 的源代 碼一致。
咱們先看存儲空間的分配(在 yaffs_guts.c 中。這個文件也是 yaffs2 文件系統的核心部分):
static int yaffs_AllocateChunk(yaffs_Device * dev, int useReserve,
yaffs_BlockInfo **blockUsedPtr)
{
int retVal;
yaffs_BlockInfo *bi;
if (dev->allocationBlock < 0) {
/* Get next block to allocate off */
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()]
static int yaffs_FindBlockForAllocation(yaffs_Device * dev)
{
int i;
yaffs_BlockInfo *bi;
if (dev->nErasedBlocks < 1) {
/* Hoosterman we've got a problem.
* Can't get space to gc
*/
T(YAFFS_TRACE_ERROR,
(TSTR("yaffs tragedy: no more eraased blocks" TENDSTR)));
return -1;
}
dev->nErasedBlocks 記錄着器件內全部可供分配的 block 的數量。若是該值小於 1,那顯
然是有問題了。不但正常的分配請求沒法完成,就連垃圾收集都辦不到了。
for (i = dev->internalStartBlock; i <= dev->internalEndBlock; i++) {
dev->allocationBlockFinder++;
if (dev->allocationBlockFinder < dev->internalStartBlock
|| dev->allocationBlockFinder > dev->internalEndBlock) {
dev->allocationBlockFinder = dev->internalStartBlock;
internalStartBlock 和 internalEndBlock 分別是 yaffs2 使用的 block 的起始序號和結束
序號。也就是說 yaffs2 文件系統不必定要佔據整個 Flash,能夠只佔用其中的一部分。
dev->allocationBlockFinder 記錄着上次分配的塊的序號。若是已經分配到系統尾部,就
從頭從新開始搜索可用塊。
bi = yaffs_GetBlockInfo(dev, dev->allocationBlockFinder);
if (bi->blockState == YAFFS_BLOCK_STATE_EMPTY) {
bi->blockState = YAFFS_BLOCK_STATE_ALLOCATING;
dev->sequenceNumber++;
bi->sequenceNumber = dev->sequenceNumber;
dev->nErasedBlocks--;
T(YAFFS_TRACE_ALLOCATE,
(TSTR("Allocated block %d, seq %d, %d left" TENDSTR),
dev->allocationBlockFinder, dev->sequenceNumber,
dev->nErasedBlocks));
return dev->allocationBlockFinder;
}
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 上切下一個
chunk:
if (dev->allocationBlock >= 0) {
bi = yaffs_GetBlockInfo(dev, dev->allocationBlock);
retVal = (dev->allocationBlock * dev->nChunksPerBlock) +
dev->allocationPage;
bi->pagesInUse++;
yaffs_SetChunkBit(dev, dev->allocationBlock,
dev->allocationPage);
dev->allocationPage++;
dev->nFreeChunks--;
/* If the block is full set the state to full */
if (dev->allocationPage >= dev->nChunksPerBlock) {
bi->blockState = YAFFS_BLOCK_STATE_FULL;
dev->allocationBlock = -1;
}
if(blockUsedPtr)
*blockUsedPtr = bi;
return retVal;
}
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:
void yaffs_DeleteChunk(yaffs_Device * dev, int chunkId, int markNAND, int lyn)
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);
}
} else {
/* T(("Bad news deleting chunk %d\n",chunkId)); */
}
首先要判斷一下該 block 上是否確實存在着可釋放的 chunk。block 不能爲空,不能是壞塊。
YAFFS_BLOCK_STATE_NEEDS_SCANNING 代表正對該塊進行垃圾回收,咱們後面會分析;
YAFFS_BLOCK_STATE_NEEDS_SCANNING 在我手上的源代碼中彷佛沒有用到。
經過判斷之後,所作的工做和 chunk 分配函數相似,只是一個遞增統計值,一個遞減。遞
減統計值之後還要判斷該 block 上的 page 是否已所有釋放,如 果已所有釋放,而且不是
當前分配塊,就經過 yaffs_BlockBecameDirty 函數刪除該 block,只要能經過刪除操做
(不是壞塊),該 block 就又能夠用於分配了。
相比較來講,yaffs_SoftDeleteChunk 所作的工做就簡單多了。關鍵的代碼只有兩行:
static void yaffs_SoftDeleteChunk(yaffs_Device * dev, int chunk)
{
......
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 在這點上是相似的。
6.文件地址映射
上面說到,yaffs 文件系統在更新文件數據的時候,會分配一塊新的 chunk,也就是說,
一樣的文件偏移地址,在該地址上的數據更新前和更新後,其對應 的 flash 上的存儲地
址是不同的。那麼,如何根據文件內偏移地址肯定 flash 存儲地址呢?最容易想到的辦
法,就是在內存中維護一張映射表。因爲 flash 基本存儲單位是 chunk,所以,只要將以
chunk 描述的文件偏移量做爲表索引,將 flash chunk 序號做爲表內容,就能夠解決該問
題了。可是這個方法有幾個問題,首先就是在作 seek 操做的時候,要從表項 0 開始按序搜
索,對於大文件會消耗很 多時間;其次是在創建映射表的時候,沒法預計文件大小的變
化,因而就可能在後來的操做中頻繁釋放分配內存以改變表長,形成內存碎片。yaffs 的
解決方法 是將這張大的映射表拆分紅若干個等長的小表,並將這些小表組織成樹的結構,
方便管理。咱們先看小表的定義:
union yaffs_Tnode_union {
union yaffs_Tnode_union *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。
先看映射樹的節點的分配。
static yaffs_Tnode *yaffs_GetTnode(yaffs_Device * dev)
{
yaffs_Tnode *tn = yaffs_GetTnodeRaw(dev);
if(tn)
memset(tn, 0, (dev->tnodeWidth * YAFFS_NTNODES_LEVEL0)/8);
return tn;
}
調用 yaffs_GetTnodeRaw 分配節點,而後將獲得的節點初始化爲零。
static yaffs_Tnode *yaffs_GetTnodeRaw(yaffs_Device * dev)
{
yaffs_Tnode *tn = NULL;
/* If there are none left make more */
if (!dev->freeTnodes) {
yaffs_CreateTnodes(dev, YAFFS_ALLOCATION_NTNODES);
}
當前全部空閒節點組成一個鏈表,dev->freeTnodes 是這個鏈表的表頭。咱們假定已經沒
有空閒節點可用,需經過 yaffs_CreateTnodes 建立一批新的節點。
static int yaffs_CreateTnodes(yaffs_Device * dev, int nTnodes)
{
......
tnodeSize = (dev->tnodeWidth * YAFFS_NTNODES_LEVEL0)/8;
newTnodes = YMALLOC(nTnodes * tnodeSize);
mem = (__u8 *)newTnodes;
上面說過,葉節點中一個 Tnode 的位寬默認爲 16 位,也就是能夠表示 65536 個 chunk。對
於時下的大容量 flash,chunk 的大小爲 2K,因 此在默認狀況下 yaffs2 所能尋址的最大
flash 空間就是 128M。爲了能將 yaffs2 用於大容量 flash 上,代碼做者試圖經過兩種手段
解決這個 問題。第一種手段就是這裏的 dev->tnodeWidth,經過增長單個 Tnode 的位寬,
就能夠增長其所能表示的最大 chunk Id;另外一種手段是咱們後面將看到的 chunk group,
經過將若干個 chunk 合成一組用同一個 id 來表示,也能夠增長系統所能尋址的 chunk 範圍。
俺爲了簡單,分析的時候不考慮這兩種狀況,因 此 tnodeWidth 取默認值 16,也不考慮將
多個 chunk 合成一組的狀況,只在遇到跟這兩種狀況有關的代碼時做簡單說明。
在 32 位的系統中,指針的寬度爲 32 位,而 chunk id 的寬度爲 16 位,所以相同大小的
Tnode 組,能夠用來表示 N 個非葉 Tnode(做爲指針使用),也能夠用來表示 N * 2 個葉子
Tnode(做爲 chunk id 使用)。代碼中分別用 YAFFS_NTNODES_INTERNAL 和
YAFFS_NTNODES_LEVEL0 來表示。前者取值爲 8,後者取值爲 16。從這裏咱們也能夠看出
若將 yaffs2 用於 64 位系統須要做哪些修改。 針對上一段敘述的問題,俺覺得在內存不緊
張的狀況下,不如將葉節點 Tnode 和非葉節點 Tnode 都設爲一個指針的長度。
分配獲得所需的內存後,就將這些空閒空間組成 Tnode 鏈表:
for(i = 0; i < nTnodes -1; 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: Could not add tnodes to management list" 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 的工做就簡單多了,簡單的鏈表和
統計值操做:
static void yaffs_FreeTnode(yaffs_Device * dev, yaffs_Tnode * tn)
{
if (tn) {
tn->internal[0] = dev->freeTnodes;
dev->freeTnodes = tn;
dev->nFreeTnodes++;
}
}
看過 Tnode 的分配和釋放,咱們再來看看這些 Tnode 是如何使用的。在後文中,我
們把以 chunk 爲單位的文件內偏移稱做邏輯 chunk id,文件內容在 flash 上的實際存儲位
置稱做物理 chunk id。先看一個比較簡單的函數。
void yaffs_PutLevel0Tnode(yaffs_Device *dev, yaffs_Tnode *tn, unsigned pos,
unsigned val)
這個函數將某個 Tnode 設置爲指定的值。tn 是指向一組 Tnode 的指針;pos 是所要設置的
那個 Tnode 在該組 Tnode 中的索引;val 就是所要設置的值,也就是物理 chunk id。函數
名中的 Level0 指映射樹的葉節點。函數開頭幾行以下:
pos &= YAFFS_TNODES_LEVEL0_MASK;
val >>= dev->chunkGroupBits;
bitInMap = pos * dev->tnodeWidth;
wordInMap = bitInMap /32;
bitInWord = bitInMap & (32 -1);
mask = dev->tnodeMask << bitInWord;
上面說過,一組 Tnode 中的 8 個指針在葉節點這一層轉換成 16 個 16 位寬的 chunk Id,因
此須要 4 位二進制碼對其進行索引,這就是 YAFFS_TNODES_LEVEL0_MASK 的值。咱們還說
過這個 16 位值就是 chunk 在 flash 上的序號,當 flash 容量比較大, chunk 數量多時,16
位可能沒法給 flash 上的全部 chunk 編號,這種狀況下能夠增長 chunk id 的位寬,具體位
寬的值記錄在 dev->tnodeWidth 中。yaffs2 容許使用非字節對齊的 tnodeWidth,所以可
能出現某個 chunk id 跨 32 位邊界存儲的狀況。因此在下面的代碼中,須要分邊界前和邊
界後兩部分處理:
map[wordInMap] &= ~mask;
map[wordInMap] |= (mask & (val << bitInWord));
if(dev->tnodeWidth > (32-bitInWord)) {
bitInWord = (32 - bitInWord);
wordInMap++;;
mask = dev->tnodeMask >> (/*dev->tnodeWidth -*/ bitInWord);
map[wordInMap] &= ~mask;
map[wordInMap] |= (mask & (val >> bitInWord));
}
if 語句判斷當前 chunk 序號是否跨越當前 32 位邊界。整個代碼初看起來比較難理解,其
實只要將 dev->tnodeWidth 以 16 或 32 代入, 就很好懂了。還有一個相似的函數
yaffs_GetChunkGroupBase,返回由 tn 和 pos 肯定的一組 chunk 的起始序號,就不詳細分
析了。
如今咱們假設有這樣一個情景:已知文件偏移地址,要找到 flash 上對應的存儲地址,該
怎麼作呢?這項工做的主體是由函數 yaffs_FindLevel0Tnode 完成的。
static yaffs_Tnode *yaffs_FindLevel0Tnode(yaffs_Device * dev,
yaffs_FileStructure * fStruct,
__u32 chunkId)
{
yaffs_Tnode *tn = fStruct->top;
__u32 i;
int requiredTallness;
int level = fStruct->topLevel;
函數參數中,fStruct 是指向文件描述結構的指針,該結構保存着文件大小、映射樹層高、
映射樹頂層節點指針等信息。chunkId 是邏輯 chunk id。
fStruct->top 是映射樹頂層節點指針,fStruct->topLevel 是映射樹層高。注意:當只有
一層時,層高爲 0。
/* First check we're tall enough (ie enough topLevel) */
i = chunkId >> YAFFS_TNODES_LEVEL0_BITS;
requiredTallness = 0;
while (i) {
i >>= YAFFS_TNODES_INTERNAL_BITS;
requiredTallness++;
}
if (requiredTallness > fStruct->topLevel) {
/* Not tall enough, so we can't find it, return NULL. */
return NULL;
}
在 看這段代碼以前,咱們先用一個例子來回顧一下映射樹的組成。假定咱們有一個大小
爲 128K 的文件,flash 的 page 大小爲 2K,那麼咱們就須要 64 個 page(或者說 chunk)來
存儲該文件。一組 Tnode 的 size 是 8 個指針,或者 16 個 16 位整數,因此咱們須要 64 / 16
= 4 組 Tnode 來存儲物理 chunk 序號。這 4 組 Tnode 就是映射樹的葉節點,也就是 Level0
節點。因爲這 4 組 Tnode 在內存中不必定連續,因此 咱們須要另一組 Tnode,將其做爲
指針數組使用,這個指針數組的前 4 個元素分別指向 4 組 Level0 節點,而 fStruct->top
就指向這 組做爲指針數組使用的 Tnode。隨着文件長度的增大,所需的葉節點越多,非葉
節點也越多,樹也就越長越高。
回過頭來看代碼,首先是檢查函數參數 chunkId 是否超過文件長度。做爲非葉節點使用的
Tnode 每組有 8 個指針,須要 3 位二進制碼對其進行索引,所以 樹每長高一層,邏輯
chunkId 就多出 3 位。相反,每 3 位非零 chunkId 就表明一層非葉節點。while 循環根據這
個原則計算參數 chunkId 所 對應的樹高。若是樹高超過了文件結構中保存的樹高,那就
說明該邏輯 chunkId 已經超出文件長度了。經過文件長度檢查以後,一樣根據上面的原則,
就能夠 找到邏輯 chunkId 對應的物理 chunkId 了。具體的操做經過一個 while 循環完成:
/* Traverse down to level 0 */
while (level > 0 && tn) {
tn = tn->
internal[(chunkId >>
( YAFFS_TNODES_LEVEL0_BITS +
(level - 1) *
YAFFS_TNODES_INTERNAL_BITS)
) &
YAFFS_TNODES_INTERNAL_MASK];
level--;
}
return tn;
將返回值和邏輯 chunk id 做爲參數調用 yaffs_GetChunkGroupBase,就能夠獲得物理
chunk id 了。
下面咱們看另外一個情景,看看當文件長度增長的時候,映射樹是如何擴展的。主要
函數爲
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 主要用於
在刪除文件時資源的釋放。
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是如何實現處理這個目標的: html
在分析這段代碼以前,咱們再來回顧一下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是否能夠回收了。node