ext4塊分配機制及源碼分析


對於文件系統來說,碎片問題是永恆的話題,碎片少的文件系統不僅能夠存儲更多的數據而且能夠帶來顯著的性能提升,為此ext4文件系統從inode分配到塊分配都做了相當多的努力。本文主要是結合源碼分析ext4文件系統的塊分配機制,所采用的源碼版本是4.20。

ext4文件系統采用多種分配方式相結合的方式解決碎片問題,此外還需要考慮到分配效率、大文件小文件、超大文件、分配后的讀寫效率等問題,其機制不可謂不復雜,限於當前的水平難免有敘述錯誤和未敘述到的地方,歡迎一起交流。

塊分配機制

預分配

給文件預留空間,當需要分配的時候從預留的空間里面分配能夠降低碎片數量。ext4文件系統考慮到大文件和小文件的不同需求,對於大文件,ext4采用per inode的方式給每個文件預留分配空間;對於小文件,ext4的策略是盡量讓這些文件都集中存儲在一起,采用的方式per_cpu locality group的方式。至於什么是per_inode,什么是per_cpu locality group就留到具體的章節詳說吧。

mballoc多塊分配

相比於ext2文件系統的塊分配器一次性只能分配一個數據塊,效率低下;ext4采用了多塊分配機制,一次性能夠分配多塊的空間,效率非常高的同時碎片也會相當的少。ext4的開發人員參考了linux內核內存管理的buddy算法來實現多塊分配,如果讀者熟悉內存管理的buddy算法的話,理解起來會快很多。

延遲分配

緩存IO的寫命令會先將數據寫入頁緩存中,然后立即返回,待到某個特定的時候再將緩存中的數據回寫到磁盤上。ext4利用此機制,在數據寫到頁緩存中的時候不做磁盤空間的分配,當寫IO持續累積在統一回寫到磁盤上的時候再統一地進行分配磁盤空間,一次性大的磁盤空間分配所產生的碎片肯定比多次小IO產生的碎片少,再結合mballoc多塊分配一次性分配多塊物理磁盤空間能夠繼續減少碎片。

但是延遲分配有一個缺點,那就是在回寫時會進行磁盤空間的分配,這會一定程度上影響刷盤的效率,而且會導致部分IO的延時會突然拔高一段時間,對於追求延時穩定的上層應用來說會有影響,例如數據庫系統的某一次SQL執行時間突然變長等。

bigAlloc

在ext4的塊組中有一個塊叫做block bitmap數據塊位圖,用於記錄塊組中的塊使用情況,一個ext4塊一般情況下是4k,也就是32768個bit位,一個bit位代表一個4k塊的使用情況,32768個bit位就能表示128M的空間的使用情況,這也是一般情況下ext4的一個塊組是128M的原因。

但是隨着單個磁盤空間的越來越大(TB級),如果ext4文件系統任然采用4k為一個文件塊的方式來管理數據,那么相對應的位圖等元數據也會增多,這方面的管理開銷也會越來越大。

暴力地增大單個塊的大小是個看起來簡單的方法,但是由於塊大小跟內核的內存管理、頁緩存塊緩存管理極為緊密,簡單地增加文件塊大小對於代碼開發的工作量將是巨大的。聰明的內核開發者引入了bigalloc的策略,隨之而來的是一個叫“block cluster”的概念,它是一系列4k的塊的集合,當需要分配空間時以block cluster為單位進行分配而不是以4k 塊為單位進行分配,與此同時,塊組里面數據塊位圖的一個bit位也不在表示一個4k塊,而是一個block cluster,當然我們的塊組的大小也隨之增大了(block cluster的大小在文件系統格式化的時候就決定了,因此塊組的大小也就決定了)。

關於bigalloc特性,內核有一個“歷史包袱”,那就是當初開發者在修改代碼時並沒有將內核代碼的所有“blocks”都換成“cluster”,估計是為了盡快上線功能吧,因此后續我們在分析代碼的時候一會兒代碼是block,一會兒代碼是cluster,這會讓人很迷惑,需要記住的一點是看到block,其實它是cluster。

block cluster的大小一般設置為64k,如果文件系統只存儲大文件,那么可以將block cluster設置為1M。假如一次性給文件分配了1M的空間,而文件並沒有用完這片空間,那么下次在文件要分配的時候會將這部分未用完的空間也利用上就不分配了。值得注意的是,如果設置了過大的block cluster,而文件系統又存儲了很多的小文件,那么會浪費很多的空間。

持久預分配

有的上層應用需要在文件開始寫入數據之前就准備好足夠的空間,例如下載軟件會先創建同等大小的文件然后將下載的數據寫入到文件等,ext4文件系統提供了持久預分配接口,能夠一次性就給文件分配好所有的空間,效率更高,碎片更少。

其他策略

除了上述的幾大分配策略以外,ext4還有很多的小策略以及分配inode的策略也會影響文件系統的性能:

1)在文件剛剛創建的時候ext4文件系統會為其分配8k的空間,然后在文件close的時候回收未用完的空間,以應對創建文件立馬寫入的場景以及臨時小文件場景。

2)盡量將文件數據和文件的inode放在同一個塊組中,以減少磁盤尋址耗時(機械硬盤)。

3) 如果啟用了flexible塊組特性,當4個以上的塊組組成一個flexible塊組的時候,將flexible塊組內的第一個塊組的inode分配給目錄而不是普通文件。

4)ext4盡量將文件inode與它的目錄inode放在同一個塊組中,普遍認為目錄下的文件是“相關的”,讀取某一個文件也會同時讀取其他文件。

5)ext4文件系統在分配inode的時候會將頂層的目錄盡量分散開,而底層的目錄盡量聚集在一起。

6)針對下層塊設備是Raid的場景ext4采用與stripe對齊的方式分配,可以加速讀寫。你可以在創建文件系統時通過設置參數"-E stride=?, stripe=?"指定按照stripe對齊得方式分配,stride就是raid的chunk size 除以文件系統的block大小的倍數,stripe就是stride * raid的數據盤個數。對於raid5或者raid6這種來說滿條帶的寫能夠提升非常大的性能,因為raid引擎不用多次讀取計算和寫入校驗值。至於你的raid的chunk size應該配置多大就得根據上層業務經常下發的IO大小決定,如果IO太大可以指定較小的chunk size使得數據能夠分到各個磁盤上,如果經常下發的IO太小可以指定較大一點的chunk size,最好有benchmark可以測試這些參數帶來的影響,選擇最合適的參數。

7)ext4最終還可以用e4defrag工具整理碎片文件。

源碼分析

代碼流程主要是梳理預分配和多塊分配的公共流程和數據結構,而bigalloc、延遲分配以及各種小策略會穿插在其中,持久預分配單獨分析。

我們的流程從ext4_ext_map_blocks()函數開始,這個函數的作用是將一段連續的邏輯塊號映射到連續的物理塊號,在開始梳理流程之前我們需要了解一個結構體:ext4_map_blocks。

/*
	文件系統將磁盤空間划分為4k的塊(通常是4k),每一個4k的塊有一個物理塊號,物理塊號從0開始,由於當前存放文件系統的磁盤空間可能是個分區,因此在通用塊層根據下發的物理塊號計算真正在磁盤上的sector(通常是512B)時要加上分區的sector偏移。
	邏輯塊號是相對於文件而言的,對於上層應用來說文件的內容是連續的,而實際的存儲物理塊號可能不連續,這個4k的塊相對於文件起始位置的塊號就是邏輯塊號。
 */

/*
	ext4_map_blocks用來描述邏輯塊號和物理塊號的映射關系
 */
struct ext4_map_blocks {
	ext4_fsblk_t m_pblk; // 物理塊號,相對於文件系統而言的
	ext4_lblk_t m_lblk; // 邏輯塊號,相對於文件的
	unsigned int m_len; // 長度,單位為文件塊
	unsigned int m_flags; // 映射關系的各種標記,參考EXT4_MAP_NEW附近的宏定義
};

ext4_ext_map_blocks()函數:

/* 我們首先來看參數:
		handle_t *handle: 是ext4文件系統的日志系統,此處用於保護分配時的元數據的修改
		struct inode *inode: 要分配的文件的inode
		struct ext4_map_blocks *map: 映射關系,找到了邏輯塊號的物理塊號就填入其中
		int flags: 調用方對於此次分配行動的“要求”,參考EXT4_GET_BLOCKS_CREATE附件的宏定義
	其次是返回值:
		返回此次映射的長度,這個值可能會小於調用方要求的長度(ext4_map_blocks里的m_len),調用方需要處理這種情況
 */
int ext4_ext_map_blocks(handle_t *handle, struct inode *inode,
			struct ext4_map_blocks *map, int flags)
{
    struct ext4_ext_path *path = NULL;
	struct ext4_extent newex, *ex, *ex2;
	struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
	ext4_fsblk_t newblock = 0;
	int free_on_err = 0, err = 0, depth, ret;
	unsigned int allocated = 0, offset = 0;
	unsigned int allocated_clusters = 0;
	struct ext4_allocation_request ar;
	ext4_lblk_t cluster_offset;
	bool map_from_cluster = false;

	ext_debug("blocks %u/%u requested for inode %lu\n",
		  map->m_lblk, map->m_len, inode->i_ino);
	trace_ext4_ext_map_blocks_enter(inode, map->m_lblk, map->m_len, flags);

    /*
    	ext4_ext_map_blocks()函數實在太長了,我們將其分為兩個部分分析,此處為第一部分,主要是從ext4的extent樹中采用二分法的方式尋找有無已經映射了的extent,查找期間如果某個extent樹節點沒有加載到頁緩存中,則會發起讀IO從磁盤讀取並加載到頁緩存中,並且加載的extent(包括空洞)會記錄到ext4_inode_info的struct ext4_es_tree i_es_tree紅黑樹中方便查詢。
    */
    
    /*
    	此處涉及到很多的ext4文件系統extent樹的知識,這里的分析從簡。
    */
    
	/* find extent for this block */
    // path表示的是從extent樹的根節點到extent所經過的所有extent_idx路徑
	path = ext4_find_extent(inode, map->m_lblk, NULL, 0);
	if (IS_ERR(path)) {
		err = PTR_ERR(path);
		path = NULL;
		goto out2;
	}

	depth = ext_depth(inode);

	/*
	 * consistent leaf must not be empty;
	 * this situation is possible, though, _during_ tree modification;
	 * this is why assert can't be put in ext4_find_extent()
	 */
	if (unlikely(path[depth].p_ext == NULL && depth != 0)) {
		EXT4_ERROR_INODE(inode, "bad extent address "
				 "lblock: %lu, depth: %d pblock %lld",
				 (unsigned long) map->m_lblk, depth,
				 path[depth].p_block);
		err = -EFSCORRUPTED;
		goto out2;
	}

	ex = path[depth].p_ext;
	if (ex) { // 如果找到了extent,證明已經映射了
		ext4_lblk_t ee_block = le32_to_cpu(ex->ee_block); // 起始邏輯塊
		ext4_fsblk_t ee_start = ext4_ext_pblock(ex); // 起始物理塊
		unsigned short ee_len;


		/*
		 * unwritten extents are treated as holes, except that
		 * we split out initialized portions during a write.
		 */
		// 如果一個extent是初始化了的則ee_len <= 32768,
		// 如果一個extent是沒有初始化的則ee_len > 32768,且真實長度為ee_len - 32768
		// 因此初始化了的最大長度為32768,未初始化的最大長度為INVALID_U16(65535)- 32768 = 32767
		ee_len = ext4_ext_get_actual_len(ex);

		trace_ext4_ext_show_extent(inode, ee_block, ee_start, ee_len);

		/* if found extent covers block, simply return it */
		if (in_range(map->m_lblk, ee_block, ee_len)) {
			newblock = map->m_lblk - ee_block + ee_start;
			/* number of remaining blocks in the extent */
			allocated = ee_len - (map->m_lblk - ee_block); // 分配的長度可能小於要求分配的長度
			ext_debug("%u fit into %u:%d -> %llu\n", map->m_lblk,
				  ee_block, ee_len, newblock);

			/*
			 * If the extent is initialized check whether the
			 * caller wants to convert it to unwritten.
			 */
			// 調用者想要清零
			if ((!ext4_ext_is_unwritten(ex)) &&
			    (flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN)) {
				allocated = convert_initialized_extent(
						handle, inode, map, &path,
						allocated);
				goto out2;
			} else if (!ext4_ext_is_unwritten(ex))
				goto out;

			ret = ext4_ext_handle_unwritten_extents(
				handle, inode, map, &path, flags,
				allocated, newblock);
			if (ret < 0)
				err = ret;
			else
				allocated = ret;
			goto out2;
		}
	}

	/*
		到這里就說明傳入的邏輯塊范圍沒有映射物理塊,
		如果調用者不需要我們分配實際的空間此處就返回了,可能的情況有兩種:
			1、ext4的延遲分配就是這樣的,寫IO流程里面不需要分配空間,回刷的時候才分配空間。
			2、可能這段空間是文件空洞,不需要分配物理塊,只需要內存中有記錄即可
	 */
	if ((flags & EXT4_GET_BLOCKS_CREATE) == 0) {
		ext4_lblk_t hole_start, hole_len;

		hole_start = map->m_lblk;
		hole_len = ext4_ext_determine_hole(inode, path, &hole_start);
		/*
			記錄這個空洞到ext4_inode_info的struct ext4_es_tree i_es_tree紅黑樹中
		 */
		ext4_ext_put_gap_in_cache(inode, hole_start, hole_len);

		/* Update hole_len to reflect hole size after map->m_lblk */
		if (hole_start != map->m_lblk)
			hole_len -= map->m_lblk - hole_start;
		map->m_pblk = 0;
		map->m_len = min_t(unsigned int, map->m_len, hole_len);

		goto out2;
	}
    
    ......
}

下面開始梳理ext4_ext_map_blocks()函數的后半段,也就是分配物理塊,不過首先我們需要了解一些結構體以及一些宏:

1)struct ext4_extent

/*
	ext4_extent是extent樹的葉子節點,用於在物理磁盤上描述邏輯塊號和物理塊號的映射關系。
*/
struct ext4_extent {
	__le32	ee_block;	/* extent包含的第一個邏輯塊 */
	__le16	ee_len;		/* extent包含的數據塊的個數 */
	__le16	ee_start_hi;	/* 物理數據塊的高16位 */
	__le32	ee_start_lo;	/* 物理數據塊的低32位 */
};

