天天看點

F2FS源碼分析-6.4 [其他重要資料結構以及函數] 邏輯位址到實體位址映射的原理以及實作-f2fs_get_dnode_of_data和get_node_path函數F2FS實體位址尋址的實作

F2FS源碼分析系列文章

主目錄
一、檔案系統布局以及中繼資料結構
二、檔案資料的存儲以及讀寫
三、檔案與目錄的建立以及删除(未完成)
四、垃圾回收機制
五、資料恢複機制
六、重要資料結構或者函數的分析
  1. f2fs_summary的作用
  2. f2fs_journal的作用
  3. f2fs_map_block的作用
  4. get_dnode_of_data的作用
  5. get_node_page的作用(未完成)

F2FS實體位址尋址的實作

VFS的讀寫都依賴于實體位址的尋址。經典的讀流程,VFS會傳入inode以及page index資訊給檔案系統,然後檔案系統需要根據以上信心,找到實體位址,然後通路磁盤将其讀取出來。F2FS的實體位址尋址,是通過

f2fs_get_dnode_of_data

函數實作。

在執行這個

f2fs_get_dnode_of_data

函數之前,需要通過

set_new_dnode

函數進行對資料結構

struct dnode_of_data

進行初始化:

struct dnode_of_data {
	struct inode *inode;		/* VFS inode結構 */
	struct page *inode_page;	/* f2fs_inode對應的node page */
	struct page *node_page;		/* 使用者需要通路的實體位址所在的node page,有可能跟inode_page一樣*/
	nid_t nid;			/* 使用者需要通路的實體位址所在的node的nid,與上面的node_page對應*/
	unsigned int ofs_in_node;	/* 使用者需要通路的實體位址位于上面的node_page對應的addr數組第幾個位置 */
	bool inode_page_locked;		/* inode page is locked or not */
	bool node_changed;		/* is node block changed */
	char cur_level;			/* 目前node_page的層次,按直接通路或者簡介通路的深度區分 */
	char max_level;			/* level of current page located */
	block_t	data_blkaddr;		/* 使用者需要通路的實體位址 */
};

static inline void set_new_dnode(struct dnode_of_data *dn, struct inode *inode,
		struct page *ipage, struct page *npage, nid_t nid)
{
	memset(dn, 0, sizeof(*dn));
	dn->inode = inode;
	dn->inode_page = ipage;
	dn->node_page = npage;
	dn->nid = nid;
}
           

大部分情況下,僅需要傳入inode進行初始化:

然後根據需要通路的page index,執行

f2fs_get_dnode_of_data

函數尋找:

err = f2fs_get_dnode_of_data(&dn, page->index, type); // type類型影響了尋址的行為
blockt blkaddr = dn.data_blkaddr; // 獲得對應位置的實體位址資訊
           

接下來分析,函數是如何尋址,由于函數比較長和複雜,先分析一個比較重要的函數

get_node_path

函數的作用,它的用法是:

概念

在分析之前,我們要明确幾個概念。f2fs有三種node的類型,

f2fs_inode

direct_node

,和

indirect node

。其中

f2fs_inode

direct_node

都是直接儲存資料的位址指針,是以一般統稱為direct node,若有下橫線,例如

direct_node

,則表示資料結構

struct direct_node

,如果沒有下橫線,則表示直接儲存資料的位址指針的node,即

f2fs_inode

direct_node

。另外

indirect node

儲存的是間接尋址的node的nid,是以一般直接稱為為indirect node。

函數用法

int level;
int offset[4];
unsigned int noffset[4];
level = get_node_path(inode, page->index, offset, noffset);
           

這裡offset和noffset分别表示block offset和node offset,傳回的level表示尋址的深度,一共有4個深度,使用0~3表示:

level=0: 表示可以直接在

f2fs_inode

找到實體位址

level=1: 表示可以在

f2fs_inode->i_nid[0~1]

對應的

direct_node

能夠找到實體位址

level=2: 表示可以在

f2fs_inode->i_nid[2~3]

對應的

indirect_node

下的nid對應的

direct_node

能夠找到實體位址

level=3: 表示隻能在

f2fs_inode->i_nid[4]

對應

indirect_node

的nid對應的

indirect_node

的nid對應的

direct_node

才能找到位址

由于offset和noffset,表示的是實體位址尋址資訊,分别表示block偏移和direct node偏移來表示,它們是長度為4的數組,代表不同level 0~3 的尋址資訊。之後的函數可以通過offset和noffset将資料塊計算出來。

尋址原理

給定page->index,計算出level之後,offset[level]表示該page在所對應的direct node裡面的block的偏移,noffset[level]表示目前的node是屬于這個檔案的第幾個node(包括f2fs_node, direct_node, indirect_node),下面用幾個例子展示一下 (注意下面計算的是不使用xattr的f2fs版本,如果使用了xattr結果會不同,但是表示的含義是一樣的):

例子1: 實體位址位于f2fs_inode

例如我們要尋找page->index = 665的資料塊所在的位置,顯然655是位于

f2fs_inode

内,是以level=0,是以我們隻需要看offset[0]以及noffset[0]的資訊,如下圖。offset[0] = 665表示這個資料塊在目前direct node(注意: f2fs_inode也是direct node的一種)的位置;noffset[0]表示目前direct node是屬于這個檔案的第幾個node,由于f2fs_inode是第一個node,是以noffset[0] = 0。

