ioctl源碼分析之交換兩個文件的物理extents
1. 交換兩個文件的extents
Ext4 的EXT4_IOC_MOVE_EXT命令用於交換兩個文件的extents,實際上是交換兩個文件的對應邏輯長度的數據的物理存儲空間(見下圖),也是EXT4文件系統碎片整理的基礎。
用戶可以通過ioctl函數使用Ext4文件系統的Ioctl命令EXT4_IOC_MOVE_EXT將用戶指定交換的兩個文件及交換的范圍的相關信息struct move_extent的地址傳給ioctl的第三個參數unsigned int arg:
ioctl(fd, EXT4_IOC_GROUP_ADD, arg )
至於操作是否成功,結果驗證可以使用filefrag命令,分別在執行轉移前后使用該命令獲取文件的區段信息,就可以判斷是否執行的轉移(交換)操作。
2. EXT4_IOC_MOVE_EXT交換文件extents的限制
利用Ext4 的EXT4_IOC_MOVE_EXT交換兩個文件的extents的操作受到以下限制(這里將進行轉移的文件稱為源文件,參與轉移操作正確執行的另一文件稱為donor文件):
1. 文件自身的限制:
1)源文件與donor文件應該是不同的文件;
2)源文件與donor文件應該是常規文件;
2. 交換區段的參數限制(即調用mext_check_arguments()函數檢查struct move_extent的元素數值是否有效):
1)donor文件都不能設置SUID或SGID;
2)兩個文件都不能支持swapfile,也就是兩個文件都不能是swapfile;
3)兩個文件應當在同一個Ext4文件系統中;
4)兩個文件都要是基於extent的;
5)兩個文件都不能是0長度的;
6)交換的(邏輯)起始偏移應該相等;
7)兩個文件的交換起始偏移塊號、交換的數據塊的個數以及轉移的最后一個數據塊的塊號都不能超過一個文件的最大邏輯塊(2^32 -1),這是因為Ext4文件系統支持的最大文件為16TB;
8)當源文件長度大於donor文件長度時:
a) 如果起始數據塊大於等於donor文件的所有數據塊個數,則退出;
b) 如果起始數據塊加上轉移數據塊的個數得到的邏輯數據塊號大於donor文件的最后一個邏輯數據塊號,則需要對轉移數據塊的個數進行修改,新的轉移長度為donor文件的長度減去起始邏輯數據塊號;
9)當源文件長度小於等於donor文件長度時:
a) 如果起始數據塊大於等於源文件的所有數據塊個數,則退出;
b) 如果起始數據塊加上轉移數據塊的個數得到的邏輯數據塊號大於源文件的最后一個邏輯數據塊號,則需要對轉移數據塊的個數進行修改,新的轉移長度為源文件的長度減去起始邏輯數據塊號;
10)經過以上檢查后還有確定轉移的數據塊的個數不能為0;
3. 交換文件的extents的操作過程
1. 首先打開的文件不是用於讀或寫,那么這個描述符無效,則不對文件進行extents交換;
2. 將用戶設置的轉移信息struct move_extent拷貝到內核空間;
3. 設置已轉移(交換)的數據塊的數據塊的個數為0;
4. 獲取donor文件的描述符,然后確認donor文件以可寫的方式打開;
5. 獲取對文件系統的寫權限;
6. 調用ext4_move_extents()函數,轉移(交換)文件指定范圍的數據的物理存儲位置。該函數的操作流程如下:
1)判斷文件自身是否受限制(標准見上一小節文件自身的限制);
2)獲取源文件與donor文件的i_mutex鎖,防止操作過程中文件被截斷;
3)按inode號順序獲取兩個inode的 i_data_sem寫鎖,防止extent tree被delalloc破壞;
4)調用mext_check_arguments()函數檢查交換的兩個文件及其指定的參數能否滿足move extent的條件(步驟參見上一小節交換區段的參數限制);
5)先獲取源文件的最后一個數據塊的邏輯塊號,然后獲取源文件的待轉移的extents中的最后一個數據塊的邏輯塊號;如果文件的最后一個數據塊邏輯號小於待轉移的extent中的最后一個數據塊的邏輯塊號,則重新修正轉移長度為起始偏移到文件末尾;
6)為交換指定的起始邏輯塊號block_start尋找一個extent path,也就是建立對這個新的extent的路徑索引,主要通過調用get_ext_path()函數實現;
7)為交換指定的起始邏輯塊號block_start尋找一個extent path以檢查文件空洞情況;
8)獲取源文件的extent樹的深度;
9)根據指定的轉移起始塊是否在文件的hole中的情況,修正轉移起始數據塊;
10)判斷以當前extent的起始塊的extent是否有數據塊位於待轉移的數據塊范圍內(這種情況指定的范圍可能是一個文件的空洞),不在則退出;
11)進行extent的轉移(交換):
a)計算當前可轉移(交換)的數據塊的個數;
b)以當前extent為起點,首先修正當前extent中可被轉移的數據塊個數;
c)設置當前extent為前一個extent(ext_prev),然后確定下一個待轉移的extent作為新的當前extent(ext_cur),計算下一個待轉移的extent中包含的數據塊的個數;
d)然后判斷當前extent是否可以與前一個extent合並,可以的話,跳到b)繼續執行,否則進入下一步;
e)判斷當前extent的前一個extent是否未初始化(如果extent的長度大於一個塊組的長度則被認為是未初始化的extent);
f)轉移(交換)前一個extent中的數據塊,首先釋放兩個inode的i_data_sem寫鎖,然后反復調用move_extent_per_page()函數,每次轉移一個page中的extent中的數據。move_extent_per_page()函數的主要操作是通過調用mext_replace_branches()函數,首先保存源inode的數據塊,然后使用donor inode的相應extents 替換源inode的extents,最后,將保存的源inode的數據寫到源inode的新的數據塊中,並返回替換的數據塊的個數;mext_replace_branches()函數一頁一頁地使用donor inode的extents替換源inode的extents;按以下三步實現這種替換:
i 保存原始inode及donor inode的數據塊信息到dummy extents中;
ii 改變源inode的數據塊信息指向donor文件的數據塊;
iii 改變donor inode的數據塊信息指向保存在dummy extents中源inode的數據塊的信息.
g)獲取兩個inode的 i_data_sem寫鎖;
h)獲取下一個轉移的extent;獲取成功,則跳到b)繼續執行;否則,表示需要轉移的數據塊都已轉移完畢。
12)資源釋放:
a)兩次調用ext4_discard_preallocations()函數分別釋放預分配給源inode和donor inode的數據塊;
b)釋放兩個extent路徑orig_path與holecheck_path占用的空間;
c)釋放兩個inode的i_data_sem寫鎖;
13)釋放兩個inode上的i_mutex 鎖,轉移(交換)操作結束;
7. 結束對文件系統的寫操作;
8. 判斷轉移(交換)的數據塊的個數是否不為0(不為0表示確實發生了數據存儲位置的轉移),如果不為0,那么移除donor文件的SUID (Set User ID);
9. 返回內核空間的struct move_extent信息到用戶空間;
10. 釋放donor文件描述符,轉移操作結束,返回。
ioctl源碼分析之強制立即分配延遲分配給文件的數據塊
Ext4文件系統支持強制立即分配延遲分配給文件的數據塊,本質上是強制立即進行數據同步,也就是立即刷新文件的寫緩沖。在Ext4文件系統中,寫文件產生新數據時,文件系統並不立即分配數據塊存儲這些新數據(臟數據),而是延時等待到不得不寫數據到磁盤為止,才會為新數據分配磁盤空間。Ext4文件系統的Ioctl命令EXT4_IOC_ALLOC_DA_BLKS用於為單個文件破解這種延遲分配的方式,也就是與該文件相關操作產生的新數據,文件系統立即為其分配空間,不再等待以延時分配。使用以下命令就可實現調用EXT4_IOC_ALLOC_DA_BLKS命令為文件立即分配數據塊:
ioctl(fd, EXT4_IOC_ALLOC_DA_BLKS, NULL)
ioctl 命令成功執行后。其中與 fd 所引用的文件相關的新數據,系統立即為其分配存儲空間,不延時分配(這一結果可以從 /dev/sys/fs/ext4//delayed_allocation_blocks中查看,如果該文件內容為0,表示沒有延遲分配的數據塊,否則表示延遲分配的數據塊的個數,其中,表示Ext4文件系統所在的設備,如果Ext4在/dev/sda5上,那么dev就表示sda5)。
強制立即分配延遲分配給文件的數據塊的操作流程
(1) 判斷執行強制立即分配操作的進程是否有相應的權限;
(2) 獲取對文件系統的寫操作權限;
(3) 調用ext4_alloc_da_blocks()函數,強制立即分配數據塊;該函數首先調用函數trace_ext4_alloc_da_blocks(inode)獲取延遲分配給文件的數據塊的個數,然后判斷文件是否為延遲分配預留有數據塊或元數據塊,如果都沒有預留則,直接返回(文件系統認為沒有給該文件開啟延遲分配的特性).否者,調用filemap_flush(inode->i_mapping)函數,立即強制分配數據塊。filemap_flush(inode->i_mapping)函數的工作僅僅是調用__filemap_fdatawrite(mapping,WB_SYNC_NONE)函數,其中WB_SYNC_NONE是一個同步標志,表示不進行任何等待。__filemap_fdatawrite(mapping,WB_SYNC_NONE)函數又調用了__filemap_fdatawrite_range(mapping, 0, LLONG_MAX, sync_mode)函數,該函數最終調用了do_writepages()函數,將文件的所有臟數據寫到磁盤。
(4) 釋放對文件系統的寫操作權限,操作結束
ioctl源碼分析之在線擴展Ext4文件系統
Ext4文件系統在線擴展大小的本質是將文件系統的數據塊個數擴展到用戶期望的數據塊的個數。從功能上來看是擴展文件系統最后一個塊組(EXT4_IOC_GROUP_EXTEND)與增加塊組擴展文件系統(EXT4_IOC_GROUP_ADD)兩種方式的結合,但是處理上稍微有點不同。Ext4文件系統的Ioctl命令EXT4_IOC_RESIZE_FS用於在線擴展Ext4文件系統的大小。使用以下命令就可實現調用EXT4_IOC_RESIZE_FS命令擴展Ext4文件系統:
ioctl(fd, EXT4_IOC_RESIZE_FS, arg )
參數arg為用戶希望擴展后最終的文件系統所具有的(文件系統)數據塊的個數unsigned int arg。
1. EXT4_IOC_RESIZE_FS擴展文件系統的限制
利用Ext4 的EXT4_IOC_RESIZE_FS命令擴展文件系統,它對文件系統的擴展結果受到以下限制:
(1)不支持縮小文件系統;
(2)如果Ext4文件系統開啟了bigalloc(大數據塊)特性,則不支持在線擴展;
(3)如果Ext4文件系統開啟了元塊組(meta_bg)特性,則不支持在線擴展;
(4)如果Ext4當前使用32位系統且系統不兼容64位,同時調整后的數據塊的個數超過2^32,那么不支持在線擴展;
(5)如果新擴展的部分要使用新的塊組描述符數據塊,且Ext4中沒有預留GDT數據塊,則不進行在線擴展;
(6)如果用戶期望擴展的文件系統大小超過實際分區,則不進行擴展。
2. 在線擴展Ext4文件系統的操作流程
(1) 依次判斷當期Ext4是否開啟bigalloc特性與元塊組特性,如果開啟,則不進行擴展文件系統的操作;
(2)如果當前Ext4文件系統使用32位數據塊個數尋址且不支持64位兼容性,那么如果用戶期望擴展文件系統使其超過2^32個,則不進行在線擴展;
(3) 設置ext4_sb_info->s_resize_flags標志位,表示將進行在線調整大小;
(4) 獲取對文件系統的寫權限;
(5) 調用ext4_resize_fs(sb, n_blocks_count)函數擴展文件系統,其中sb為要擴展的文件系統的超級塊,n_blocks_count為期望擴展到的最終數據塊個數。該函數實際執行如下操作:
a) 判斷參數以及文件系統自身的配置是否滿足擴展操作執行的條件。
i. 獲取當前文件系統的數據塊個數;
ii. 判斷參數是否指向縮小文件系統,如果是,則推出。不支持縮小文件系統的在線擴展調整;
iii. 如果新增的部分的塊組的描述符要在一個新的塊組描述符數據塊中分配,判斷文件系統中是否有預留GDT數據塊,如果沒有,那么不能進行擴展。
b) 獲取預留GDT數據塊的Inode並進行有效性檢查;
c) 查看硬件物理空間是否足夠擴展文件系統;
d) 先將當前文件系統的最后一個塊組擴充完整;
e) 以每次新增一個flex塊組的方式擴展文件系統,具體步驟如下:
i. 調用alloc_flex_gd(flexbg_size)分配一個struct ext4_new_flex_group_data,用於增加一個flex塊組,其中參數flexbg_size為每個flex塊組中普通塊組的個數;
ii. 反復調用ext4_setup_next_flex_gd(sb, flex_gd, n_blocks_count,flexbg_size)函數每次增加一個新的flex塊組,直到所有新增數據塊都增加到文件系統中。每次增加一個新的flex塊組后,先調用ext4_alloc_group_tables(sb, flex_gd, flexbg_size)在創建的flex塊組中分配塊位圖、Inode位圖以及Inode表,然后調用ext4_flex_group_add(sb, resize_inode, flex_gd)函數將塊組加到文件系統中。
f) 釋放分配的struct ext4_new_flex_group_data空間,將預留GDT數據塊的Inode的引用次數減1,返回。
(6) 建立日志事務鎖(barrier機制),更新文件系統元數據;
(7) 釋放對文件系統的寫權限;
(8) 清除ext4_sb_info->s_resize_flags標志位,返回;擴展文件系統操作結束。