2)struct ext4_allocation_request

/*
	ext4_allocation_request表示的是一次分配的具體請求。
	ext4_lblk_t是logic block的意思,代表邏輯塊號
	ext4_fsblk_t是物理塊號
*/
struct ext4_allocation_request { // 塊分配請求
	/* 要分配物理塊的文件inode */
	struct inode *inode;
	/* 要分配的長度 */
	unsigned int len;
	/* 要分配物理塊號的邏輯塊號 */
	ext4_lblk_t logical;
	/* the closest logical allocated block to the left */
	ext4_lblk_t lleft;
	/* the closest logical allocated block to the right */
	ext4_lblk_t lright;
	/* phys. target (a hint) */
    // 目標物理塊號,分配器會首先看看在某些“相鄰”的物理位置能不能分配,這些相鄰的物理位置相當程度地保證數據局部性,具體可參考ext4_ext_find_goal()函數
	ext4_fsblk_t goal; 
	/* phys. block for the closest logical allocated block to the left */
	ext4_fsblk_t pleft;
	/* phys. block for the closest logical allocated block to the right */
	ext4_fsblk_t pright;
	/* flags. see above EXT4_MB_HINT_* */
	unsigned int flags;
};

3)EXT4_LBLK_COFF

/*
	s是ext4_sb_info,字段s_cluster_ratio表示的是一個block cluster的block數目,它的值是2的冪,因此這個宏表示的是lblk在cluster里面的偏移。
*/
#define EXT4_LBLK_COFF(s, lblk) ((lblk) &				\
				 ((ext4_lblk_t) (s)->s_cluster_ratio - 1))

開始分配:

int ext4_ext_map_blocks(handle_t *handle, struct inode *inode,
			struct ext4_map_blocks *map, int flags)
{
    ......
	// 開始分配物理塊
	/*
	 * Okay, we need to do block allocation.
	 */
	newex.ee_block = cpu_to_le32(map->m_lblk); // 起始邏輯塊
	cluster_offset = EXT4_LBLK_COFF(sbi, map->m_lblk); // 起始邏輯塊在cluster內的偏移

	/*
	 * If we are doing bigalloc, check to see if the extent returned
	 * by ext4_find_extent() implies a cluster we can use.
	 */
	// bigalloc上一次分配的cluster可能還沒有用完,剩余的空間可以用於這一次的分配
    // 值得注意的時候如果本次的起始位置在cluster內的偏移是0,那么證明這一次是新分配一個cluster,因此也就沒有必要檢查了
    // ext4_extent ex可以理解為離這一次分配邏輯塊段左邊最近的邏輯塊段
    // 此時cluster、extent、request region之間的重疊關系就決定了能否使用前面分配的cluster未使用的區域
	if (cluster_offset && ex &&
		// 檢查重疊關系
	    get_implied_cluster_alloc(inode->i_sb, map, ex, path)) {
		ar.len = allocated = map->m_len;
		newblock = map->m_pblk;
		map_from_cluster = true;
		goto got_allocated_blocks;
	}

	/* find neighbour allocated blocks */
    // 如果沒法利用左邊的extent分配的cluster未使用完的區域,則搜索右邊有無可用區域
    // 設想當前要分配的這段邏輯塊段是在一個空洞內,那么它的前后邏輯塊都是被分配了的extent
    // 往ext4_allocation_request里記錄要分配的起始邏輯塊地址
	ar.lleft = map->m_lblk;
    // 首先記錄左邊的extent的邏輯塊和物理塊地址,在預估后續的文件大小時使用
	err = ext4_ext_search_left(inode, path, &ar.lleft, &ar.pleft);
	if (err)
		goto out2;
	ar.lright = map->m_lblk;
	ex2 = NULL;
    // 搜索右邊的extent,找到離分配段最近的extent,存到ex2中
	err = ext4_ext_search_right(inode, path, &ar.lright, &ar.pright, &ex2);
	if (err)
		goto out2;

	/* Check if the extent after searching to the right implies a
	 * cluster we can use. */
	// 如果右邊有extent則可以看看有沒有未使用的cluster的區域,有則將這段區域分配出去
	if ((sbi->s_cluster_ratio > 1) && ex2 &&
	    get_implied_cluster_alloc(inode->i_sb, map, ex2, path)) {
		ar.len = allocated = map->m_len;
		newblock = map->m_pblk;
		map_from_cluster = true;
		goto got_allocated_blocks;
	}

	/*
		如果要分配的長度大於最大值,將分配長度對齊到最大值
	 */
	if (map->m_len > EXT_INIT_MAX_LEN &&
	    !(flags & EXT4_GET_BLOCKS_UNWRIT_EXT))
		map->m_len = EXT_INIT_MAX_LEN;
	else if (map->m_len > EXT_UNWRITTEN_MAX_LEN &&
		 (flags & EXT4_GET_BLOCKS_UNWRIT_EXT))
		map->m_len = EXT_UNWRITTEN_MAX_LEN;

	/* Check if we can really insert (m_lblk)::(m_lblk + m_len) extent */
	newex.ee_len = cpu_to_le16(map->m_len);
    // 檢查分配的區域跟右邊的extent是否有重疊,有則縮短分配的長度
	err = ext4_ext_check_overlap(sbi, inode, &newex, path);
	if (err)
        // err表示有重疊,那么用新的調整了的分配長度
		allocated = ext4_ext_get_actual_len(&newex);
	else
        // 否則使用調用者傳入的長度
		allocated = map->m_len;

	/* 分配一個新的物理塊段 */
	ar.inode = inode;
    // 先找到目標區域,開始分配的時候先檢查能否從目標區域分配物理塊
	ar.goal = ext4_ext_find_goal(inode, path, map->m_lblk);
	ar.logical = map->m_lblk;
	/*
	 * We calculate the offset from the beginning of the cluster
	 * for the logical block number, since when we allocate a
	 * physical cluster, the physical block should start at the
	 * same offset from the beginning of the cluster.  This is
	 * needed so that future calls to get_implied_cluster_alloc()
	 * work correctly.
	 */
	offset = EXT4_LBLK_COFF(sbi, map->m_lblk); // 起始邏輯塊在cluster內的偏移
	ar.len = EXT4_NUM_B2C(sbi, offset+allocated); // 把要分配的長度換算成cluster數量
    											  // 有可能因為偏移的原因占用多個cluster
	// 將goal和logical都對齊到cluster,方便分配
    ar.goal -= offset; 
	ar.logical -= offset;
    // 如果inode是普通文件,則置上EXT4_MB_HINT_DATA標記
	if (S_ISREG(inode->i_mode))
		ar.flags = EXT4_MB_HINT_DATA;
	else
		/* disable in-core preallocation for non-regular files */
		ar.flags = 0;
    // 將調用者的“要求”一一轉換到ext4_allocation_request中
	if (flags & EXT4_GET_BLOCKS_NO_NORMALIZE)
		ar.flags |= EXT4_MB_HINT_NOPREALLOC;
	if (flags & EXT4_GET_BLOCKS_DELALLOC_RESERVE)
		ar.flags |= EXT4_MB_DELALLOC_RESERVED;
	if (flags & EXT4_GET_BLOCKS_METADATA_NOFAIL)
		ar.flags |= EXT4_MB_USE_RESERVED;
	
	newblock = ext4_mb_new_blocks(handle, &ar, &err); // 核心分配函數!
	if (!newblock)
		goto out2;
	ext_debug("allocate new block: goal %llu, found %llu/%u\n",
		  ar.goal, newblock, allocated);
	free_on_err = 1;
	allocated_clusters = ar.len;
	ar.len = EXT4_C2B(sbi, ar.len) - offset; // 分配到的cluster還原到要分配的長度
	if (ar.len > allocated)
		ar.len = allocated;
    
    ......
}

get_implied_cluster_alloc()函數:

cluster、extent、request region之間的重疊關系決定了此次分配的具體長度,能夠走到這個函數的證明request region的起始邏輯地址是不在extent的邏輯地址范圍內的。

static int get_implied_cluster_alloc(struct super_block *sb,
				     struct ext4_map_blocks *map,
				     struct ext4_extent *ex,
				     struct ext4_ext_path *path)
{
	struct ext4_sb_info *sbi = EXT4_SB(sb);
	ext4_lblk_t c_offset = EXT4_LBLK_COFF(sbi, map->m_lblk);
	ext4_lblk_t ex_cluster_start, ex_cluster_end;
	ext4_lblk_t rr_cluster_start;
	ext4_lblk_t ee_block = le32_to_cpu(ex->ee_block);
	ext4_fsblk_t ee_start = ext4_ext_pblock(ex);
	unsigned short ee_len = ext4_ext_get_actual_len(ex);

	/* The extent passed in that we are trying to match */
	ex_cluster_start = EXT4_B2C(sbi, ee_block); // extent起始邏輯地址所在的cluster
	ex_cluster_end = EXT4_B2C(sbi, ee_block + ee_len - 1); // extent結束邏輯地址所在的cluster

	/* The requested region passed into ext4_map_blocks() */
    // request region的起始邏輯地址所在的cluster
	rr_cluster_start = EXT4_B2C(sbi, map->m_lblk);
	// 當起始cluster或者結束cluster重合時才有可能分配
	if ((rr_cluster_start == ex_cluster_end) ||
	    (rr_cluster_start == ex_cluster_start)) {
       	/*
       	 第一種情況,”|==|“表示分配的長度以及起始物理塊: 
       	 *		 |--- cluster # N--|
 		 *    |--- extent ---|	|---- requested region ---|
 		 *			            |==|
       	 */
		if (rr_cluster_start == ex_cluster_end)
			ee_start += ee_len - 1;
        // EXT4_PBLK_CMASK表示的是ee_start所在的cluster的起始的物理地址
        // c_offset表示的是requested region的起始邏輯地址在cluster內的偏移量
		map->m_pblk = EXT4_PBLK_CMASK(sbi, ee_start) + c_offset;
		map->m_len = min(map->m_len,
				 (unsigned) sbi->s_cluster_ratio - c_offset);
		/*
		 * 第二種情況:
		 	細分之下有兩種情況,這也是第51行代碼取最小值的原因:
         *   |--------- cluster # N ----------------|
         *	   |--- requested region --|   |------- extent ----|
         *	   |=======================|
         *
		 *   |--------- cluster # N-------------|
		 *		       |------- extent ----|
		 *	   |--- requested region ---|
		 *	   |=======|
		 */

		if (map->m_lblk < ee_block)
			map->m_len = min(map->m_len, ee_block - map->m_lblk);

		/*
		 * 第三種情況,此時要考慮到右邊的extent的分配情況,其實這里沒有畫出來,細分之下也類似第二種情況有兩種,因此第63行也有個取最小值的動作:
		 *
		 *          |------------- cluster # N-------------|
		 * |----- ex -----|                  |---- ex_right ----|
		 *                  |------ requested region ------|
		 *                  |================|
		 */
		if (map->m_lblk > ee_block) {
			ext4_lblk_t next = ext4_ext_next_allocated_block(path);
			map->m_len = min(map->m_len, next - map->m_lblk);
		}

		trace_ext4_get_implied_cluster_alloc_exit(sb, map, 1);
		return 1;
	}

	trace_ext4_get_implied_cluster_alloc_exit(sb, map, 0);
	return 0;
}

以上是bigalloc的情況下從前面已經分配了的cluster未用完的區域分配的情況,接下來分析分配新物理塊的流程:

這個流程主要有兩個函數:ext4_ext_find_goal()函數和ext4_mb_new_blocks()函數,

// 返回的是一個物理塊號,分配的時候檢查從這個物理塊號起始分配是否可行
static ext4_fsblk_t ext4_ext_find_goal(struct inode *inode,
			      struct ext4_ext_path *path,
			      ext4_lblk_t block)
{
	if (path) {
		int depth = path->p_depth;
		struct ext4_extent *ex;

		ex = path[depth].p_ext; // path的終點是數據extent,即ext4_extent
		                        // path是從根節點到葉子節點的路徑
		if (ex) {
			ext4_fsblk_t ext_pblk = ext4_ext_pblock(ex);
			ext4_lblk_t ext_block = le32_to_cpu(ex->ee_block);
			/*
				舉兩個例子:
				|---request region---|  |---extent---|
				|-> goal
				或者
				|---extent---|  |---request region---|
								|-> goal
			*/
			if (block > ext_block) // 尋找與其最近的空閑物理塊,左邊或者右邊
				return ext_pblk + (block - ext_block);
			else
				return ext_pblk - (ext_block - block);
		}

		/* it looks like index is empty;
		 * try to find starting block from index itself */
		if (path[depth].p_bh) // index節點為空
			return path[depth].p_bh->b_blocknr; // 最后一個extent block對應的物理塊號
	}

	/* OK. use inode's group */
	return ext4_inode_to_goal_block(inode); // inode剛剛創建時,沒有path
}

ext4_inode_to_goal_block()函數:

ext4_fsblk_t ext4_inode_to_goal_block(struct inode *inode)
{
	struct ext4_inode_info *ei = EXT4_I(inode);
	ext4_group_t block_group;
	ext4_grpblk_t colour;
	int flex_size = ext4_flex_bg_size(EXT4_SB(inode->i_sb));
	ext4_fsblk_t bg_start;
	ext4_fsblk_t last_block;
	// 找到inode所在的block group
	block_group = ei->i_block_group;
    // EXT4_FLEX_SIZE_DIR_ALLOC_SCHEME的值為4
    // 如果ext4啟用了flexible group特性,並且每個flexible group的block group數量大於等於4
    // 則將第一個block group用於目錄和特殊文件,以加速目錄的訪問和fsck的時間
	if (flex_size >= EXT4_FLEX_SIZE_DIR_ALLOC_SCHEME) {
		/*
		 * If there are at least EXT4_FLEX_SIZE_DIR_ALLOC_SCHEME
		 * block groups per flexgroup, reserve the first block
		 * group for directories and special files.  Regular
		 * files will start at the second block group.  This
		 * tends to speed up directory access and improves
		 * fsck times.
		 */
        // 此時的block_group為flexible group的第一個block group的編號
		block_group &= ~(flex_size-1);
        // 如果文件是普通文件,則將block group號加一
		if (S_ISREG(inode->i_mode))
			block_group++;
	}
    // bg_start為block group的第一個塊的塊號(物理塊號)
	bg_start = ext4_group_first_block_no(inode->i_sb, block_group);
	last_block = ext4_blocks_count(EXT4_SB(inode->i_sb)->s_es) - 1;

	/*
	 * If we are doing delayed allocation, we don't need take
	 * colour into account.
	 */
	if (test_opt(inode->i_sb, DELALLOC))
		return bg_start;
	// 計算一個隨機值color,最終goal會在flexible group的隨機的一個block group上
    // 當flex_group的block數量大於4時goal才確定不會在其第一個block group上
	if (bg_start + EXT4_BLOCKS_PER_GROUP(inode->i_sb) <= last_block)
		colour = (current->pid % 16) *
			(EXT4_BLOCKS_PER_GROUP(inode->i_sb) / 16);
	else
		colour = (current->pid % 16) * ((last_block - bg_start) / 16);
	return bg_start + colour;
}