level = 0 // 可以直接在f2fs_inode找到實體位址
offset[0] = 665 // 由于level=0,是以我們隻需要看offset[level]=offset[0]的資訊,這裡offset[0] = 665表示位址位于f2fs_inode->i_addr[665]
noffset[0] = 0 // 對于level=0的情況,即看noffset[0],因為level=0表示資料在唯一一個的f2fs_inode中,是以這裡表示inode。
           

例子2: 實體位址位于direct_node

例如我們要尋找page->index = 2113的資料塊所在的位置,它位于第二個direct_node,是以level=1。我們隻需要看offset[1]以及noffset[1]的資訊,如下圖。offset[1] = 172表示這個資料塊在目前direct node的位置,即

direct_node->addr[172]

;noffset[1]表示目前direct node是屬于這個檔案的第幾個node,由于它位于第二個direct_node,前面還有一個f2fs_inode以及一個direct node,是以這是第三個node,是以noffset[1] = 2。

level = 1 // 表示可以在f2fs_inode->i_nid[0~1]對應的direct_node能夠找到實體位址
offset[1] = 172 // 表示實體位址位于對應的node page的i_addr的第172個位置中,即direct_node->addr[172]
noffset[1] = 2 // 資料儲存在總共第三個node中 (1個f2fs_inode,2個direct_node)
           

例子3: 實體位址位于indirect_node

例如我們要尋找page->index = 4000的資料塊所在的位置,它位于第1個indirect_node的第2個direct_node中,是以level=2。我們隻需要看offset[2]以及noffset[2]的資訊,如下圖。offset[2] = 23表示這個資料塊在目前direct node的位置;noffset[2]表示目前direct node是屬于這個檔案的第幾個direct node,即這是第6個node。(1 * f2fs_inode + 2 * direct_node + 1 * indirect_node + 2 * direct node)。

offset[2] = 23
noffset[2] = 5
           

例子4: 實體位址位于indirect_node再indiret_node中 (double indirect node)

例如我們要尋找page->index = 2075624的資料塊所在的位置,它位于第一個double indirect_node的第一個indirect_node的第一個direct_node中,是以level=3。同理我們隻需要看offset[3]以及noffset[3]的資訊,如下,可以自己計算一下:

offset[3] = 17
noffset[3] = 2043
           

從上面可以知道

get_node_path

函數以後,執行可以根據offset和noffset直接知道page->index對應的實體位址,位于第幾個node page的第幾個offset對應的實體位址中。下面分析

f2fs_get_dnode_of_data

的原理:

int f2fs_get_dnode_of_data(struct dnode_of_data *dn, pgoff_t index, int mode)
{
	struct f2fs_sb_info *sbi = F2FS_I_SB(dn->inode);
	struct page *npage[4];
	struct page *parent = NULL;
	int offset[4];
	unsigned int noffset[4];
	nid_t nids[4];
	int level, i = 0;
	int err = 0;
	
	// 通過計算得到offset, noffset,進而知道位于第幾個node page的第幾個offset對應的實體位址中
	level = get_node_path(dn->inode, index, offset, noffset);
	if (level < 0)
		return level;

	nids[0] = dn->inode->i_ino;
	npage[0] = dn->inode_page;

	if (!npage[0]) {
		npage[0] = f2fs_get_node_page(sbi, nids[0]); // 擷取inode對應的f2fs_inode的node page
	}

	parent = npage[0];
	if (level != 0)
		nids[1] = get_nid(parent, offset[0], true); // 擷取f2fs_inode->i_nid
		
	dn->inode_page = npage[0];
	dn->inode_page_locked = true;

	for (i = 1; i <= level; i++) {
		bool done = false;

		if (!nids[i] && mode == ALLOC_NODE) { 
			// 建立模式,常用,寫入檔案時,需要node page再寫入資料,是以對于較大檔案,在這裡建立node page
			if (!f2fs_alloc_nid(sbi, &(nids[i]))) { // 配置設定nid
				err = -ENOSPC;
				goto release_pages;
			}

			dn->nid = nids[i];
			npage[i] = f2fs_new_node_page(dn, noffset[i]); // 配置設定node page
			//  如果i == 1,表示f2fs_inode->nid[0~1],即direct node,直接指派到f2fs_inode->i_nid中
			//  如果i != 1,表示parent是indirect node類型的,要指派到indirect_node->nid中
			set_nid(parent, offset[i - 1], nids[i], i == 1); 
			f2fs_alloc_nid_done(sbi, nids[i]);
			done = true;
		} else if (mode == LOOKUP_NODE_RA && i == level && level > 1) {
			// 預讀模式,少用,将node page全部預讀出來
			npage[i] = f2fs_get_node_page_ra(parent, offset[i - 1]);
			done = true;
		}
		if (i == 1) {
			dn->inode_page_locked = false;
			unlock_page(parent);
		} else {
			f2fs_put_page(parent, 1);
		}

		if (!done) {
			npage[i] = f2fs_get_node_page(sbi, nids[i]); // 根據nid擷取node page
		}
		if (i < level) {
			parent = npage[i]; // 注意這裡parent被遞歸地指派,目的是處理direct node和indrect node的指派問題
			nids[i + 1] = get_nid(parent, offset[i], false); // 計算下一個nid
		}
	}
	// 全部完成後,将結果指派到dn,然後退出函數
	dn->nid = nids[level];
	dn->ofs_in_node = offset[level];
	dn->node_page = npage[level];
	dn->data_blkaddr = datablock_addr(dn->inode, dn->node_page, dn->ofs_in_node); // 這個就是根據page index所得到的實體位址
	return 0;
}