在實(shí)際寫(xiě)數(shù)據(jù)到磁盤(pán)之前需要分配磁盤(pán)上的空間。這里的寫(xiě)數(shù)據(jù)包括寫(xiě)文件數(shù)據(jù)、在目錄中創(chuàng)建文件和添加擴(kuò)展屬性等等。但凡需要存儲(chǔ)新數(shù)據(jù)的場(chǎng)景都需要分配磁盤(pán)空間。分配磁盤(pán)空間的主要功能在函數(shù)ext2_get_blocks中實(shí)現(xiàn),該函數(shù)的原型如下所示:
static int ext2_get_blocks(struct inode *inode,
sector_t iblock, unsigned long maxblocks,
struct buffer_head *bh_result,
int create)
該函數(shù)原型中,重點(diǎn)需要說(shuō)明的是iblock參數(shù),該參數(shù)表示文件的邏輯位置,位置以文件系統(tǒng)的塊大小為單位,值為文件系統(tǒng)的以0為起始位置邏輯地址。舉個(gè)簡(jiǎn)單的例子,加入文件系統(tǒng)格式化時(shí)塊大小是2K,而此時(shí)寫(xiě)入數(shù)據(jù)的偏移為4K,那么此時(shí)iblock就是2。也就是說(shuō),該函數(shù)通過(guò)數(shù)據(jù)在文件中的邏輯位置計(jì)算需要分配多少磁盤(pán)空間。

函數(shù)ext2_get_blocks的主流程如圖1所示,該函數(shù)主要完成三方面的工作:
- 計(jì)算并獲取存儲(chǔ)路徑,我們知道文件數(shù)據(jù)是通過(guò)間接塊的方式存儲(chǔ)的,因此這里主要是根據(jù)數(shù)據(jù)邏輯地址計(jì)算出其存儲(chǔ)的路徑情況,并獲得該路徑。
- 計(jì)算需要分配的塊的數(shù)量和期望的磁盤(pán)物理位置。
-
分配磁盤(pán)空間,計(jì)算出需要的磁盤(pán)空間的數(shù)量后,最后該函數(shù)調(diào)用ext2_alloc_branch來(lái)分配需要的磁盤(pán)空間,具體就是將空間管理的位圖置位。
圖2 Ext2間接塊數(shù)組組織形式
為了容易理解磁盤(pán)整個(gè)分配磁盤(pán)空間的流程,我們先回顧一下Ext2的間接塊結(jié)構(gòu),如圖2所示(具體解釋請(qǐng)參考本號(hào)歷史文章)。為了理解更清楚一些,我們假設(shè)請(qǐng)求的數(shù)據(jù)位置需要二級(jí)間接塊,因此,我們將該關(guān)系放大,如圖3所示。我們知道對(duì)于Ext2來(lái)說(shuō),其地址是32位的,因此在間接塊中的數(shù)據(jù)其實(shí)可以理解為無(wú)符號(hào)整型的大數(shù)組。其中數(shù)組中的每一項(xiàng)就是一個(gè)下一級(jí)磁盤(pán)數(shù)據(jù)塊的地址。這樣我們根據(jù)數(shù)據(jù)在文件中的邏輯地址就可以計(jì)算出來(lái)需要幾級(jí)間接塊及位置數(shù)據(jù)在該間接塊中存放的位置。