正式第分配物理塊,首先會調用ext4_mb_use_preallocated()函數從預分配空間里分配物理塊,如果分配失敗則調用ext4_mb_regular_allocator()函數進行常規的分配。

首先來看分配過程種的兩個結構體:

struct ext4_free_extent結構體:

// 表示分配的請求和結果
struct ext4_free_extent {
	ext4_lblk_t fe_logical; // 要分配的起始邏輯塊號
	ext4_grpblk_t fe_start;	// 分配的起始
	ext4_group_t fe_group; // 從哪個block group分配的
	ext4_grpblk_t fe_len;	// 分配的長度,單位為cluster
};

struct ext4_allocation_context結構體:

// 對分配行為本身的描述,記錄分配的中間過程數據以及分配的結果
struct ext4_allocation_context { 
	struct inode *ac_inode; // 要分配的inode
	struct super_block *ac_sb; // inode的super_block

	/* original request */
	struct ext4_free_extent ac_o_ex; // 最開始的分配請求

	/* goal request (normalized ac_o_ex) */
	struct ext4_free_extent ac_g_ex; // 如果goal能分配成功

	/* the best found extent */
	struct ext4_free_extent ac_b_ex; // 最終的分配結果

	/* copy of the best found extent taken before preallocation efforts */
	struct ext4_free_extent ac_f_ex; // ac_b_ex的一份拷貝
	 // 分配過程種的相關信息
	__u16 ac_groups_scanned;
	__u16 ac_found;
	__u16 ac_tail;
	__u16 ac_buddy;
	__u16 ac_flags;		/* allocation hints */
	__u8 ac_status;
	__u8 ac_criteria;
	__u8 ac_2order;		/* if request is to allocate 2^N blocks and
				 * N > 0, the field stores N, otherwise 0 */
	__u8 ac_op;		/* operation, for history only */
    // 跟mballoc相關的bit位信息
	struct page *ac_bitmap_page; 
	struct page *ac_buddy_page;
    // 預分配相關信息
	struct ext4_prealloc_space *ac_pa; 
	struct ext4_locality_group *ac_lg;
};

ext4_mb_new_blocks()函數:

ext4_fsblk_t ext4_mb_new_blocks(handle_t *handle,
				struct ext4_allocation_request *ar, int *errp)
{
	int freed;
	struct ext4_allocation_context *ac = NULL;
	struct ext4_sb_info *sbi;
	struct super_block *sb;
	ext4_fsblk_t block = 0;
	unsigned int inquota = 0;
	unsigned int reserv_clstrs = 0;

	might_sleep();
	sb = ar->inode->i_sb;
	sbi = EXT4_SB(sb);

	trace_ext4_request_blocks(ar);

	/*
		......
		前面一段是檢查是否有足夠的空間分配這么多,並且檢查quota是否有足夠的余額
		......
	*/
	// 從事先預留好的內存里分配一個ext4_allocation_context
	ac = kmem_cache_zalloc(ext4_ac_cachep, GFP_NOFS);
	if (!ac) {
		ar->len = 0;
		*errp = -ENOMEM;
		goto out;
	}

	*errp = ext4_mb_initialize_context(ac, ar); // 初始化allocation_context
	if (*errp) {
		ar->len = 0;
		goto out;
	}

	ac->ac_op = EXT4_MB_HISTORY_PREALLOC;
	// 非文件不用預分配
	if (!ext4_mb_use_preallocated(ac)) { // 預分配失敗
		ac->ac_op = EXT4_MB_HISTORY_ALLOC;
		ext4_mb_normalize_request(ac, ar); // 對文件大小進行預估,預估后的大小比原來大一些
repeat:
		// 常規分配
		*errp = ext4_mb_regular_allocator(ac);
		if (*errp)
			goto discard_and_exit;

		/* as we've just preallocated more space than
		 * user requested originally, we store allocated
		 * space in a special descriptor */
		if (ac->ac_status == AC_STATUS_FOUND &&
		    ac->ac_o_ex.fe_len < ac->ac_b_ex.fe_len)
			*errp = ext4_mb_new_preallocation(ac); // 將分配的多余的空間放到預分配空間里
		if (*errp) {
		discard_and_exit:
			ext4_discard_allocated_blocks(ac);
			goto errout;
		}
	}
	if (likely(ac->ac_status == AC_STATUS_FOUND)) {
		*errp = ext4_mb_mark_diskspace_used(ac, handle, reserv_clstrs);
		if (*errp) {
			ext4_discard_allocated_blocks(ac);
			goto errout;
		} else {
			block = ext4_grp_offs_to_block(sb, &ac->ac_b_ex);
			ar->len = ac->ac_b_ex.fe_len;
		}
	} else {
		freed  = ext4_mb_discard_preallocations(sb, ac->ac_o_ex.fe_len);
		if (freed)
			goto repeat;
		*errp = -ENOSPC;
	}

	/*
		......
		分配失敗的處理
	*/

	return block;
}

ext4_mb_initialize_context()函數初始化ext4_allocation_context。

static noinline_for_stack int
ext4_mb_initialize_context(struct ext4_allocation_context *ac,
				struct ext4_allocation_request *ar)
{
	struct super_block *sb = ar->inode->i_sb;
	struct ext4_sb_info *sbi = EXT4_SB(sb);
	struct ext4_super_block *es = sbi->s_es;
	ext4_group_t group;
	unsigned int len;
	ext4_fsblk_t goal;
	ext4_grpblk_t block;

	/* we can't allocate > group size */
	len = ar->len; // len就是要申請的cluster的數量

	/* just a dirty hack to filter too big requests  */
    // 分配的cluster數量超過了一個block group的cluster數量,減小之
	if (len >= EXT4_CLUSTERS_PER_GROUP(sb)) 
		len = EXT4_CLUSTERS_PER_GROUP(sb);

	/* start searching from the goal */
	goal = ar->goal;
	if (goal < le32_to_cpu(es->s_first_data_block) ||
			goal >= ext4_blocks_count(es))
		goal = le32_to_cpu(es->s_first_data_block);
    // group為goal的block group的編號
    // block為在group中的cluster的偏移(塊組中的第幾個cluster)
	ext4_get_group_no_and_offset(sb, goal, &group, &block);

	/* set up allocation goals */
	ac->ac_b_ex.fe_logical = EXT4_LBLK_CMASK(sbi, ar->logical); // 邏輯塊號
	ac->ac_status = AC_STATUS_CONTINUE; // 分配的結果狀態
	ac->ac_sb = sb;
	ac->ac_inode = ar->inode;
    // 初始化ac_o_ex,表示原始的分配請求,初始化為goal的值,表示最開始想從goal分配
	ac->ac_o_ex.fe_logical = ac->ac_b_ex.fe_logical;
	ac->ac_o_ex.fe_group = group;
	ac->ac_o_ex.fe_start = block;
	ac->ac_o_ex.fe_len = len; // len就是要分配的cluster的數量
	ac->ac_g_ex = ac->ac_o_ex; // ac_g_ex被賦值成ac_o_ex
	ac->ac_flags = ar->flags;

	// 這個函數在預分配一節里分析
	ext4_mb_group_or_file(ac);

	mb_debug(1, "init ac: %u blocks @ %u, goal %u, flags %x, 2^%d, "
			"left: %u/%u, right %u/%u to %swritable\n",
			(unsigned) ar->len, (unsigned) ar->logical,
			(unsigned) ar->goal, ac->ac_flags, ac->ac_2order,
			(unsigned) ar->lleft, (unsigned) ar->pleft,
			(unsigned) ar->lright, (unsigned) ar->pright,
			atomic_read(&ar->inode->i_writecount) ? "" : "non-");
	return 0;

}

預分配

ext4對於大文件采用的是per inode預留分配空間的方式分配,對於小文件采用的是per_cpu locality group的方式,那么怎么定義文件是大文件還是小文件呢?在sys文件系統的“/sys/fs/ext4/ /mb_stream_req”的值,如果文件大小超過了這個值就是“大文件”,小於等於則是“小文件”,mb_stream_req默認為16個文件塊,按照每個文件塊4k計算為64k。

ext4在ext4_mb_group_or_file()函數決定是使用per inode預分配還是per_cpu locality group預分配,此時函數被ext4_mb_initialize_context()函數調用。

static void ext4_mb_group_or_file(struct ext4_allocation_context *ac)
{
	struct ext4_sb_info *sbi = EXT4_SB(ac->ac_sb);
	int bsbits = ac->ac_sb->s_blocksize_bits;
	loff_t size, isize;

	if (!(ac->ac_flags & EXT4_MB_HINT_DATA))
		return;

	if (unlikely(ac->ac_flags & EXT4_MB_HINT_GOAL_ONLY))
		return;

	// 此次分配后文件的大小,單位為文件塊
	size = ac->ac_o_ex.fe_logical + EXT4_C2B(sbi, ac->ac_o_ex.fe_len);
	// 文件當前總長度,單位為文件塊
	isize = (i_size_read(ac->ac_inode) + ac->ac_sb->s_blocksize - 1)
		>> bsbits;

	if ((size == isize) &&
	    !ext4_fs_is_busy(sbi) &&
	    (atomic_read(&ac->ac_inode->i_writecount) == 0)) {
		ac->ac_flags |= EXT4_MB_HINT_NOPREALLOC;
		return;
	}

    // s_mb_group_prealloc未設置
	if (sbi->s_mb_group_prealloc <= 0) {
		ac->ac_flags |= EXT4_MB_STREAM_ALLOC;
		return;
	}

	/* don't use group allocation for large files */
	size = max(size, isize);
	if (size > sbi->s_mb_stream_request) { // s_mb_stream_request默認是16個文件塊
		ac->ac_flags |= EXT4_MB_STREAM_ALLOC;
		return;
	}
	
    // 小文件采用per_cpu locality group方式分配
	BUG_ON(ac->ac_lg != NULL);
	/*
	 * locality group prealloc space are per cpu. The reason for having
	 * per cpu locality group is to reduce the contention between block
	 * request from multiple CPUs.
	 */
    // 從ext4_sb_info里獲取當前CPU的ext4_locality_group
	ac->ac_lg = raw_cpu_ptr(sbi->s_locality_groups);

	/* we're going to use group allocation */
	ac->ac_flags |= EXT4_MB_HINT_GROUP_ALLOC;

	/* serialize all allocations in the group */
	mutex_lock(&ac->ac_lg->lg_mutex);
}

預分配空間struct ext4_prealloc_space結構體:

struct ext4_prealloc_space { // 預分配空間
	struct list_head	pa_inode_list; // 如果是per inode的預分配空間則掛在ext4_inode_info的i_prealloc_list鏈表; 如果是per_cpu locality group的預分配空間則掛在ext4_locality_group的lg_prealloc_list鏈表上
	struct list_head	pa_group_list; // 預分配空間同時也會掛在ext4_group_info的bb_prealloc_list鏈表上,用於初始化buddy bitmap的之前給block bitmap置上對應的已使用標記
	union {
		struct list_head pa_tmp_list;
		struct rcu_head	pa_rcu;
	} u;
	spinlock_t		pa_lock;
	atomic_t		pa_count;
	unsigned		pa_deleted; // 預分配空間是否處於刪除狀態
	ext4_fsblk_t		pa_pstart;	/* phys. block 起始物理地址 */
	ext4_lblk_t		pa_lstart;	/* log. block 起始邏輯地址,相對於文件而言 */
	ext4_grpblk_t		pa_len;		/* len of preallocated chunk, 空間長度 */
	ext4_grpblk_t		pa_free;	/* how many blocks are free,空間的可用長度 */
	unsigned short		pa_type;	/* pa type. inode or group */
	spinlock_t		*pa_obj_lock;
	struct inode		*pa_inode;	/* hack, for history only */
};

ext4_locality_group結構體:

#define PREALLOC_TB_SIZE 10
struct ext4_locality_group {
	/* for allocator */
	/* to serialize allocates */
	struct mutex		lg_mutex;
	/* list of preallocations */
    // 掛ext4_prealloc_space的鏈表,按照預分配空間的可用長度進行分組
	struct list_head	lg_prealloc_list[PREALLOC_TB_SIZE];
	spinlock_t		lg_prealloc_lock;
};

ext4_mb_use_preallocated()函數,檢查預分配空間里是否能夠分配goal:

