跟索引節點一樣,Ext2也對磁碟數據塊進行分配與釋放。在詳細分析相關代碼之前,先引出兩個重要的預備,一個是數據塊定址,一個是文件的洞

1 數據塊定址

每個非空的普通文件都由一組數據塊組成。這些塊或者由文件內的相對位置(它們的文件塊號)來標識,或者由磁碟分區內的位置(它們的邏輯塊號)來標識。

從文件內的偏移量 f 導出相應數據塊的邏輯塊號需要兩個步驟:

1. 從偏移量f導出文件的塊號,即在偏移量f處的字元所在的塊索引。

2. 把文件的塊號轉化為相應的邏輯塊號。

因為Unix文件不包含任何控制字元,因此,導出文件的第f個字元所在的文件塊號當容易的,只是用f除以文件系統塊的大小,並取整即可。

例如,讓我們假定塊的大小為4KB。如果f小於4096,那麼這個字元就在文件的第一數據塊中,其文件的塊號為O。如果f等於或大於4096而小於8192,則這個字元就在文件塊號為1的數據塊中,以此類推。

得到了文件的塊號是第一步。但是,由於Ext2文件的數據塊在磁碟上不必是相鄰的,因此把文件的塊號轉化為相應的邏輯塊號可不是這麼直截了當的了。因此,Ext2文件系統必須提供一種方法,用這種方法可以在磁碟上建立每個文件塊號與相應邏輯塊號之間的關係。在索引節點內部部分實現了這種映射(回到了AT&T Unix的早期版本)。這種映射也涉及一些包含額外指針的專用塊,這些塊用來處理大型文件的索引節點的擴展。

ext2磁碟索引節點ext2_inode的i_block欄位是一個有EXT2_N_BLOCKS個元素且包含邏輯塊號的數組。在下面的討論中,我們假定EXT2_N_BLOCKS的默認值為15(實際上到2.6.18這個值都一直是15)。如圖所示,這個數組表示一個大型數據結構的初始化部分。

正如從圖中所看到的,數組的15個元素有4種不同的類型:

- 最初的12個元素產生的邏輯塊號與文件最初的12個塊對應,即對應的文件塊號從0 - 11。

- 下標12中的元素包含一個塊的邏輯塊號(叫做間接塊),這個塊中存放著一個表示邏輯塊號的二級數組。這個數組的元素對應的文件塊號從12 到 b/4+11,這裡b是文件系統的塊大小(每個邏輯塊號佔4個位元組,因此我們在式子中用4作除數,如果塊大小是4096,則該數組對應文件塊號從12到1035)。因此,內核為了查找指向一個塊的指針必須先訪問這個元素,然後,在這個塊中找到另一個指向最終塊(包含文件內容)的指針。

- 下標13中的元素包含一個間接塊的邏輯塊號,而這個塊包含邏輯塊號的一個二級數組,這個二級數組的數組項依次指向三級數組,這個三級數組存放的纔是文件塊號對應的邏輯塊號,範圍從b/4+12到(b/4)^2+(b/4)+11。如果塊大小是4096,則範圍是從1036到1049611。

- 最後,下標14中的元素使用三級間接索引,第四級數組中存放的纔是文件塊號對應的邏輯塊號,範圍從(b/4)^2+(b/4)+12到(b/4)^3+(b/4)^2+(b/4)+11。

在圖中,塊內的數字表示相應的文件塊號。箭頭(表示存放在數組元素中的邏輯塊號)指示了內核如何通過間接塊找到包含文件實際內容的塊。

注意這種機制是如何支持小文件的。如果文件需要的數據塊小於12,那麼兩次磁碟訪問就可以檢索到任何數據:一次是讀磁碟索引節點i_block數組的一個元素,另一次是讀所需要的數據塊。對於大文件來說,可能需要三四次的磁碟訪問才能找到需要的塊。實際上,這是一種最壞的估計,因為目錄項、索引節點、頁高速緩存都有助於極大地減少實際訪問磁碟的次數。

還要注意文件系統的塊大小是如何影響定址機制的,因為大的塊允許Ext2把更多的邏輯塊號存放在一個單獨的塊中。例如,如果塊的大小是1024位元組,並且文件包含的數據最多為268KB,那麼,通過直接映射可以訪問文件最初的12KB數據,通過簡單的間接映射可以訪問剩的13KB到268KB的數據。大於2GB的大型文件通過指定O_LARGEFILE打開標誌必須在32位體系結構上進行打開。