有了間接塊需要的數(shù)據(jù),我們?cè)诮Y(jié)合數(shù)據(jù)本身需要的磁盤(pán)空間,就可以計(jì)算出本次請(qǐng)求需要申請(qǐng)的磁盤(pán)空間。最后就是根據(jù)這些來(lái)分配磁盤(pán)空間了。下面我們分布詳細(xì)介紹一下各個(gè)流程的實(shí)現(xiàn)細(xì)節(jié)。
計(jì)算存儲(chǔ)路徑
所謂計(jì)算存儲(chǔ)路徑是指計(jì)文件數(shù)據(jù)邏輯地址在間接塊中存儲(chǔ)的物理地址表示。仍然以上述圖3為例,對(duì)于二級(jí)間接塊可存儲(chǔ)的情況,在分配磁盤(pán)空間之前需要計(jì)算出其在一級(jí)間接塊和二級(jí)間接塊中的位置。這樣,在后續(xù)的數(shù)據(jù)查找流程,根據(jù)這些間接塊中存儲(chǔ)的地址信息就可以找到文件某個(gè)位置的數(shù)據(jù)在磁盤(pán)的物理地址。
函數(shù)ext2_block_to_path用于計(jì)算數(shù)據(jù)在間接塊數(shù)組中存放的路徑。該函數(shù)的功能主要是根據(jù)邏輯地址計(jì)算出在深度及每一層的位置。前文我們已經(jīng)知道文件數(shù)據(jù)的放置方式,結(jié)合圖3以比較清楚的理解本函數(shù)的代碼。這里根據(jù)數(shù)據(jù)的邏輯地址分為4種情況,分別如下:
- ** 不需要間接塊 **: 也就是數(shù)據(jù)目的位置(以文件塊大小為單位)在12以?xún)?nèi),則說(shuō)明是直接引用,不需要間接塊,此時(shí)在數(shù)組的前12個(gè)元素中的一個(gè)。
- ** 一級(jí)間接塊 **: 數(shù)據(jù)范圍在一級(jí)間接塊可表示的范圍內(nèi),此時(shí)表示路徑的數(shù)組的第一個(gè)元素是inode數(shù)組中的第12個(gè)元素,而第二個(gè)元素則是在間接塊中的具體位置。比如i_block是18,此時(shí)通過(guò)直接尋址無(wú)法滿(mǎn)足要求,因此需要一級(jí)間接塊。這樣,offsets中第一個(gè)元素的值是12,表示是一級(jí)間接塊;offsets的第二個(gè)元素是6,因?yàn)橹苯铀饕梢员硎?2個(gè)數(shù)據(jù)塊,因此在間接塊中的分別可以存儲(chǔ)從第13到256+12的數(shù)據(jù)范圍,對(duì)于位置為18的數(shù)據(jù)在間接塊的位置就是6。
- ** 二三級(jí)間接塊 **: 以此類(lèi)推,根據(jù)邏輯地址的大小可能會(huì)需要二級(jí)甚至三級(jí)間接塊,依照這種算法可以計(jì)算出每一級(jí)間接塊的位置。這里不在贅述。
下面是本函數(shù)的代碼,本文加了一些注釋?zhuān)a本身比較簡(jiǎn)單,本文不在贅述。這里需要注意的是,除了返回深度和每一層的位置外,還會(huì)返回在最后的間接塊上可容納的地址的數(shù)量。比如計(jì)算出來(lái)在最后一級(jí)間接塊的位置是250,那么最多可容納6個(gè)地址。
static int ext2_block_to_path(struct inode *inode,
long i_block, int offsets[4], int *boundary)
{
int ptrs = EXT2_ADDR_PER_BLOCK(inode->i_sb); //這里計(jì)算出每個(gè)間接塊可以存儲(chǔ)多少地址
int ptrs_bits = EXT2_ADDR_PER_BLOCK_BITS(inode->i_sb); //塊大小的位數(shù)
const long direct_blocks = EXT2_NDIR_BLOCKS, //直接塊的數(shù)量
indirect_blocks = ptrs, //間接塊可存儲(chǔ)地址的數(shù)量
double_blocks = (1 << (ptrs_bits * 2)); //二級(jí)間接塊可存儲(chǔ)地址的數(shù)量
int n = 0;
int final = 0;
if (i_block < 0) {
ext2_msg(inode->i_sb, KERN_WARNING,
"warning: %s: block < 0", __func__);
} else if (i_block < direct_blocks) {
offsets[n++] = i_block;
final = direct_blocks;
} else if ( (i_block -= direct_blocks) < indirect_blocks) {
offsets[n++] = EXT2_IND_BLOCK;
offsets[n++] = i_block;
final = ptrs;
} else if ((i_block -= indirect_blocks) < double_blocks) {
offsets[n++] = EXT2_DIND_BLOCK;
offsets[n++] = i_block >> ptrs_bits;
offsets[n++] = i_block & (ptrs - 1);
final = ptrs;
} else if (((i_block -= double_blocks) >> (ptrs_bits * 2)) < ptrs) {
offsets[n++] = EXT2_TIND_BLOCK;
offsets[n++] = i_block >> (ptrs_bits * 2);
offsets[n++] = (i_block >> ptrs_bits) & (ptrs - 1);
offsets[n++] = i_block & (ptrs - 1);
final = ptrs;
} else {
ext2_msg(inode->i_sb, KERN_WARNING,
"warning: %s: block is too big", __func__);
}
if (boundary)
*boundary = final - 1 - (i_block & (ptrs - 1));
return n;
}
獲取存儲(chǔ)路徑
前面邏輯計(jì)算出了深度和每一級(jí)間接塊應(yīng)該存儲(chǔ)的信息,但具體的間接塊目前處于什么情況并不清楚。仍然以上述二級(jí)間接塊為例,可能會(huì)出現(xiàn)如下幾種情況:
- 用戶(hù)訪問(wèn)的數(shù)據(jù)位置所需要的間接塊已經(jīng)全部分配了
- 一級(jí)間接塊已經(jīng)具備了,但二級(jí)間接塊不具備
- 所有間接塊都不具備
因此,這里的工作就是根據(jù)當(dāng)前信息及上一步計(jì)算出的信息進(jìn)行綜合判斷,確定已經(jīng)具備的間接塊,并返回關(guān)鍵信息,為后續(xù)流程分配磁盤(pán)空間做準(zhǔn)備。具體實(shí)現(xiàn)在函數(shù)ext2_get_branch中。該函數(shù)的輸入是上一個(gè)函數(shù)的計(jì)算結(jié)果,輸出是需要分配的間接塊的信息(chain)。
static Indirect *ext2_get_branch(struct inode *inode,
int depth,
int *offsets,
Indirect chain[4],
int *err)
分配磁盤(pán)空間
完成間接塊情況分析之后,再經(jīng)過(guò)簡(jiǎn)單的計(jì)算,就可以計(jì)算出總共需要分配的磁盤(pán)塊的數(shù)量。然后就可以調(diào)用ext2_alloc_branch函數(shù)分配磁盤(pán)空間了。該函數(shù)主要調(diào)用了2個(gè)函數(shù),具體如圖3所示。其中ext2_alloc_blocks用戶(hù)分配磁盤(pán)塊,本質(zhì)是將管理磁盤(pán)空間的位圖的對(duì)應(yīng)位進(jìn)行置位操作;另外一個(gè)函數(shù)sb_getblk用于從磁盤(pán)讀取該塊的數(shù)據(jù),并進(jìn)行初始化。

初始化的目的比較明確,因?yàn)殚g接塊用來(lái)存儲(chǔ)地址信息,如果是從磁盤(pán)讀取的新間接塊數(shù)據(jù)可能是未知值,因此需要清零操作,并且完成本請(qǐng)求地址的初始化操作。
至此,磁盤(pán)空間分配的主要流程執(zhí)行完成,但仍然有一些小的處理流程,比如更新inode中的記錄最后一次分配位置、更新時(shí)間和將inode變臟等等。這些細(xì)節(jié)讀者可以自行閱讀代碼理解。
另外,為了簡(jiǎn)化,本文有些內(nèi)容沒(méi)有介紹,比如預(yù)留窗口,還有就是跨越間接塊的處理等問(wèn)題。這些問(wèn)題相對(duì)復(fù)雜一些,我們后續(xù)文章會(huì)繼續(xù)介紹。