static noinline_for_stack int
ext4_mb_use_preallocated(struct ext4_allocation_context *ac)
{
	struct ext4_sb_info *sbi = EXT4_SB(ac->ac_sb);
	int order, i;
	struct ext4_inode_info *ei = EXT4_I(ac->ac_inode);
	struct ext4_locality_group *lg;
	struct ext4_prealloc_space *pa, *cpa = NULL;
	ext4_fsblk_t goal_block;

	/* only data can be preallocated */
	if (!(ac->ac_flags & EXT4_MB_HINT_DATA))
		return 0;

	// 先嘗試per inode的預分配,遍歷預分配空間鏈表
	rcu_read_lock();
	list_for_each_entry_rcu(pa, &ei->i_prealloc_list, pa_inode_list) {

		/* all fields in this condition don't change,
		 * so we can skip locking for them */
		 // 不在這個預分配空間范圍內,跳到下一個預分配空間
		if (ac->ac_o_ex.fe_logical < pa->pa_lstart ||
		    ac->ac_o_ex.fe_logical >= (pa->pa_lstart +
					       EXT4_C2B(sbi, pa->pa_len)))
			continue;

		/* non-extent files can't have physical blocks past 2^32 */
	    // 非extent的ext4文件的最大可訪問的物理塊為2^32,如果
		if (!(ext4_test_inode_flag(ac->ac_inode, EXT4_INODE_EXTENTS)) &&
		    (pa->pa_pstart + EXT4_C2B(sbi, pa->pa_len) >
		     EXT4_MAX_BLOCK_FILE_PHYS))
			continue;

		// 找到了合適的預分配空間,則分配之
		spin_lock(&pa->pa_lock);
		if (pa->pa_deleted == 0 && pa->pa_free) {
			atomic_inc(&pa->pa_count);
            // 往ext4_allocation_context記錄分配的最終結果
			ext4_mb_use_inode_pa(ac, pa);
			spin_unlock(&pa->pa_lock);
			ac->ac_criteria = 10;
			rcu_read_unlock();
			return 1;
		}
		spin_unlock(&pa->pa_lock);
	}
	rcu_read_unlock();
	// 進行per_cpu locality group的預分配
	/* can we use group allocation? */
	if (!(ac->ac_flags & EXT4_MB_HINT_GROUP_ALLOC))
		return 0;

	/* inode may have no locality group for some reason */
	lg = ac->ac_lg;
	if (lg == NULL)
		return 0;
    // fls的意思是找到fe_len從右往左的最后一個1的位置,即為len的階
	order  = fls(ac->ac_o_ex.fe_len) - 1;
	if (order > PREALLOC_TB_SIZE - 1)
		/* The max size of hash table is PREALLOC_TB_SIZE */
		order = PREALLOC_TB_SIZE - 1;
	// goal_block為goal在block group中的偏移
	goal_block = ext4_grp_offs_to_block(ac->ac_sb, &ac->ac_g_ex);
	/*
	 * search for the prealloc space that is having
	 * minimal distance from the goal block.
	 */
	for (i = order; i < PREALLOC_TB_SIZE; i++) {
		rcu_read_lock();
        // 遍歷當前CPU的預分配空間鏈表
		list_for_each_entry_rcu(pa, &lg->lg_prealloc_list[i],
					pa_inode_list) {
			spin_lock(&pa->pa_lock);
            // 預分配空間沒有被刪除,且有足夠的可用空間進行分配
			if (pa->pa_deleted == 0 &&
					pa->pa_free >= ac->ac_o_ex.fe_len) {
				// 進一步檢查,以找到最合適的
				cpa = ext4_mb_check_group_pa(goal_block, pa, cpa);
			}
			spin_unlock(&pa->pa_lock);
		}
		rcu_read_unlock();
	}
	if (cpa) {
		ext4_mb_use_group_pa(ac, cpa);
		ac->ac_criteria = 20;
		return 1;
	}
	return 0;
}

ext4_mb_use_inode_pa()函數:

/*
	per inode的預分配成功分配
*/
static void ext4_mb_use_inode_pa(struct ext4_allocation_context *ac,
				struct ext4_prealloc_space *pa)
{
	struct ext4_sb_info *sbi = EXT4_SB(ac->ac_sb);
	ext4_fsblk_t start;
	ext4_fsblk_t end;
	int len;

	/* found preallocated blocks, use them */
	start = pa->pa_pstart + (ac->ac_o_ex.fe_logical - pa->pa_lstart);
	end = min(pa->pa_pstart + EXT4_C2B(sbi, pa->pa_len),
		  start + EXT4_C2B(sbi, ac->ac_o_ex.fe_len));
	len = EXT4_NUM_B2C(sbi, end - start);
	ext4_get_group_no_and_offset(ac->ac_sb, start, &ac->ac_b_ex.fe_group,
					&ac->ac_b_ex.fe_start);
	ac->ac_b_ex.fe_len = len; // 將分配結果置在ac_b_ex
	ac->ac_status = AC_STATUS_FOUND; // 分配結果為找到了
	ac->ac_pa = pa; // 保存是從哪個預分配空間分配的

	BUG_ON(start < pa->pa_pstart);
	BUG_ON(end > pa->pa_pstart + EXT4_C2B(sbi, pa->pa_len));
	BUG_ON(pa->pa_free < len);
	pa->pa_free -= len; // 減少預分配空間的可用長度

	mb_debug(1, "use %llu/%u from inode pa %p\n", start, len, pa);
}

ext4_mb_check_group_pa()函數:

/*
	找到一個最合適的預分配空間,什么是“最合適的”呢?
	要分配的目標物理塊地址跟預分配空間的起始地址最近的預分配空間。
*/
static struct ext4_prealloc_space *
ext4_mb_check_group_pa(ext4_fsblk_t goal_block,
			struct ext4_prealloc_space *pa,
			struct ext4_prealloc_space *cpa)
{
	ext4_fsblk_t cur_distance, new_distance;

	if (cpa == NULL) {
		atomic_inc(&pa->pa_count);
		return pa;
	}
	cur_distance = abs(goal_block - cpa->pa_pstart);
	new_distance = abs(goal_block - pa->pa_pstart);
	// 找到一個goal離預分配空間起始地址最近的預分配空間
	if (cur_distance <= new_distance)
		return cpa;

	/* drop the previous reference */
	atomic_dec(&cpa->pa_count);
	atomic_inc(&pa->pa_count);
	return pa;
}

ext4_mb_use_group_pa()函數:

// 當從per_cpu locality group分配成功時
static void ext4_mb_use_group_pa(struct ext4_allocation_context *ac,
				struct ext4_prealloc_space *pa)
{
	unsigned int len = ac->ac_o_ex.fe_len;

	ext4_get_group_no_and_offset(ac->ac_sb, pa->pa_pstart,
					&ac->ac_b_ex.fe_group,
					&ac->ac_b_ex.fe_start);
	ac->ac_b_ex.fe_len = len;
	ac->ac_status = AC_STATUS_FOUND;
	ac->ac_pa = pa;

	/* 
		對比ext4_mb_use_inode_pa()我們可以發現這里並沒有着急地減少預分配空間的可用空間,一方面是因為此時是加了lg_mutex鎖的,另一方面是為了防止其他流程也加載了block group而帶來的競爭,最后在更新了block bitmap的時候才減少預分配空間的可用空間。
	 */
	mb_debug(1, "use %u/%u from group pa %p\n", pa->pa_lstart-len, len, pa);
}

預分配空間的來源

在ext4_mb_new_blocks()函數,當采用常規的分配方式分配成功后,如果所分得的長度大於最初要求分配的長度,那么多余出來的這部分空間就可以用來預分配:

ext4_fsblk_t ext4_mb_new_blocks(handle_t *handle,
				struct ext4_allocation_request *ar, int *errp)
{
    /* ...... */
	if (!ext4_mb_use_preallocated(ac)) { // 預分配失敗
		ac->ac_op = EXT4_MB_HISTORY_ALLOC;
		ext4_mb_normalize_request(ac, ar); // 對文件大小進行預估,預估后的大小比原來大一些
repeat:
		/* 常規多塊分配 */
		*errp = ext4_mb_regular_allocator(ac);
		if (*errp)
			goto discard_and_exit;

		/* as we've just preallocated more space than
		 * user requested originally, we store allocated
		 * space in a special descriptor */
		if (ac->ac_status == AC_STATUS_FOUND &&
		    ac->ac_o_ex.fe_len < ac->ac_b_ex.fe_len)
			*errp = ext4_mb_new_preallocation(ac); // 有多余的分配的空間作為預分配空間
		if (*errp) {
		discard_and_exit:
			ext4_discard_allocated_blocks(ac);
			goto errout;
		}
	}
    /* ...... */
}

ext4_mb_new_preallocation()函數,將分配的多余的空間加入到預分配鏈表:

static int ext4_mb_new_preallocation(struct ext4_allocation_context *ac)
{
	int err;

    // 如果該文件是小文件則將多余空間加入到per_cpu locality group預分配空間,如果是大文件則將多余空間加入到per inode預分配空間
    // 這個標記是在ext4_mb_group_or_file()函數根據配置的判別文件是大文件還是小文件的配置項來置上的
	if (ac->ac_flags & EXT4_MB_HINT_GROUP_ALLOC)
		err = ext4_mb_new_group_pa(ac);
	else
		err = ext4_mb_new_inode_pa(ac);
	return err;
}

ext4_mb_new_group_pa()函數:

static noinline_for_stack int
ext4_mb_new_group_pa(struct ext4_allocation_context *ac)
{
	struct super_block *sb = ac->ac_sb;
	struct ext4_locality_group *lg;
	struct ext4_prealloc_space *pa;
	struct ext4_group_info *grp;

	/* preallocate only when found space is larger then requested */
	BUG_ON(ac->ac_o_ex.fe_len >= ac->ac_b_ex.fe_len);
	BUG_ON(ac->ac_status != AC_STATUS_FOUND);
	BUG_ON(!S_ISREG(ac->ac_inode->i_mode));

	BUG_ON(ext4_pspace_cachep == NULL);
	pa = kmem_cache_alloc(ext4_pspace_cachep, GFP_NOFS);
	if (pa == NULL)
		return -ENOMEM;

	/* preallocation can change ac_b_ex, thus we store actually
	 * allocated blocks for history */
	ac->ac_f_ex = ac->ac_b_ex;

	pa->pa_pstart = ext4_grp_offs_to_block(sb, &ac->ac_b_ex);
	pa->pa_lstart = pa->pa_pstart;
	pa->pa_len = ac->ac_b_ex.fe_len; // 注意到pa的len是常規分配后分配到的長度!
	pa->pa_free = pa->pa_len;
	atomic_set(&pa->pa_count, 1);
	spin_lock_init(&pa->pa_lock);
	INIT_LIST_HEAD(&pa->pa_inode_list);
	INIT_LIST_HEAD(&pa->pa_group_list);
	pa->pa_deleted = 0;
	pa->pa_type = MB_GROUP_PA;

	mb_debug(1, "new group pa %p: %llu/%u for %u\n", pa,
			pa->pa_pstart, pa->pa_len, pa->pa_lstart);
	trace_ext4_mb_new_group_pa(ac, pa);

	ext4_mb_use_group_pa(ac, pa); // 注意這一行
	atomic_add(pa->pa_free, &EXT4_SB(sb)->s_mb_preallocated);

	grp = ext4_get_group_info(sb, ac->ac_b_ex.fe_group);
	lg = ac->ac_lg;
	BUG_ON(lg == NULL);

	pa->pa_obj_lock = &lg->lg_prealloc_lock;
	pa->pa_inode = NULL;

	ext4_lock_group(sb, ac->ac_b_ex.fe_group);
	list_add(&pa->pa_group_list, &grp->bb_prealloc_list);
	ext4_unlock_group(sb, ac->ac_b_ex.fe_group);

	/*
	 * We will later add the new pa to the right bucket
	 * after updating the pa_free in ext4_mb_release_context
	 */
	return 0;
}

觀察整個函數以及第38行可知,ext4_mb_new_group_pa()函數生成了一個ext4_prealloc_space但是沒有急於將其掛入到per_cpu locality group中,而且ext4_prealloc_space的長度也是常規分配后分配到的長度而不是多余的長度,而是先調用ext4_mb_use_group_pa()函數將其保存到ac中(這里會改變分配到的長度len,因此在21行先將ac_b_ex拷貝到ac_f_ex),然后在最后的ext4_mb_release_context()流程中將其加入到per_cpu locality group,這樣做是為了跟如果從per_cpu locality group分配到了預分配空間一起統一處理。

ext4_mb_release_context()函數:

static int ext4_mb_release_context(struct ext4_allocation_context *ac)
{
	struct ext4_sb_info *sbi = EXT4_SB(ac->ac_sb);
	struct ext4_prealloc_space *pa = ac->ac_pa;
	if (pa) {
        // 這里才減少ext4_prealloc_space的可用長度
		if (pa->pa_type == MB_GROUP_PA) {
			/* see comment in ext4_mb_use_group_pa() */
			spin_lock(&pa->pa_lock);
			pa->pa_pstart += EXT4_C2B(sbi, ac->ac_b_ex.fe_len);
			pa->pa_lstart += EXT4_C2B(sbi, ac->ac_b_ex.fe_len);
			pa->pa_free -= ac->ac_b_ex.fe_len;
			pa->pa_len -= ac->ac_b_ex.fe_len;
			spin_unlock(&pa->pa_lock);
		}
	}
	if (pa) {
		/*
		 * We want to add the pa to the right bucket.
		 * Remove it from the list and while adding
		 * make sure the list to which we are adding
		 * doesn't grow big.
		 */
		if ((pa->pa_type == MB_GROUP_PA) && likely(pa->pa_free)) {
			spin_lock(pa->pa_obj_lock);
			list_del_rcu(&pa->pa_inode_list);
			spin_unlock(pa->pa_obj_lock);
			ext4_mb_add_n_trim(ac); // 將其加入到per_cpu locality group中
		}
		ext4_mb_put_pa(ac, ac->ac_sb, pa);
	}
	if (ac->ac_bitmap_page)
		put_page(ac->ac_bitmap_page);
	if (ac->ac_buddy_page)
		put_page(ac->ac_buddy_page);
	if (ac->ac_flags & EXT4_MB_HINT_GROUP_ALLOC)
		mutex_unlock(&ac->ac_lg->lg_mutex);
	ext4_mb_collect_stats(ac);
	return 0;
}

ext4_mb_add_n_trim()函數:

static void ext4_mb_add_n_trim(struct ext4_allocation_context *ac)
{
	int order, added = 0, lg_prealloc_count = 1;
	struct super_block *sb = ac->ac_sb;
	struct ext4_locality_group *lg = ac->ac_lg;
	struct ext4_prealloc_space *tmp_pa, *pa = ac->ac_pa;

    // 根據預留空間的長度找到其應該所在的桶
	order = fls(pa->pa_free) - 1;
	if (order > PREALLOC_TB_SIZE - 1)
		/* The max size of hash table is PREALLOC_TB_SIZE */
		order = PREALLOC_TB_SIZE - 1;
	/* Add the prealloc space to lg */
	spin_lock(&lg->lg_prealloc_lock);
    // 遍歷桶的鏈表,然后將ext4_prealloc_space插入其中
	list_for_each_entry_rcu(tmp_pa, &lg->lg_prealloc_list[order],
						pa_inode_list) {
		spin_lock(&tmp_pa->pa_lock);
		if (tmp_pa->pa_deleted) {
			spin_unlock(&tmp_pa->pa_lock);
			continue;
		}
        // 在鏈表里面ext4_prealloc_space是按照可用空間由前到后的順序排列的
		if (!added && pa->pa_free < tmp_pa->pa_free) {
			/* Add to the tail of the previous entry */
			list_add_tail_rcu(&pa->pa_inode_list,
						&tmp_pa->pa_inode_list);
			added = 1;
			/*
			 * we want to count the total
			 * number of entries in the list
			 */
		}
		spin_unlock(&tmp_pa->pa_lock);
		lg_prealloc_count++;
	}
    // 如果新的這個ext4_prealloc_space的可用空間是最大的,那么插入到鏈表尾端
	if (!added)
		list_add_tail_rcu(&pa->pa_inode_list,
					&lg->lg_prealloc_list[order]);
	spin_unlock(&lg->lg_prealloc_lock);

	// 如果當前桶的預分配空間個數超過了8個,則相應的減少它們
	if (lg_prealloc_count > 8) {
		ext4_mb_discard_lg_preallocations(sb, lg,
						  order, lg_prealloc_count);
		return;
	}
	return ;
}