2 文件的洞

文件的洞(file hole)是普通文件的一部分,它是一些空字元但沒有存放在磁碟的任何數據塊中。洞是Unix文件一直存在的一個特點。例如,下列的Unix命令創建了第一個位元組是洞的文件:

[root@localhost]# echo -n "X" | dd of=/tmp/hole bs=1024 seek=6

現在,/tmp/hole有6145個字元(6144個空字元加一個X字元),然而,這個文件在磁碟上只佔一個數據塊。

引人文件的洞是為了避免磁碟空間的浪費。它們因此被廣泛地用在資料庫應用中,更一般地說,用於在文件上進行散列的所有應用。

文件洞在Ext2中的實現是基於動態數據塊的分配的:只有當進程需要向一個塊寫數據時,才真正把這個塊分配給文件。每個索引節點的i_size欄位定義程序所看到的文件大小,包括洞,而i_blocks欄位存放分配給文件有效的數據塊數(以512位元組為單位)。

在前面dd命令的例子中,假定/tmp/hole文件創建在塊大小為4096的Ext2分區上。其相應磁碟索引節點的i_size欄位存放的數為6145,而i_blocks欄位存放的數為8(因為每4096位元組的邏輯塊包含8個512位元組的物理塊)。i_block數組的第二個元素(對應塊的文件塊號為1)存放已分配塊的邏輯塊號,而數組中的其他元素都為空(參看下圖)。

3 分配數據塊

當內核要分配一個數據塊來保存Ext2普通文件的數據時,就調用ext2_get_block()函數。如果塊不存在,該函數就自動為文件分配塊。請記住,每當內核在Ext2普通文件上執行讀或寫操作時就調用這個函數;顯然,這個函數只在頁高速緩存內沒有相應的塊時才被調用。

ext2_get_block()函數處理在剛才「數據塊定址」描述的數據結構,並在必要時調用ext2_alloc_block()函數在Ext2分區真正搜索一個空閑塊。如果需要,該函數還為間接定址分配相應的塊(參見本篇博文第一個圖)。

為了減少文件的碎片,Ext2文件系統儘力在已分配給文件的最後一個塊附近找一個新塊分配給該文件。如果失敗,Ext2文件系統又在包含這個文件索引節點的塊組中搜尋一個新的塊。如果還是失敗,作為最後一個辦法,可以從其他一個塊組中獲得空閑塊。

Ext2文件系統使用數據塊的預分配策略。文件並不僅僅獲得所需要的塊,而是獲得一組多達8個鄰接的塊。ext2_inode_info結構的i_prealloc_count欄位存放預分配給某一文件但還沒有使用的數據塊的數量,而i_prealloc_block欄位存放下一次要使用的預分配塊的邏輯塊號。當下列情況發生時,釋放預分配而一直沒有使用的塊:當文件被關閉時,當文件被縮短時,或者當一個寫操作相對於引發塊預分配的寫操作不是順序的時。

ext2_alloc_block()函數接收的參數為指向索引節點對象的指針、目標(goal)和存放錯誤碼的變數地址。目標是一個邏輯塊號,表示新塊的首選位置:

static unsigned long ext2_alloc_block (struct inode * inode, unsigned long goal, int *err){#ifdef EXT2FS_DEBUG static unsigned long alloc_hits, alloc_attempts;#endif unsigned long result;

#ifdef EXT2_PREALLOCATE struct ext2_inode_info *ei = EXT2_I(inode); write_lock(&ei->i_meta_lock); if (ei->i_prealloc_count && (goal == ei->i_prealloc_block || goal + 1 == ei->i_prealloc_block)) { result = ei->i_prealloc_block++; ei->i_prealloc_count--; write_unlock(&ei->i_meta_lock); ext2_debug ("preallocation hit (%lu/%lu)./n", ++alloc_hits, ++alloc_attempts); } else { write_unlock(&ei->i_meta_lock); ext2_discard_prealloc (inode); ext2_debug ("preallocation miss (%lu/%lu)./n", alloc_hits, ++alloc_attempts); if (S_ISREG(inode->i_mode)) /* 如果是普通文件 */ result = ext2_new_block (inode, goal, &ei->i_prealloc_count, &ei->i_prealloc_block, err); else /* 如果是目錄或符號鏈接 */ result = ext2_new_block(inode, goal, NULL, NULL, err); }#else result = ext2_new_block (inode, goal, 0, 0, err);#endif return result;}

代碼很容易看懂,如果先前有預分配,則直接返回ei->i_prealloc_block++,沒有,則丟棄所有剩餘的預分配塊ext2_discard_prealloc(inode),並調用ext2_new_block函數分配一個塊:

unsigned long ext2_new_block(struct inode *inode, unsigned long goal, u32 *prealloc_count, u32 *prealloc_block, int *err){ struct buffer_head *bitmap_bh = NULL; struct buffer_head *gdp_bh; /* bh2 */ struct ext2_group_desc *desc; int group_no; /* i */ int ret_block; /* j */ int group_idx; /* k */ unsigned long target_block; /* tmp */ unsigned long block = 0; struct super_block *sb = inode->i_sb; struct ext2_sb_info *sbi = EXT2_SB(sb); struct ext2_super_block *es = sbi->s_es; unsigned group_size = EXT2_BLOCKS_PER_GROUP(sb); unsigned prealloc_goal = es->s_prealloc_blocks; unsigned group_alloc = 0, es_alloc, dq_alloc; int nr_scanned_groups;

if (!prealloc_goal--) prealloc_goal = EXT2_DEFAULT_PREALLOC_BLOCKS - 1; if (!prealloc_count || *prealloc_count) prealloc_goal = 0;

if (DQUOT_ALLOC_BLOCK(inode, 1)) { *err = -EDQUOT; goto out; }

while (prealloc_goal && DQUOT_PREALLOC_BLOCK(inode, prealloc_goal)) prealloc_goal--;

dq_alloc = prealloc_goal + 1; es_alloc = reserve_blocks(sb, dq_alloc); if (!es_alloc) { *err = -ENOSPC; goto out_dquot; }

ext2_debug ("goal=%lu./n", goal);

if (goal < le32_to_cpu(es->s_first_data_block) || goal >= le32_to_cpu(es->s_blocks_count)) goal = le32_to_cpu(es->s_first_data_block); group_no = (goal - le32_to_cpu(es->s_first_data_block)) / group_size; desc = ext2_get_group_desc (sb, group_no, &gdp_bh); if (!desc) { /* * gdp_bh may still be uninitialised. But group_release_blocks * will not touch it because group_alloc is zero. */ goto io_error; }

group_alloc = group_reserve_blocks(sbi, group_no, desc, gdp_bh, es_alloc); if (group_alloc) { ret_block = ((goal - le32_to_cpu(es->s_first_data_block)) % group_size); brelse(bitmap_bh); bitmap_bh = read_block_bitmap(sb, group_no); if (!bitmap_bh) goto io_error; ext2_debug("goal is at %d:%d./n", group_no, ret_block);

ret_block = grab_block(sb_bgl_lock(sbi, group_no), bitmap_bh->b_data, group_size, ret_block); if (ret_block >= 0) goto got_block; group_release_blocks(sb, group_no, desc, gdp_bh, group_alloc); group_alloc = 0; }

ext2_debug ("Bit not found in block group %d./n", group_no);

/* * Now search the rest of the groups. We assume that * i and desc correctly point to the last group visited. */ nr_scanned_groups = 0;retry: for (group_idx = 0; !group_alloc && group_idx < sbi->s_groups_count; group_idx++) { group_no++; if (group_no >= sbi->s_groups_count) group_no = 0; desc = ext2_get_group_desc(sb, group_no, &gdp_bh); if (!desc) goto io_error; group_alloc = group_reserve_blocks(sbi, group_no, desc, gdp_bh, es_alloc); } if (!group_alloc) { *err = -ENOSPC; goto out_release; } brelse(bitmap_bh); bitmap_bh = read_block_bitmap(sb, group_no); if (!bitmap_bh) goto io_error;

ret_block = grab_block(sb_bgl_lock(sbi, group_no), bitmap_bh->b_data, group_size, 0); if (ret_block < 0) { /* * If a free block counter is corrupted we can loop inifintely. * Detect that here. */ nr_scanned_groups++; if (nr_scanned_groups > 2 * sbi->s_groups_count) { ext2_error(sb, "ext2_new_block", "corrupted free blocks counters"); goto io_error; } /* * Someone else grabbed the last free block in this blockgroup * before us. Retry the scan. */ group_release_blocks(sb, group_no, desc, gdp_bh, group_alloc); group_alloc = 0; goto retry; }

got_block: ext2_debug("using block group %d(%d)/n", group_no, desc->bg_free_blocks_count);

target_block = ret_block + group_no * group_size + le32_to_cpu(es->s_first_data_block);

if (target_block == le32_to_cpu(desc->bg_block_bitmap) || target_block == le32_to_cpu(desc->bg_inode_bitmap) || in_range(target_block, le32_to_cpu(desc->bg_inode_table), sbi->s_itb_per_group)) ext2_error (sb, "ext2_new_block", "Allocating block in system zone - " "block = %lu", target_block);

if (target_block >= le32_to_cpu(es->s_blocks_count)) { ext2_error (sb, "ext2_new_block", "block(%d) >= blocks count(%d) - " "block_group = %d, es == %p ", ret_block, le32_to_cpu(es->s_blocks_count), group_no, es); goto io_error; } block = target_block;

/* OK, we _had_ allocated something */ ext2_debug("found bit %d/n", ret_block);

dq_alloc--; es_alloc--; group_alloc--;

/* * Do block preallocation now if required. */ write_lock(&EXT2_I(inode)->i_meta_lock); if (group_alloc && !*prealloc_count) { unsigned n;

for (n = 0; n < group_alloc && ++ret_block < group_size; n++) { if (ext2_set_bit_atomic(sb_bgl_lock(sbi, group_no), ret_block, (void*) bitmap_bh->b_data)) break; } *prealloc_block = block + 1; *prealloc_count = n; es_alloc -= n; dq_alloc -= n; group_alloc -= n; } write_unlock(&EXT2_I(inode)->i_meta_lock);

mark_buffer_dirty(bitmap_bh); if (sb->s_flags & MS_SYNCHRONOUS) sync_dirty_buffer(bitmap_bh);

ext2_debug ("allocating block %d. ", block);

*err = 0;out_release: group_release_blocks(sb, group_no, desc, gdp_bh, group_alloc); release_blocks(sb, es_alloc);out_dquot: DQUOT_FREE_BLOCK(inode, dq_alloc);out: brelse(bitmap_bh); return block;

io_error: *err = -EIO; goto out_release;}

ext2_new_block()函數用下列策略在Ext2分區內搜尋一個空閑塊:

1. 如果傳遞給ext2_alloc_block()的首選塊(目標塊)是空閑的,就分配它。

2. 如果目標為忙,就檢查首選塊後的其餘塊之中是否有空閑的塊。

3. 如果在首選塊附近沒有找到空閑塊,就從包含目標的塊組開始,查找所有的塊組,對每個塊組有:

a. 尋找至少有8個相鄰空閑塊的一個組塊。 b. 如果沒有找到這樣的一組塊,就尋找一個單獨的空閑塊。

下面我們就來詳細分析這個函數,其接收的參數為:- inode:指向被分配塊的文件的索引節點- goal:由ext2_alloc_block傳遞過來的目標塊號- prealloc_count:指向打算預分配塊的計數器的指針- prealloc_block:指向預分配的第一個塊的位置- err:存放錯誤碼的變數地址

只要找到一個空閑塊,搜索就結束,返回該塊的塊號。在結束前,ext2_new_block()函數還儘力在找到的空閑塊附近的塊中找8個空閑塊進行預分配,並把磁碟索引節點的i_prealloc_block和i_prealloc_count欄位置為適當的塊位置及塊數。函數執行以下步驟:

1. 首先初始化一些內部變數: struct buffer_head *bitmap_bh = NULL; struct buffer_head *gdp_bh; /* bh2 */ struct ext2_group_desc *desc; …… unsigned long block = 0; struct super_block *sb = inode->i_sb; struct ext2_sb_info *sbi = EXT2_SB(sb); struct ext2_super_block *es = sbi->s_es; unsigned group_size = EXT2_BLOCKS_PER_GROUP(sb); unsigned prealloc_goal = es->s_prealloc_blocks; unsigned group_alloc = 0, es_alloc, dq_alloc;

這些內部變數各有各的含義,其中,bitmap_bh是點陣圖的緩存;gdp_bh是塊組緩存;block是當前搜索到的塊號;sb是VFS超級快結構,由inode的i_sb欄位得出;sbi是磁碟超級塊的內存對象描述符,由VFS超級快的s_fs_info欄位得到;es是磁碟超級快對象,由超級快內存對象描述符的s_es欄位得到;group_size是磁碟塊組的以塊為單位的大小,由超級快的s_blocks_per_group欄位得到;

prealloc_goal是已預分配的塊數,由磁碟超級快對象的s_prealloc_blocks欄位得到;group_alloc表示同一個組分配塊的數量。

2. 如果prealloc_goal減1為0了,則說明預分配的塊已經用完,則: if (!prealloc_goal--) prealloc_goal = EXT2_DEFAULT_PREALLOC_BLOCKS - 1; if (!prealloc_count || *prealloc_count) prealloc_goal = 0;

其中EXT2_DEFAULT_PREALLOC_BLOCKS為8,也就是需要重新預分配8個塊。當然,如果傳遞進來的參數prealloc_count為空或者是0,則說明不是普通文件,或者沒有啟動預分配機制,則prealloc_goal設置為0 。

3. 對配額進行檢查,分配的目標塊超過了用戶配額,則將出錯對象err設置為-EDQUOT;如果預分配超過了用戶配額,則將預分配數量減至配額以內;檢查完畢後,將預分配數量賦給dq_alloc內部變數,再執行reserve_blocks函數檢查預分配的塊dq_alloc是否到達了保留塊,如果是則減去所處保留塊的數量。如果得到的結果es_alloc為0了,則將出錯對象err設置為-ENOSPC,表示no space: if (DQUOT_ALLOC_BLOCK(inode, 1)) { *err = -EDQUOT; goto out; } while (prealloc_goal && DQUOT_PREALLOC_BLOCK(inode, prealloc_goal)) prealloc_goal--; dq_alloc = prealloc_goal + 1; es_alloc = reserve_blocks(sb, dq_alloc); if (!es_alloc) { *err = -ENOSPC; goto out_dquot; }

4. 如果目標塊小於1(es->s_first_data_block總為1,查看「Ext2磁碟數據結構」博文),或者大於整個文件系統所有塊的大小,則將goal設置為1。 if (goal < le32_to_cpu(es->s_first_data_block) || goal >= le32_to_cpu(es->s_blocks_count)) goal = le32_to_cpu(es->s_first_data_block);

5. 得到goal所對應的塊組號,並根據塊組號獲得對應的組描述符: group_no = (goal - le32_to_cpu(es->s_first_data_block)) / group_size; desc = ext2_get_group_desc (sb, group_no, &gdp_bh);

這裡面的ext2_get_group_desc接收三個參數,sb是VFS超級快,group_no是塊組號,&gdp_bh是該塊組對應的頁高速緩存:struct ext2_group_desc * ext2_get_group_desc(struct super_block * sb, unsigned int block_group, struct buffer_head ** bh){ unsigned long group_desc; unsigned long offset; struct ext2_group_desc * desc; struct ext2_sb_info *sbi = EXT2_SB(sb);……

group_desc = block_group >> EXT2_DESC_PER_BLOCK_BITS(sb); offset = block_group & (EXT2_DESC_PER_BLOCK(sb) - 1); if (!sbi->s_group_desc[group_desc]) { ext2_error (sb, "ext2_get_group_desc", "Group descriptor not loaded - " "block_group = %d, group_desc = %lu, desc = %lu", block_group, group_desc, offset); return NULL; }

desc = (struct ext2_group_desc *) sbi->s_group_desc[group_desc]->b_data; if (bh) *bh = sbi->s_group_desc[group_desc]; return desc + offset;}

函數裡面的group_desc內部變數通過block_group參數獲得組描述符數組的位置下標,ext2磁碟組描述符ext2_group_desc緩存於sbi->s_group_desc[group_desc]->b_data的某個位置,因為總是緩存的,見博文「Ext2的索引節點對象」最後那個表。

6. desc指向了組描述符以後,事情就好辦了。執行 group_reserve_blocks(sbi, group_no, desc, gdp_bh, es_alloc)查看goal對應的那個組內是否有連續es_alloc個空閑的塊,當然,組內如果一個空閑的塊都沒有,就返回0。如果空閑的塊小於es_alloc,就返回可以分配的塊數,並修改組描述符的空閑塊數,然後把組描述符對應的緩存標記為臟:static int group_reserve_blocks(struct ext2_sb_info *sbi, int group_no, struct ext2_group_desc *desc, struct buffer_head *bh, int count){ unsigned free_blocks;

if (!desc->bg_free_blocks_count) return 0;

spin_lock(sb_bgl_lock(sbi, group_no)); free_blocks = le16_to_cpu(desc->bg_free_blocks_count); if (free_blocks < count) count = free_blocks; desc->bg_free_blocks_count = cpu_to_le16(free_blocks - count); spin_unlock(sb_bgl_lock(sbi, group_no)); mark_buffer_dirty(bh); return count;}

7. 好了,group_alloc局部變數就等於group_reserve_blocks的返回值。此時此刻,group_alloc為goal期望的那個塊可分配的數量。那麼接下來就要做一個判斷,如果group_alloc大於0,則說明首選塊是空閑的,就分配它: if (group_alloc) { ret_block = ((goal - le32_to_cpu(es->s_first_data_block)) % group_size); bitmap_bh = read_block_bitmap(sb, group_no); if (!bitmap_bh) goto io_error;

ret_block = grab_block(sb_bgl_lock(sbi, group_no), bitmap_bh->b_data, group_size, ret_block); if (ret_block >= 0) goto got_block; group_release_blocks(sb, group_no, desc, gdp_bh, group_alloc); group_alloc = 0; }

read_block_bitmap讀取超級塊sb對應塊組group_no的那個點陣圖,並把這個點陣圖動態緩存到bitmap_bh頁高速緩存中:static struct buffer_head * read_block_bitmap(struct super_block *sb, unsigned int block_group){ struct ext2_group_desc * desc; struct buffer_head * bh = NULL; desc = ext2_get_group_desc (sb, block_group, NULL); if (!desc) goto error_out; bh = sb_bread(sb, le32_to_cpu(desc->bg_block_bitmap)); if (!bh) ext2_error (sb, "read_block_bitmap", "Cannot read block bitmap - " "block_group = %d, block_bitmap = %u", block_group, le32_to_cpu(desc->bg_block_bitmap));error_out: return bh;}

隨後調用grab_block(sb_bgl_lock(sbi, group_no), bitmap_bh->b_data, group_size, ret_block)檢查一下goal所對應的那個數據塊點陣圖對應的位是否為0,如果是,則把它的塊號賦給相對位置ret_block;如果不是,則ext2_find_next_zero_bit在組內分配一個空閑塊的塊號到ret_block。當然,如果ret_block大於等於0,則說明這個組內有空閑塊,則跳到got_block;如果小於0,就說明這個組的塊已經分配完了,那麼就把剛才給組描述符增加的那些值減回來,並把group_alloc重新設置為0。

8. 如果group_alloc為0,則說明組內沒有goal期望的那個塊可分配的數量那麼多的塊,就到retry程序段,到其他組去找es_alloc個空閑塊,具體代碼跟前邊一樣。

9. 如果得到這個塊了,那麼ret_block就是這個連續塊的第一個塊的相對組頭的位置,到got_block程序段,此時此刻,group_no是該快所在的塊組號,隨後:target_block = ret_block + group_no * group_size + le32_to_cpu(es->s_first_data_block);

target_block就是要分配的實際邏輯塊號(相對於目標塊號)。got_block程序段隨後對這個邏輯塊號進行一系列檢查,包括這個塊是否已經存放了索引節點、點陣圖、組描述符等內容了。當然,肯定不會出現這些問題的,因為這些都是系統bug。

10. 設置點陣圖(注意,是一個組內的預分配連續塊都設置),並把bitmap_bh標記為臟。 write_lock(&EXT2_I(inode)->i_meta_lock); if (group_alloc && !*prealloc_count) { unsigned n;

for (n = 0; n < group_alloc && ++ret_block < group_size; n++) { if (ext2_set_bit_atomic(sb_bgl_lock(sbi, group_no), ret_block, (void*) bitmap_bh->b_data)) break; } *prealloc_block = block + 1; *prealloc_count = n; es_alloc -= n; dq_alloc -= n; group_alloc -= n; } write_unlock(&EXT2_I(inode)->i_meta_lock); mark_buffer_dirty(bitmap_bh);

11. 最後返回這個物理塊: brelse(bitmap_bh); block = target_block; return block


推薦閱讀:
查看原文 >>
相關文章