1. 程式人生 > 其它 >檔案地址對映之yaffs_GetTnode

檔案地址對映之yaffs_GetTnode

yaffs檔案系統在更新檔案資料的時候,會分配一塊新的chunk,也就是說,同樣的檔案偏移地址,在該地址上的資料更新前和更新後,其對應的flash上的儲存地址是不一樣的。那麼,如何根據檔案內偏移地址確定flash儲存地址呢?最容易想到的辦法,就是在記憶體中維護一張對映表。由於 flash基本儲存單位是chunk,因此,只要將以chunk描述的檔案偏移量作為表索引,將flash chunk序號作為表內容,就可以解決該問題了。但是這個方法有幾個問題,首先就是在做seek操作的時候,要從表項0開始按序搜尋,對於大檔案會消耗很多時間;其次是在建立對映表的時候,無法預計檔案大小的變化,於是就可能在後來的操作中頻繁釋放分配記憶體以改變表長,造成記憶體碎片。yaffs的解決方法是將這張大的對映表拆分成若干個等長的小表,並將這些小表組織成樹的結構,方便管理。我們先看小表的定義:

struct yaffs_tnode {
struct yaffs_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。

先看對映樹的節點的分配。

struct yaffs_tnode *yaffs_get_tnode(struct yaffs_dev *dev)
{
struct yaffs_tnode *tn = yaffs_alloc_raw_tnode(dev);
if (tn) {
memset(tn, 0, dev->tnode_size);
dev->n_tnodes++;
}
dev->checkpoint_blocks_required = 0;/* force recalculation */
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; 
}

(其實在最新版本的yaffs中已經加入了slab緩衝區,這樣提高了效率)上面說過,葉節點中一個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--; 
 }

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