ext4_mb_discard_lg_preallocations()函數:

static noinline_for_stack void
ext4_mb_discard_lg_preallocations(struct super_block *sb,
					struct ext4_locality_group *lg,
					int order, int total_entries)
{
	ext4_group_t group = 0;
	struct ext4_buddy e4b;
	struct list_head discard_list;
	struct ext4_prealloc_space *pa, *tmp;

	mb_debug(1, "discard locality group preallocation\n");

	INIT_LIST_HEAD(&discard_list);

	spin_lock(&lg->lg_prealloc_lock);
	list_for_each_entry_rcu(pa, &lg->lg_prealloc_list[order],
						pa_inode_list) {
		spin_lock(&pa->pa_lock);
        // 如果預分配空間正在使用(剛剛分配流程在使用),則不能刪除
		if (atomic_read(&pa->pa_count)) {
			/*
			 * This is the pa that we just used
			 * for block allocation. So don't
			 * free that
			 */
			spin_unlock(&pa->pa_lock);
			continue;
		}
		if (pa->pa_deleted) {
			spin_unlock(&pa->pa_lock);
			continue;
		}
		/* only lg prealloc space */
		BUG_ON(pa->pa_type != MB_GROUP_PA);

		/* seems this one can be freed ... */
		pa->pa_deleted = 1;
		spin_unlock(&pa->pa_lock);
		// 將預分配空間摘鏈
		list_del_rcu(&pa->pa_inode_list);
        // 加入到臨時的discard_list鏈表中
		list_add(&pa->u.pa_tmp_list, &discard_list);

		total_entries--;
		if (total_entries <= 5) { // 預分配空間個數減少到5個的時候才停止,以防止很快又調用本函數
			/*
			 * we want to keep only 5 entries
			 * allowing it to grow to 8. This
			 * mak sure we don't call discard
			 * soon for this list.
			 */
			break;
		}
	}
	spin_unlock(&lg->lg_prealloc_lock);

	list_for_each_entry_safe(pa, tmp, &discard_list, u.pa_tmp_list) {
		int err;

		group = ext4_get_group_number(sb, pa->pa_pstart);
		err = ext4_mb_load_buddy_gfp(sb, group, &e4b,
					     GFP_NOFS|__GFP_NOFAIL);
		if (err) {
			ext4_error(sb, "Error %d loading buddy information for %u",
				   err, group);
			continue;
		}
		ext4_lock_group(sb, group);
		list_del(&pa->pa_group_list); // 從ext4_group_info里面摘鏈
		ext4_mb_release_group_pa(&e4b, pa); // 釋放預分配空間,將其返還給文件系統
		ext4_unlock_group(sb, group);

		ext4_mb_unload_buddy(&e4b);
		list_del(&pa->u.pa_tmp_list);
		call_rcu(&(pa)->u.pa_rcu, ext4_mb_pa_callback); // 在callback里面會釋放掉ext4_prealloc_space
	}
}

ext4_mb_new_inode_pa()函數:

static noinline_for_stack int
ext4_mb_new_inode_pa(struct ext4_allocation_context *ac)
{
	struct super_block *sb = ac->ac_sb;
	struct ext4_sb_info *sbi = EXT4_SB(sb);
	struct ext4_prealloc_space *pa;
	struct ext4_group_info *grp;
	struct ext4_inode_info *ei;

	/* preallocate only when found space is larger then requested */
	BUG_ON(ac->ac_o_ex.fe_len >= ac->ac_b_ex.fe_len);
	BUG_ON(ac->ac_status != AC_STATUS_FOUND);
	BUG_ON(!S_ISREG(ac->ac_inode->i_mode));

	pa = kmem_cache_alloc(ext4_pspace_cachep, GFP_NOFS);
	if (pa == NULL)
		return -ENOMEM;

	if (ac->ac_b_ex.fe_len < ac->ac_g_ex.fe_len) {
		int winl;
		int wins;
		int win;
		int offs;

		/* we can't allocate as much as normalizer wants.
		 * so, found space must get proper lstart
		 * to cover original request */
		BUG_ON(ac->ac_g_ex.fe_logical > ac->ac_o_ex.fe_logical);
		BUG_ON(ac->ac_g_ex.fe_len < ac->ac_o_ex.fe_len);

		/* we're limited by original request in that
		 * logical block must be covered any way
		 * winl is window we can move our chunk within */
		winl = ac->ac_o_ex.fe_logical - ac->ac_g_ex.fe_logical;

		/* also, we should cover whole original request */
		wins = EXT4_C2B(sbi, ac->ac_b_ex.fe_len - ac->ac_o_ex.fe_len);

		/* the smallest one defines real window */
		win = min(winl, wins);

		offs = ac->ac_o_ex.fe_logical %
			EXT4_C2B(sbi, ac->ac_b_ex.fe_len);
		if (offs && offs < win)
			win = offs;

		ac->ac_b_ex.fe_logical = ac->ac_o_ex.fe_logical -
			EXT4_NUM_B2C(sbi, win);
		BUG_ON(ac->ac_o_ex.fe_logical < ac->ac_b_ex.fe_logical);
		BUG_ON(ac->ac_o_ex.fe_len > ac->ac_b_ex.fe_len);
	}

	/* preallocation can change ac_b_ex, thus we store actually
	 * allocated blocks for history */
	ac->ac_f_ex = ac->ac_b_ex; // 還是先將ac_b_ex拷貝到ac_f_ex

	pa->pa_lstart = ac->ac_b_ex.fe_logical;
	pa->pa_pstart = ext4_grp_offs_to_block(sb, &ac->ac_b_ex);
	pa->pa_len = ac->ac_b_ex.fe_len;
	pa->pa_free = pa->pa_len;
	atomic_set(&pa->pa_count, 1);
	spin_lock_init(&pa->pa_lock);
	INIT_LIST_HEAD(&pa->pa_inode_list);
	INIT_LIST_HEAD(&pa->pa_group_list);
	pa->pa_deleted = 0;
	pa->pa_type = MB_INODE_PA;

	mb_debug(1, "new inode pa %p: %llu/%u for %u\n", pa,
			pa->pa_pstart, pa->pa_len, pa->pa_lstart);
	trace_ext4_mb_new_inode_pa(ac, pa);
	// 同樣的也會走ext4_mb_release_context()流程
	ext4_mb_use_inode_pa(ac, pa);
	atomic_add(pa->pa_free, &sbi->s_mb_preallocated);

	ei = EXT4_I(ac->ac_inode);
	grp = ext4_get_group_info(sb, ac->ac_b_ex.fe_group);

	pa->pa_obj_lock = &ei->i_prealloc_lock;
	pa->pa_inode = ac->ac_inode;
	// 加入到block group的鏈表下
	ext4_lock_group(sb, ac->ac_b_ex.fe_group);
	list_add(&pa->pa_group_list, &grp->bb_prealloc_list);
	ext4_unlock_group(sb, ac->ac_b_ex.fe_group);
	// 加入到ext4_inode_info的i_prealloc_list鏈表下,供后續大文件的預分配
	spin_lock(pa->pa_obj_lock);
	list_add_rcu(&pa->pa_inode_list, &ei->i_prealloc_list);
	spin_unlock(pa->pa_obj_lock);

	return 0;
}

從上述流程分析可以看出per_cpu locality group的預分配方式的預分配空間是文件系統全局的,這樣可以使得小文件都聚集在一起,而per inode的預分配方式是針對單個文件的,使得文件所占的物理空間盡量挨在一起。

mballoc多塊分配

在開始分析ext4_mb_regular_allocator()的多塊分配流程之前,我們先來分析多塊分配所采用的buddy系統的數據結構和初始化流程。

buddy系統將block group內的空閑空間按照其長度的階進行分組,分組的長度為20到213次方,一個block group通常有32768個block,因此最多可以划分成4個213和32768個20,其他的階以此類推。

查看/proc/fs/ext4/ /mb_groups可以看到當前所有block group的空閑空間分組情況:
image

struct ext4_buddy結構體:
struct ext4_buddy {
	struct page *bd_buddy_page; // buddy系統有一個4k大小的bitmap來表示block group內的空間使用情況,而這個bitmap是由頁緩存來管理的,這里就是bitmap所在的頁
	void *bd_buddy; // 指向頁里面具體的buddy的bitmap
    // buddy的bitmap是根據block group內的block bitmap構建而成的,這里是對應的block bitmap
	struct page *bd_bitmap_page; 
	void *bd_bitmap;
	struct ext4_group_info *bd_info; // block group的信息
	struct super_block *bd_sb;
	__u16 bd_blkbits; // 在load buddy的時候初始化為super_block的s_blocksize_bits,通常為12
	ext4_group_t bd_group;
};

buddy系統的bitmap是由頁緩存來管理的,而這個頁緩存必須是整個文件系統范圍內的,因此ext4在ext4_sb_info結構里面用一個inode結構體來管理buddy的頁緩存,字段名為s_buddy_cache。我們通過分析buddy系統的初始化流程來深入理解其構造:

ext4調用ext4_mb_init_group()函數對buddy系統進行初始化:

static noinline_for_stack
int ext4_mb_init_group(struct super_block *sb, ext4_group_t group, gfp_t gfp)
{

	struct ext4_group_info *this_grp;
	struct ext4_buddy e4b;
	struct page *page;
	int ret = 0;

	might_sleep();
	mb_debug(1, "init group %u\n", group);
	this_grp = ext4_get_group_info(sb, group);
	/*
	 * This ensures that we don't reinit the buddy cache
	 * page which map to the group from which we are already
	 * allocating. If we are looking at the buddy cache we would
	 * have taken a reference using ext4_mb_load_buddy and that
	 * would have pinned buddy page to page cache.
	 * The call to ext4_mb_get_buddy_page_lock will mark the
	 * page accessed.
	 */
	 // 申請buddy系統的頁
	ret = ext4_mb_get_buddy_page_lock(sb, group, &e4b, gfp);
	if (ret || !EXT4_MB_GRP_NEED_INIT(this_grp)) {
		/*
		 * somebody initialized the group
		 * return without doing anything
		 */
		goto err;
	}

	page = e4b.bd_bitmap_page;
    // 負責讀取磁盤,並且初始化block bitmap
	ret = ext4_mb_init_cache(page, NULL, gfp); 
	if (ret)
		goto err;
	if (!PageUptodate(page)) {
		ret = -EIO;
		goto err;
	}

	if (e4b.bd_buddy_page == NULL) {
		/*
		 * If both the bitmap and buddy are in
		 * the same page we don't need to force
		 * init the buddy
		 */
		ret = 0;
		goto err;
	}
	// 初始化buddy bit map
	page = e4b.bd_buddy_page;
	ret = ext4_mb_init_cache(page, e4b.bd_bitmap, gfp);
	if (ret)
		goto err;
	if (!PageUptodate(page)) {
		ret = -EIO;
		goto err;
	}
err:
	ext4_mb_put_buddy_page_lock(&e4b);
	return ret;
}

ext4_mb_get_buddy_page_lock()函數:

static int ext4_mb_get_buddy_page_lock(struct super_block *sb,
		ext4_group_t group, struct ext4_buddy *e4b, gfp_t gfp)
{
	// 所有buddy的內存內容都是由s_buddy_cache管理
	struct inode *inode = EXT4_SB(sb)->s_buddy_cache;
	int block, pnum, poff;
	int blocks_per_page;
	struct page *page;

	e4b->bd_buddy_page = NULL;
	e4b->bd_bitmap_page = NULL;

	blocks_per_page = PAGE_SIZE / sb->s_blocksize; // 假定塊大小為4k,則blocks_per_page為1
	
    /*
    	在s_buddy_cache的頁緩存里,page的index為偶數的時候代表這個page里面裝的是block group的block bitmap,為奇數的時候page里面裝的是buddy bitmap,也叫buddy cache,也就是說0、2、4、6、8...為block bit map的page,每個block bitmap page緊挨着的1、3、5、7、9..為buddy bitmap page,依次對應的block group的編號為0、1、2、3、4...
    */
	block = group * 2;
	pnum = block / blocks_per_page; // 就是block
	poff = block % blocks_per_page; // 0
    // 找到或者創建page來存儲block bitmap
	page = find_or_create_page(inode->i_mapping, pnum, gfp);
	if (!page)
		return -ENOMEM;
	BUG_ON(page->mapping != inode->i_mapping);
	e4b->bd_bitmap_page = page;
    // 通常這里就是page的首地址
	e4b->bd_bitmap = page_address(page) + (poff * sb->s_blocksize);

	if (blocks_per_page >= 2) {
		/* buddy and bitmap are on the same page */
		return 0;
	}

	block++; // 獲取到buddy_page,buddy bitmap的page排在block bitmap page的后面
	pnum = block / blocks_per_page;
	page = find_or_create_page(inode->i_mapping, pnum, gfp);
	if (!page)
		return -ENOMEM;
	BUG_ON(page->mapping != inode->i_mapping);
	e4b->bd_buddy_page = page;
	return 0;
}

ext4_mb_init_cache()函數,ext4_mb_init_group()函數會調用兩次這個函數,區別是第一次調時incore為空,為的是初始化block bitmap;第二次調用時,incore傳入的是bd_bitmap,用block bitmap來初始化buddy bitmap。

static int ext4_mb_init_cache(struct page *page, char *incore, gfp_t gfp)
{
	ext4_group_t ngroups;
	int blocksize;
	int blocks_per_page;
	int groups_per_page;
	int err = 0;
	int i;
	ext4_group_t first_group, group;
	int first_block;
	struct super_block *sb;
	struct buffer_head *bhs;
	struct buffer_head **bh = NULL;
	struct inode *inode;
	char *data;
	char *bitmap;
	struct ext4_group_info *grinfo;

	mb_debug(1, "init page %lu\n", page->index);

	inode = page->mapping->host;
	sb = inode->i_sb;
	ngroups = ext4_get_groups_count(sb);
	blocksize = i_blocksize(inode);
	blocks_per_page = PAGE_SIZE / blocksize;

    // 通常情況下blocks_per_page為1,這里groups_per_page的最終結果為1
	groups_per_page = blocks_per_page >> 1;
	if (groups_per_page == 0)
		groups_per_page = 1;

	/* allocate buffer_heads to read bitmaps */
	if (groups_per_page > 1) {
		i = sizeof(struct buffer_head *) * groups_per_page;
		bh = kzalloc(i, gfp);
		if (bh == NULL) {
			err = -ENOMEM;
			goto out;
		}
	} else
		bh = &bhs;

	first_group = page->index * blocks_per_page / 2;

	/* read all groups the page covers into the cache */
	for (i = 0, group = first_group; i < groups_per_page; i++, group++) {
		if (group >= ngroups)
			break;
		// block group信息以二維數組的形式存在ext4_sb_info里面的,
        // 二維數組一行64個,通過對64取商能找到其在第幾行,通過對64取余能找到在第幾列
		grinfo = ext4_get_group_info(sb, group);
		/*
		 * If page is uptodate then we came here after online resize
		 * which added some new uninitialized group info structs, so
		 * we must skip all initialized uptodate buddies on the page,
		 * which may be currently in use by an allocating task.
		 */
		if (PageUptodate(page) && !EXT4_MB_GRP_NEED_INIT(grinfo)) {
			bh[i] = NULL;
			continue;
		}
        // 發送讀IO去讀取block bitmap
        // 通過block group的編號能夠拿到block group在磁盤上的描述ext4_group_desc,通過ext4_group_desc能夠拿到block group的block bitmap在哪個block,然后通過在super_block里面的頁緩存獲取到block bitmap的具體內容
        // 在super_block里面的頁緩存是針對文件系統的元素據的緩存,而在inode里面的緩存是文件的數據的頁緩存
		bh[i] = ext4_read_block_bitmap_nowait(sb, group);
		if (IS_ERR(bh[i])) {
			err = PTR_ERR(bh[i]);
			bh[i] = NULL;
			goto out;
		}
		mb_debug(1, "read bitmap for group %u\n", group);
	}

	// 等待讀block bitmap的IO完成
	for (i = 0, group = first_group; i < groups_per_page; i++, group++) {
		int err2;

		if (!bh[i])
			continue;
		err2 = ext4_wait_block_bitmap(sb, group, bh[i]);
		if (!err)
			err = err2;
	}

	first_block = page->index * blocks_per_page; // 這個頁里面的第一個block
	for (i = 0; i < blocks_per_page; i++) {
		group = (first_block + i) >> 1; // 除以2,得到block group號
		if (group >= ngroups)
			break;

		if (!bh[group - first_group])
			/* skip initialized uptodate buddy */
			continue;

		if (!buffer_verified(bh[group - first_group]))
			/* Skip faulty bitmaps */
			continue;
		err = 0;

		/*
		 * data carry information regarding this
		 * particular group in the format specified
		 * above
		 *
		 */
		data = page_address(page) + (i * blocksize);
		bitmap = bh[group - first_group]->b_data;

		/*
		 * We place the buddy block and bitmap block
		 * close together
		 */
		if ((first_block + i) & 1) { // 奇數則為buddy bitmap
			/* this is block of buddy */
			BUG_ON(incore == NULL);
			mb_debug(1, "put buddy for group %u in page %lu/%x\n",
				group, page->index, i * blocksize);
			trace_ext4_mb_buddy_bitmap_load(sb, group);
			grinfo = ext4_get_group_info(sb, group);
			grinfo->bb_fragments = 0;
			memset(grinfo->bb_counters, 0,
			       sizeof(*grinfo->bb_counters) *
				(sb->s_blocksize_bits+2)); // 0 到 13 一共14個
			/*
			 * incore got set to the group block bitmap below
			 */
			ext4_lock_group(sb, group);
			/* init the buddy */
			memset(data, 0xff, blocksize); // 先把buddy page全部置成1,有空閑的則清為0
			// 初始化buddy重點這個函數
			ext4_mb_generate_buddy(sb, data, incore, group);
			ext4_unlock_group(sb, group);
			incore = NULL;
		} else { // 偶數為block bitmap
			/* this is block of bitmap */
			BUG_ON(incore != NULL);
			mb_debug(1, "put bitmap for group %u in page %lu/%x\n",
				group, page->index, i * blocksize);
			trace_ext4_mb_bitmap_load(sb, group);

			/* see comments in ext4_mb_put_pa() */
			ext4_lock_group(sb, group);
            // 將讀上來的block bitmap拷貝到buddy系統的頁緩存里面
			memcpy(data, bitmap, blocksize); 

			/* mark all preallocated blks used in in-core bitmap */
			ext4_mb_generate_from_pa(sb, data, group);
			ext4_mb_generate_from_freelist(sb, data, group);
			ext4_unlock_group(sb, group);

			/* set incore so that the buddy information can be
			 * generated using this
			 */
			incore = data;
		}
	}
	SetPageUptodate(page);

out:
	if (bh) {
		for (i = 0; i < groups_per_page; i++)
			brelse(bh[i]);
		if (bh != &bhs)
			kfree(bh);
	}
	return err;
}

在讀取了block bitmap后將其拷貝到buddy系統的頁緩存里面,此后還要調用兩個函數來做一些標記:

1)ext4_mb_generate_from_pa()函數

static noinline_for_stack
void ext4_mb_generate_from_pa(struct super_block *sb, void *bitmap,
					ext4_group_t group)
{
	struct ext4_group_info *grp = ext4_get_group_info(sb, group);
	struct ext4_prealloc_space *pa;
	struct list_head *cur;
	ext4_group_t groupnr;
	ext4_grpblk_t start;
	int preallocated = 0;
	int len;
	/*
		遍歷所有該block group下的預分配空間,將預分配空間的范圍標記到block bitmap里面
	*/
	list_for_each(cur, &grp->bb_prealloc_list) {
		pa = list_entry(cur, struct ext4_prealloc_space, pa_group_list);
		spin_lock(&pa->pa_lock);
		ext4_get_group_no_and_offset(sb, pa->pa_pstart,
					     &groupnr, &start);
		len = pa->pa_len;
		spin_unlock(&pa->pa_lock);
		if (unlikely(len == 0))
			continue;
		BUG_ON(groupnr != group);
		ext4_set_bits(bitmap, start, len);
		preallocated += len;
	}
    
	mb_debug(1, "preallocated %u for group %u\n", preallocated, group);
}

2)ext4_mb_generate_from_freelist()函數

static void ext4_mb_generate_from_freelist(struct super_block *sb, void *bitmap,
						ext4_group_t group)
{
	struct rb_node *n;
	struct ext4_group_info *grp;
	struct ext4_free_data *entry;

	grp = ext4_get_group_info(sb, group);
	n = rb_first(&(grp->bb_free_root));
	// 遍歷block group的bb_free_root紅黑樹,將空間范圍標記到block bitmap里面
	while (n) {
		entry = rb_entry(n, struct ext4_free_data, efd_node);
		ext4_set_bits(bitmap, entry->efd_start_cluster, entry->efd_count);
		n = rb_next(n);
	}
	return;
}

初始化buddy bitmap:

struct ext4_group_info結構體,用於描述block group在內存中的信息:

struct ext4_group_info {
	unsigned long   bb_state;
	struct rb_root  bb_free_root; // 掛ext4_free_data的紅黑樹
	ext4_grpblk_t	bb_first_free;	// 第一個是空閑的塊
	ext4_grpblk_t	bb_free;	// 總的空間塊個數
	ext4_grpblk_t	bb_fragments;	// 連續的空閑空間段數目
	ext4_grpblk_t	bb_largest_free_order; // block group中最大的空閑空間的階
	struct          list_head bb_prealloc_list; // 掛ext4_prealloc_space
#ifdef DOUBLE_CHECK
	void            *bb_bitmap;
#endif
	struct rw_semaphore alloc_sem;
	ext4_grpblk_t	bb_counters[];	// 用一個0長數組記錄每個階的空閑空間有多少個
};

初始化buddy bitmap需要知道這到底是個什么,它跟block bitmap的關系是什么:

通常情況下block bitmap和buddy bitmap的大小都是4k,對於block bitmap來說,4k的空間有32768個bit位,而每一個bit位代表一個block塊(4k)的使用情況(1為占用,0為空閑),因此能表示128M的空間。

buddy bitmap首先拿出這32768個bit位的前一半,也就是0-16383,這些bit位每一個bit位表示連續的兩個(21)block的空閑情況;然后再從剩下的一半bit中拿出一半的bit來表示連續4個(22)block的使用情況;接着再從剩下的一半bit位里面拿出一半來表示連續8個(23)block的使用情況......依次類推,最終可以表示4個213個block的使用情況。
image

舉個例子,在初始化的時候,buddy系統通過block bitmap發現有連續的14個空間是連續的,並假設其地址為0-13(相對於block group的第一個block而言的),此時流程會將這個14個block按照階划分為8+4+2,即分到3階、2階和1階,對應在buddy bitmap的bit位里面(借用一張其他人畫的圖):

image

由圖可見,buddy bitmap里面不僅僅包含了block的使用情況信息,而且還包含了block的位置信息(划分到1階的那兩個block的偏移為12-13,bitmap里面的第7個bit位為0)。此外,我們還可以看出當高階為0時所有的低階都為1,這是初始化的結果。

那么有的同學會問了,當只有一個block是連續的空閑空間時怎么表示呢?

當空閑空間只有一個block時,buddy系統將其記錄在ext4_group_info的bb_counters[0]里面。

ext4_mb_init_cache()函數的第114到133行:

首先獲取ext4_group_info准備初始化,將bb_fragments和bb_counters都置為0,后續會增加這兩個值,接着第129行將buddy bitmap全部置1,當后續遍歷block bitmap時划分了階再置相應的bit為0,最后調用ext4_mb_generate_buddy()函數根據block bitmap初始化buddy bitmap:

static noinline_for_stack
void ext4_mb_generate_buddy(struct super_block *sb,
				void *buddy, void *bitmap, ext4_group_t group)
{
	struct ext4_group_info *grp = ext4_get_group_info(sb, group);
	struct ext4_sb_info *sbi = EXT4_SB(sb);
	ext4_grpblk_t max = EXT4_CLUSTERS_PER_GROUP(sb); // max為每個group的cluster數量
	ext4_grpblk_t i = 0;
	ext4_grpblk_t first;
	ext4_grpblk_t len;
	unsigned free = 0;
	unsigned fragments = 0;
	unsigned long long period = get_cycles();

	/* initialize buddy from bitmap which is aggregation
	 * of on-disk bitmap and preallocations */
	i = mb_find_next_zero_bit(bitmap, max, 0); // 找到bitmap里面第一個0,也就是第一個空閑block的位置
	grp->bb_first_free = i;
	while (i < max) {
		fragments++;
		first = i;
        // 找到從i開始的下一個1的位置,從i到這個位置就是這一段空閑空間的長度(i - first)
		i = mb_find_next_bit(bitmap, max, i);
		len = i - first;
		free += len; // 記錄總的空閑空間的長度
		if (len > 1)
            // 如果長度大於1則去划分階,然后記錄到buddy bitmap和bb_counters
			ext4_mb_mark_free_simple(sb, buddy, first, len, grp);
		else
            // 如果長度為1則記錄到bb_counters中即可
			grp->bb_counters[0]++;
        // 繼續尋找下一段空閑空間
		if (i < max)
			i = mb_find_next_zero_bit(bitmap, max, i);
	}
	grp->bb_fragments = fragments; // 碎片數

	.......
}

ext4_mb_mark_free_simple()函數:

static void ext4_mb_mark_free_simple(struct super_block *sb,
				void *buddy, ext4_grpblk_t first, ext4_grpblk_t len,
					struct ext4_group_info *grp)
{
	struct ext4_sb_info *sbi = EXT4_SB(sb);
	ext4_grpblk_t min;
	ext4_grpblk_t max;
	ext4_grpblk_t chunk;
	unsigned int border;

	BUG_ON(len > EXT4_CLUSTERS_PER_GROUP(sb));

	border = 2 << sb->s_blocksize_bits; // 8192即2^13次方, 32M的空間

	while (len > 0) { // 將長度進行拆分,比如長度為14會拆分成8+4+2
		/* find how many blocks can be covered since this position */
		// ffs為find first set,轉換成二進制之后從右往左的第一個1的位置
		max = ffs(first | border) - 1;

	 	// find last set,轉換成二進制之后從右往左的最后一個1的位置
		min = fls(len) - 1;

		if (max < min)
			min = max;
		chunk = 1 << min;

		// 往bb_counters記錄計數
		grp->bb_counters[min]++;
		if (min > 0)
            // 將buddy bitmap里面的相應的bit位置0
            // buddy + sbi->s_mb_offsets[min]的意思是找到對應階的bit位的起始位置
            // 例如1階的起始位置是0,二階的起始位置是16384......
			mb_clear_bit(first >> min,
				     buddy + sbi->s_mb_offsets[min]);

		len -= chunk;
		first += chunk;
	}
}

到此位置buddy bitmap和相關的計數就已經完成了,可以進行多塊分配動作,讓我們回到ext4_mb_regular_allocator()函數,這個函數太長,我們還是分成兩個部分來分析:

static noinline_for_stack int
ext4_mb_regular_allocator(struct ext4_allocation_context *ac)
{
	ext4_group_t ngroups, group, i;
	int cr;
	int err = 0, first_err = 0;
	struct ext4_sb_info *sbi;
	struct super_block *sb;
	struct ext4_buddy e4b;

	sb = ac->ac_sb;
	sbi = EXT4_SB(sb);
	ngroups = ext4_get_groups_count(sb); // 文件系統的block group個數
	/* non-extent files are limited to low blocks/groups */
	if (!(ext4_test_inode_flag(ac->ac_inode, EXT4_INODE_EXTENTS)))
		ngroups = sbi->s_blockfile_groups;

	BUG_ON(ac->ac_status == AC_STATUS_FOUND);

	// 先嘗試在goal的地方能不能分配
	err = ext4_mb_find_by_goal(ac, &e4b);
	if (err || ac->ac_status == AC_STATUS_FOUND)
		goto out;

	if (unlikely(ac->ac_flags & EXT4_MB_HINT_GOAL_ONLY))
		goto out;

	......
}

ext4_mb_find_by_goal()函數,從goal的位置嘗試進行分配:

static noinline_for_stack
int ext4_mb_find_by_goal(struct ext4_allocation_context *ac,
				struct ext4_buddy *e4b)
{
	ext4_group_t group = ac->ac_g_ex.fe_group;
	int max;
	int err;
	struct ext4_sb_info *sbi = EXT4_SB(ac->ac_sb);
	struct ext4_group_info *grp = ext4_get_group_info(ac->ac_sb, group);
	struct ext4_free_extent ex;

	if (!(ac->ac_flags & EXT4_MB_HINT_TRY_GOAL))
		return 0;
	if (grp->bb_free == 0) // block group已經沒有空閑空間了,直接返回
		return 0;
	// 初始化buddy bitmap,詳細見上面
	err = ext4_mb_load_buddy(ac->ac_sb, group, e4b);
	if (err)
		return err;

	if (unlikely(EXT4_MB_GRP_BBITMAP_CORRUPT(e4b->bd_info))) {
		ext4_mb_unload_buddy(e4b);
		return 0;
	}

	ext4_lock_group(ac->ac_sb, group);
	
    // 根據buddy bitmap尋找合適的空閑空間
	max = mb_find_extent(e4b, ac->ac_g_ex.fe_start, // start為在group內的偏移
			     ac->ac_g_ex.fe_len, &ex);
	ex.fe_logical = 0xDEADFA11; /* debug value */

    // 這個分支是ext4文件系統對raid的優化,當分配的起始地址和長度都對齊到stripe時才分配
	if (max >= ac->ac_g_ex.fe_len && ac->ac_g_ex.fe_len == sbi->s_stripe) {
		ext4_fsblk_t start;

		start = ext4_group_first_block_no(ac->ac_sb, e4b->bd_group) +
			ex.fe_start;
		/* use do_div to get remainder (would be 64-bit modulo) */
		if (do_div(start, sbi->s_stripe) == 0) {
			ac->ac_found++;
			ac->ac_b_ex = ex;
			ext4_mb_use_best_found(ac, e4b);
		}
    // 分配成功
	} else if (max >= ac->ac_g_ex.fe_len) {
		BUG_ON(ex.fe_len <= 0);
		BUG_ON(ex.fe_group != ac->ac_g_ex.fe_group);
		BUG_ON(ex.fe_start != ac->ac_g_ex.fe_start);
		ac->ac_found++;
		ac->ac_b_ex = ex;
		ext4_mb_use_best_found(ac, e4b);
    // 調用者只是想合並某些小的空閑空間,這就是其他流程了
	} else if (max > 0 && (ac->ac_flags & EXT4_MB_HINT_MERGE)) {
		/* Sometimes, caller may want to merge even small
		 * number of blocks to an existing extent */
		BUG_ON(ex.fe_len <= 0);
		BUG_ON(ex.fe_group != ac->ac_g_ex.fe_group);
		BUG_ON(ex.fe_start != ac->ac_g_ex.fe_start);
		ac->ac_found++;
		ac->ac_b_ex = ex;
		ext4_mb_use_best_found(ac, e4b);
	}
	ext4_unlock_group(ac->ac_sb, group);
	ext4_mb_unload_buddy(e4b);

	return 0;
}

mb_find_extent()函數,分配的核心函數,根據buddy bitmap進行分配動作,傳入的block和needed都是以cluster為單位的,分別表示起始和長度:

static int mb_find_extent(struct ext4_buddy *e4b, int block, 
				int needed, struct ext4_free_extent *ex)
{
	int next = block;
	int max, order;
	void *buddy;

	assert_spin_locked(ext4_group_lock_ptr(e4b->bd_sb, e4b->bd_group));
	BUG_ON(ex == NULL);

    // max表示傳入的階能夠遍歷多少個bit位
    // 例如0階可以遍歷整個block bitmap,此時max為32768
    // 1階就必須遍歷buddy bitmap的前半段,max為16384
    // 2階max為8192
    // 3階......
	buddy = mb_find_buddy(e4b, 0, &max); 
	BUG_ON(buddy == NULL);
	BUG_ON(block >= max);
	if (mb_test_bit(block, buddy)) { // 起始位置已經被分配了
		ex->fe_len = 0;
		ex->fe_start = 0;
		ex->fe_group = 0;
		return 0;
	}

	// 注意這里的block是起始位置,不是長度
    // order表示的是從block位置開始的最長空閑空閑長度的階
	order = mb_find_order_for_block(e4b, block);
	block = block >> order; // 將起始位置對齊到order

    // fe_len表示已經分配到的長度,當前是假設分配了這么多
	ex->fe_len = 1 << order;
    // 暫時將fe_start與當前的階對齊
	ex->fe_start = block << order;
	ex->fe_group = e4b->bd_group; 

	/* calc difference from given start */
	next = next - ex->fe_start; //原始為起始位置減去當前階的起始位置表示"多分配了"多少個block
    // 與階對齊的分配長度減去多分配的block就是實際分配到的長度
	ex->fe_len -= next; 
	ex->fe_start += next; // 起始位置最終又被還原到最初的起始位置了 

	while (needed > ex->fe_len &&
	       mb_find_buddy(e4b, order, &max)) { 

		if (block + 1 >= max)
			break;
		// 此時的next被賦值為上一次的階能找到的最長的分配長度后緊接着要從哪里開始分配
        // next與上一次分配的起始位置相差上一次分配的長度
		next = (block + 1) * (1 << order); 
        // 如果這一次探測的起始位置已經被占用了則結束后續的探測,因為空閑的block已經不連續了
		if (mb_test_bit(next, e4b->bd_bitmap))
			break;
		// 從next開始的連續的空閑空間的長度的階
		order = mb_find_order_for_block(e4b, next); 
		// 如果申請分配的長度還沒有分配完畢則繼續往后探測
		block = next >> order; 
		ex->fe_len += 1 << order; 
	}
	// 檢查一下
	if (ex->fe_start + ex->fe_len > (1 << (e4b->bd_blkbits + 3))) {
		/* Should never happen! (but apparently sometimes does?!?) */
		WARN_ON(1);
		ext4_error(e4b->bd_sb, "corruption or bug in mb_find_extent "
			   "block=%d, order=%d needed=%d ex=%u/%d/%d@%u",
			   block, order, needed, ex->fe_group, ex->fe_start,
			   ex->fe_len, ex->fe_logical);
		ex->fe_len = 0;
		ex->fe_start = 0;
		ex->fe_group = 0;
	}
	return ex->fe_len;
}

mb_find_order_for_block()函數:

/*
	block為起始地址,根據buddy bitmap探測從起始位置開始能夠分配的最長空閑空間的階
*/
static int mb_find_order_for_block(struct ext4_buddy *e4b, int block)
{
	int order = 1;
	int bb_incr = 1 << (e4b->bd_blkbits - 1); // 2048個字節
	void *bb;

	BUG_ON(e4b->bd_bitmap == e4b->bd_buddy);
	BUG_ON(block >= (1 << (e4b->bd_blkbits + 3)));

	bb = e4b->bd_buddy;

	while (order <= e4b->bd_blkbits + 1) { // order <= 13
        // order等於1時,buddy bitmap的一個bit位表示兩個block,因此起始位置要除以2得到的便是這個起始位置對應這buddy bitmap里面的第幾個bit位,然后看這個bit位有無被占用
		block = block >> 1;
        // 如果這個bit位沒有被占用表示可以分配1 << order個block
		if (!mb_test_bit(block, bb)) {
			/* this block is part of buddy of order 'order' */
			return order;
		}
        // 如果當前bit位為1,那么有兩種情況:
        // 1) 其上階對應的bit位都為1表示占用,最終會返回order=0
        // 2) 其上階的某一階對應的bit位為0表示可用,因此這里向高階搜索
		bb += bb_incr; // order = 1時,這里加的量是2048字節也就是16384個bit位,就找到了第2階的buddy bitmap的起始位置
		bb_incr >>= 1;
		order++;
	}
	return 0;
}

mb_find_extent()函數的第26到59行需要舉個例子:

假設申請從7開始分配,分配9個block:

1)假設6-7的block是空閑的,0-5的block是占用的,8-15的block是空閑的,那么它構建出來的buddy bitmap如下:

image

  • [ ] 28行:order的值為1
  • [ ] 29行:block = 7 >> 1 = 3
  • [ ] 32行:fe_len = 1 << 1 = 2
  • [ ] 34行:fe_start = 3 << 1 = 6
  • [ ] 38行:next = 7 - 6 = 1 此時實際應該只分配到了1個block,但卻分配到了2個block,多分配了1個block
  • [ ] 40行:fe_len = 2 - 1 = 1表示當前已經分配到了的block,還剩下8個block需要分配,而且是緊接着的連續的8個。
  • [ ] 41行:fe_start = 6 + 1 = 7 start被恢復到原始的起始位置。
  • [ ] 43行-59行:進入while循環,next = ( 3 + 1) * (1 << 1) = 8表示從8開始搜索,此時計算所得的order = 3,表示能分配8個block,循環結束。

2)假設0-7和8-9的空間都是空閑的,那么它構建出來的buddy bitmap如下:

image

  • [ ] 28行:order的值為3
  • [ ] 29行:block = 7 >> 3 = 0
  • [ ] 32行:fe_len = 1 << 3 = 8
  • [ ] 34行:fe_start = 0 << 3 = 0
  • [ ] 38行:next = 7 - 0 = 7 此時實際應該只分配到了1個block,但卻分配到了8個block,多分配了7個block
  • [ ] 40行:fe_len = 8 - 7 = 1表示當前實際已經分配到了的block,還剩下8個block需要分配,而且是緊接着的連續的8個。
  • [ ] 41行:fe_start = 0 + 7 = 7 start被恢復到原始的起始位置。
  • [ ] 43行-59行:進入while循環,next = ( 3 + 1) * (1 << 1) = 8表示從8開始搜索,此時計算所得的order = 3,表示能分配8個block,循環結束。

可以把buddy bitmap當成一棵樹來看待,28到41行的代碼就是要找到起始位置所在的子樹能夠申請到多少個block,進入while循環后就是在"緊挨着的子樹"上從最底層往上尋找能否分配了。

如果從goal分配成功會調用函數ext4_mb_use_best_found()置上最終的結果。

我們回到ext4_mb_regular_allocator()函數,分析一下如果在goal分配失敗了會作何策略:

static noinline_for_stack int
ext4_mb_regular_allocator(struct ext4_allocation_context *ac)
{
	/*
		......
		嘗試從goal開始分配
	*/

	// 找到目標長度的二進制的從右往左數最后一個1的位置,len的order
	i = fls(ac->ac_g_ex.fe_len);
	ac->ac_2order = 0;
	/*
		當請求分配的長度的order大於等於s_mb_order2_reqs時,
		s_mb_order2_reqs可以通過/sys/fs/ext4/<partition>/mb_order2_req配置
	 */
	if (i >= sbi->s_mb_order2_reqs && i <= sb->s_blocksize_bits + 2) {
		/*
		 * 申請分配的數量剛好是2的N次方
		 */
		if ((ac->ac_g_ex.fe_len & (~(1 << (i - 1)))) == 0)
			ac->ac_2order = array_index_nospec(i - 1,
							   sb->s_blocksize_bits + 2);
	}

	/* if stream allocation is enabled, use global goal */
	if (ac->ac_flags & EXT4_MB_STREAM_ALLOC) {
		/* TBD: may be hot point */
		spin_lock(&sbi->s_md_lock);
		// 從文件系統上一次分配的地方開始分配
		ac->ac_g_ex.fe_group = sbi->s_mb_last_group;
		ac->ac_g_ex.fe_start = sbi->s_mb_last_start;
		spin_unlock(&sbi->s_md_lock);
	}

	/*
		cr表示搜索block group的嚴苛程度,嚴苛程度從高到低
		0是最嚴苛的程度,詳見ext4_mb_good_group()函數
	*/
	cr = ac->ac_2order ? 0 : 1;

repeat:
    // 每一輪的嚴苛程度不一樣
	for (; cr < 4 && ac->ac_status == AC_STATUS_CONTINUE; cr++) {
		ac->ac_criteria = cr;
		// 從goal的那個block group開始搜索,如果遍歷到了最后一個block group則轉去第一個block group
		group = ac->ac_g_ex.fe_group;
		for (i = 0; i < ngroups; group++, i++) {
			int ret = 0;
			cond_resched();
			/*
			 * Artificially restricted ngroups for non-extent
			 * files makes group > ngroups possible on first loop.
			 */
			if (group >= ngroups) // 回到起始的第一個group看能否分配
				group = 0;

			// 在不加鎖的情況下先檢查一下能否分配,如果能則加鎖去檢查
			ret = ext4_mb_good_group(ac, group, cr);
			if (ret <= 0) {
				if (!first_err)
					first_err = ret;
				continue;
			}
			// 加載buddy
			err = ext4_mb_load_buddy(sb, group, &e4b);
			if (err)
				goto out;

			ext4_lock_group(sb, group);

			// 加載了buddy,加了鎖之后再check能否分配
			ret = ext4_mb_good_group(ac, group, cr);
			if (ret <= 0) {
				ext4_unlock_group(sb, group);
				ext4_mb_unload_buddy(&e4b);
				if (!first_err)
					first_err = ret;
				continue;
			}

			ac->ac_groups_scanned++;
			if (cr == 0)
				// 申請分配的長度剛好是2的N次方的時候才會走這里
				ext4_mb_simple_scan_group(ac, &e4b);
			else if (cr == 1 && sbi->s_stripe &&
					!(ac->ac_g_ex.fe_len % sbi->s_stripe)) // 要分配的長度是stripe的整數倍,這是對raid的優化
				ext4_mb_scan_aligned(ac, &e4b);
			else
				ext4_mb_complex_scan_group(ac, &e4b);

			ext4_unlock_group(sb, group);
			ext4_mb_unload_buddy(&e4b);

			if (ac->ac_status != AC_STATUS_CONTINUE)
				break;
		}
	}
	// 最終也還是沒有分配成功
	if (ac->ac_b_ex.fe_len > 0 && ac->ac_status != AC_STATUS_FOUND &&
	    !(ac->ac_flags & EXT4_MB_HINT_FIRST)) {
		
        // 有空閑空間就行
		ext4_mb_try_best_found(ac, &e4b);
		if (ac->ac_status != AC_STATUS_FOUND) {
			/*
				再嘗試遍歷一次所有的block group有空閑空間就行
			 */
			ac->ac_b_ex.fe_group = 0;
			ac->ac_b_ex.fe_start = 0;
			ac->ac_b_ex.fe_len = 0;
			ac->ac_status = AC_STATUS_CONTINUE;
			ac->ac_flags |= EXT4_MB_HINT_FIRST;
			cr = 3;
			atomic_inc(&sbi->s_mb_lost_chunks);
			goto repeat;
		}
	}
out:
	if (!err && ac->ac_status != AC_STATUS_FOUND && first_err)
		err = first_err;
	return err;
}

ext4_mb_good_group()函數,檢查在當前的嚴苛程度下可否進行分配:

static int ext4_mb_good_group(struct ext4_allocation_context *ac,
				ext4_group_t group, int cr)
{
	unsigned free, fragments;
	int flex_size = ext4_flex_bg_size(EXT4_SB(ac->ac_sb));
	struct ext4_group_info *grp = ext4_get_group_info(ac->ac_sb, group);

	BUG_ON(cr < 0 || cr >= 4);

	free = grp->bb_free;
	if (free == 0) // 沒有空閑空間了直接返回
		return 0;
    // 如果空閑空間小於要分配的長度時,只在嚴苛程度為3,即最低時才往下走去檢查
	if (cr <= 2 && free < ac->ac_g_ex.fe_len)
		return 0;

	if (unlikely(EXT4_MB_GRP_BBITMAP_CORRUPT(grp)))
		return 0;

	// 有必要則初始化block group
	if (unlikely(EXT4_MB_GRP_NEED_INIT(grp))) {
		int ret = ext4_mb_init_group(ac->ac_sb, group, GFP_NOFS);
		if (ret) 
			return ret;
	}

	fragments = grp->bb_fragments;
	if (fragments == 0) // 沒有空閑空間段了,直接返回 
		return 0;

	switch (cr) {
	case 0:
		BUG_ON(ac->ac_2order == 0);

		/* Avoid using the first bg of a flexgroup for data files */
		// flex塊組的第一個塊組一般是給目錄和特殊文件用的,當“最嚴苛的時候”跳過
		if ((ac->ac_flags & EXT4_MB_HINT_DATA) &&
		    (flex_size >= EXT4_FLEX_SIZE_DIR_ALLOC_SCHEME) &&
		    ((group % flex_size) == 0))
			return 0;

		if ((ac->ac_2order > ac->ac_sb->s_blocksize_bits+1) || // 大於13
		    (free / fragments) >= ac->ac_g_ex.fe_len) // 或者空閑空間的平均長度大於等於申請的長度,可以進行分配
			return 1;

        // 如果block group內最大的空閑空間的階小於要分配的階則不能分配
		if (grp->bb_largest_free_order < ac->ac_2order)
			return 0;

		return 1;
	case 1:
		if ((free / fragments) >= ac->ac_g_ex.fe_len) // 空閑空間的平均長度大於等於申請的長度,可以進行分配
			return 1;
		break;
	case 2:
		if (free >= ac->ac_g_ex.fe_len) // 空閑空間的總長度大於申請的長度時可進行分配
			return 1;
		break;
	case 3: // 只要有空閑空間就可以進行分配
		return 1;
	default:
		BUG();
	}

	return 0;
}

ext4_mb_simple_scan_group()函數,當申請的長度剛好是2的N次方時才會調用這個函數:

static noinline_for_stack
void ext4_mb_simple_scan_group(struct ext4_allocation_context *ac,
					struct ext4_buddy *e4b)
{
	struct super_block *sb = ac->ac_sb;
	struct ext4_group_info *grp = e4b->bd_info;
	void *buddy;
	int i;
	int k;
	int max;

	BUG_ON(ac->ac_2order <= 0);
	for (i = ac->ac_2order; i <= sb->s_blocksize_bits + 1; i++) {
		if (grp->bb_counters[i] == 0)
			continue;

		buddy = mb_find_buddy(e4b, i, &max);
		BUG_ON(buddy == NULL);

		k = mb_find_next_zero_bit(buddy, max, 0);
		BUG_ON(k >= max);

		ac->ac_found++;
		
		ac->ac_b_ex.fe_len = 1 << i;
		ac->ac_b_ex.fe_start = k << i;
		ac->ac_b_ex.fe_group = e4b->bd_group;
		// 這里更新的是best,也就是最終的分配地方
		ext4_mb_use_best_found(ac, e4b);

		BUG_ON(ac->ac_b_ex.fe_len != ac->ac_g_ex.fe_len);

		if (EXT4_SB(sb)->s_mb_stats)
			atomic_inc(&EXT4_SB(sb)->s_bal_2orders);

		break;
	}
}

ext4_mb_scan_aligned()函數,這是ext4文件系統對raid的優化,針對分配的長度是stripe的整數倍的場景:

static noinline_for_stack
void ext4_mb_scan_aligned(struct ext4_allocation_context *ac,
				 struct ext4_buddy *e4b)
{
	struct super_block *sb = ac->ac_sb;
	struct ext4_sb_info *sbi = EXT4_SB(sb);
	void *bitmap = e4b->bd_bitmap;
	struct ext4_free_extent ex;
	ext4_fsblk_t first_group_block;
	ext4_fsblk_t a;
	ext4_grpblk_t i;
	int max;

	BUG_ON(sbi->s_stripe == 0);

	/* find first stripe-aligned block in group */
	first_group_block = ext4_group_first_block_no(sb, e4b->bd_group);

	a = first_group_block + sbi->s_stripe - 1;
	do_div(a, sbi->s_stripe); // 計算后a表示起始塊在第幾個stripe
	i = (a * sbi->s_stripe) - first_group_block; // 計算后的i為在block group內第一個與stripe對齊的塊在block group內的編號
	
    // 在一個cluster的范圍內開始尋找
	while (i < EXT4_CLUSTERS_PER_GROUP(sb)) {
        // block group的block bitmap是從0開始編號的,結合上面對i的注釋理解
		if (!mb_test_bit(i, bitmap)) {
            // 查看從這里起始能否找到一個stripe長度的空閑空間
			max = mb_find_extent(e4b, i, sbi->s_stripe, &ex);
			if (max >= sbi->s_stripe) { // 找到則置上best
				ac->ac_found++;
				ex.fe_logical = 0xDEADF00D; /* debug value */
				ac->ac_b_ex = ex;
				ext4_mb_use_best_found(ac, e4b);
				break;
                // 雖然調用者要求的是申請stripe的整數倍長度的空間,但是這里一次也只分配一個stripe長度的空間
			}
		}
		i += sbi->s_stripe; // i指向下一個與stripe對齊的塊
	}
}

ext4_mb_complex_scan_group()函數, 遍歷block group內的所有空閑空間段,然后找出最合適的空閑空間段:

static noinline_for_stack
void ext4_mb_complex_scan_group(struct ext4_allocation_context *ac,
					struct ext4_buddy *e4b)
{
	struct super_block *sb = ac->ac_sb;
	void *bitmap = e4b->bd_bitmap;
	struct ext4_free_extent ex;
	int i;
	int free;

	free = e4b->bd_info->bb_free;
	BUG_ON(free <= 0);
	// 從第一個空閑的block開始搜索
	i = e4b->bd_info->bb_first_free;
	// 遍歷搜索最佳的空閑空間,在ext4_mb_measure_extent()函數確定是否為最佳
	while (free && ac->ac_status == AC_STATUS_CONTINUE) {
        // 每一輪循環開始就找到下一段空閑空間的起始位置,第一輪的時候就是其本身,即第一個空閑空間的起始位置
		i = mb_find_next_zero_bit(bitmap,
						EXT4_CLUSTERS_PER_GROUP(sb), i);
		if (i >= EXT4_CLUSTERS_PER_GROUP(sb)) {
			/*
			 * IF we have corrupt bitmap, we won't find any
			 * free blocks even though group info says we
			 * we have free blocks
			 */
			ext4_grp_locked_error(sb, e4b->bd_group, 0, 0,
					"%d free clusters as per "
					"group info. But bitmap says 0",
					free);
			ext4_mark_group_bitmap_corrupted(sb, e4b->bd_group,
					EXT4_GROUP_INFO_BBITMAP_CORRUPT);
			break;
		}
		// 返回的ex的fe_len表示的是此次尋找能分配的最大的長度
		mb_find_extent(e4b, i, ac->ac_g_ex.fe_len, &ex);
		BUG_ON(ex.fe_len <= 0);
		if (free < ex.fe_len) {
			ext4_grp_locked_error(sb, e4b->bd_group, 0, 0,
					"%d free clusters as per "
					"group info. But got %d blocks",
					free, ex.fe_len);
			ext4_mark_group_bitmap_corrupted(sb, e4b->bd_group,
					EXT4_GROUP_INFO_BBITMAP_CORRUPT);
			/*
			 * The number of free blocks differs. This mostly
			 * indicate that the bitmap is corrupt. So exit
			 * without claiming the space.
			 */
			break;
		}
		ex.fe_logical = 0xDEADC0DE; /* debug value */
        // 檢查是否合適
		ext4_mb_measure_extent(ac, &ex, e4b);
		// 進入到下一輪尋找
		i += ex.fe_len;
		free -= ex.fe_len;
	}

	ext4_mb_check_limits(ac, e4b, 1);
}

ext4_mb_measure_extent()函數,負責判斷當前的空閑空間段是否就是分配的最佳的段:

static void ext4_mb_measure_extent(struct ext4_allocation_context *ac,
					struct ext4_free_extent *ex,
					struct ext4_buddy *e4b)
{
	struct ext4_free_extent *bex = &ac->ac_b_ex;
	struct ext4_free_extent *gex = &ac->ac_g_ex;

	BUG_ON(ex->fe_len <= 0);
	BUG_ON(ex->fe_len > EXT4_CLUSTERS_PER_GROUP(ac->ac_sb));
	BUG_ON(ex->fe_start >= EXT4_CLUSTERS_PER_GROUP(ac->ac_sb));
	BUG_ON(ac->ac_status != AC_STATUS_CONTINUE);

	ac->ac_found++;

	/*
	 如果調用者要求直接就是找到的第一個段那么就直接分配
	 */
	if (unlikely(ac->ac_flags & EXT4_MB_HINT_FIRST)) {
		*bex = *ex;
		ext4_mb_use_best_found(ac, e4b); // 這里面會把ext4_allocation_context的狀態置為FOUND,外面調用的循環自然就停止了。
		return;
	}

	/*
	 檢查是否最佳:
	 */
    // 如果空閑空間長度剛好跟要求分配的長度一致那么這個就是最佳的
	if (ex->fe_len == gex->fe_len) {
		*bex = *ex;
		ext4_mb_use_best_found(ac, e4b);
		return;
	}

	/*
	 第一個段進來還沒有可比較的,就先保存着
	 */
	if (bex->fe_len == 0) {
		*bex = *ex;
		return;
	}

	/*
	 * If new found extent is better, store it in the context
	 */
	if (bex->fe_len < gex->fe_len) {
		// 如果前面找到的段的長度小於要求分配的長度,而且當前找到的段的可分配長度比前面找到的要長,那么自然就是比前面的段要更合適一些
		if (ex->fe_len > bex->fe_len)
			*bex = *ex;
	} else if (ex->fe_len > gex->fe_len) {
		// 如果當前找到的段的可分配長度已經大於了要求分配的長度,那么就找到分配后剩余的長度最短的段
		if (ex->fe_len < bex->fe_len)
			*bex = *ex;
	}

	ext4_mb_check_limits(ac, e4b, 0);
}

ext4_mb_try_best_found()函數,遍歷了很久但還是沒有找到最佳的分配地點,需要做最后的嘗試:

static noinline_for_stack
int ext4_mb_try_best_found(struct ext4_allocation_context *ac,
					struct ext4_buddy *e4b)
{
	struct ext4_free_extent ex = ac->ac_b_ex;
	ext4_group_t group = ex.fe_group;
	int max;
	int err;

    // ac_b_ex可能是在ext4_mb_complex_scan_group()的時候置上的,但沒有成為最終的分配地點
	BUG_ON(ex.fe_len <= 0);
	err = ext4_mb_load_buddy(ac->ac_sb, group, e4b);
	if (err)
		return err;
	// 嘗試從這個前面可能找到的ext4_free_extent進行分配
	ext4_lock_group(ac->ac_sb, group);
	max = mb_find_extent(e4b, ex.fe_start, ex.fe_len, &ex);

	if (max > 0) { // 都此時此刻了,有空閑空間就行,能分配多少分配多少了
		ac->ac_b_ex = ex;
		ext4_mb_use_best_found(ac, e4b);
	}

	ext4_unlock_group(ac->ac_sb, group);
	ext4_mb_unload_buddy(e4b);

	return 0;
}

在ext4_mb_regular_allocator()函數的最后,當所有的手段都不能分配到空閑空間,那么會做最后的一搏,將嚴苛程度置為3,然后回到repeat再一次遍歷所有的block group看能否分配。

持久預分配

linux提供了一個非posix標准的接口fallocate()以實現持久預分配,同時還提供了一個posix標准的接口posix_fallocate(),此接口不需要下層文件系統支持持久預分配,而前面的fallocate()需要。

fallocate()從vfs最終會調到ext4文件系統注冊的ext4_fallocate()接口,fallocate()提供持久預分配的同時還會提供打洞、清零等操作,這個可以單獨開一期分析,但對於持久預分配來說就是調用ext4_map_blocks()函數去分配傳入的邏輯空間。

參考資料

https://oenhan.com/ext4-mballoc

https://younger.blog.csdn.net/article/details/22759619

https://blog.csdn.net/tobbeone/article/details/80852816


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM