Ext4文件系統架構分析(二)


Ext4 文件系統 ioctl功能概述

 ioctl.c 源碼功能概述

Ext4的ioctl提供給用戶以下接口,以方便用戶更改文件系統的各種設置和狀態:

(1)    EXT4_IOC_GETFLAGS: 獲取inode的標志位,用戶獲取當前的inode標志位信息;

(2)    EXT4_IOC_SETFLAGS: 設置inode的標志位,用戶為inode標志位設置新的信息;

(3)    EXT4_IOC_GETVERSION 或EXT4_IOC_GETVERSION_OLD:獲取inode->i_generation,用戶獲取當前inode對應的文件的版本號;

(4)    EXT4_IOC_SETVERSION 或EXT4_IOC_SETVERSION_OLD:設置inode->i_generation,用戶設置當前inode對應的文件的版本號;

(5)    EXT4_IOC_WAIT_FOR_READONLY:用於JBD2調試時使用,未定義編譯開關CONFIG_JBD2_DEBUG 則該ioctl命令不可用;

(6)    EXT4_IOC_GROUP_EXTEND: 塊組擴展,在文件系統的最后一個塊組上擴展文件系統的容量;

(7)    EXT4_IOC_MOVE_EXT:交換一個文件的指定范圍到另一個文件的指定位置;

(8)    EXT4_IOC_GROUP_ADD:增加塊組擴展文件系統容量;

(9)    EXT4_IOC_MIGRATE:將文件數據塊的映射方式由間接映射遷移為extents方式;

(10)  EXT4_IOC_ALLOC_DA_BLK:強制將延遲分配的所有數據塊分配給文件。

 

 

 

ioctl源碼分析之設置/獲取文件版本號

1. EXT4_IOC_GETVERSION獲取文件的版本號

Ext4 的EXT4_IOC_GETVERSION命令用於獲取文件的版本號(inode.i_generation),將獲取的文件版本號傳給ioctl的第三個參數unsigned int arg:

 ioctl(fd, EXT4_IOC_GETVERSION, arg )

2.  EXT4_IOC_SETVERSION設置文件的版本號

Ext4 的EXT4_IOC_SETVERSION命令用於設置文件的版本號,將要設置的最終版本號作為ioctl的第三個參數unsigned int arg傳入ioctl函數中:

 ioctl(fd, EXT4_IOC_SETVERSION, arg )

3.  設置文件版本號的操作過程

(1)    首先確定用戶對文件具有訪問權限且對文件所在的文件系統具有寫權限(因為設置inode標志成功會引起元數據更新操作)。並將用戶設置的文件版本號(ioctl命令的第三個參數)拷貝到內核空間。

(2)    發起一個日志事務,用於后面提交日志事務時更新inode;

(3)    在日志中建立元數據更新事務,用於更新inode元數據;

(4)    更新inode元數據,設置新的文件版本號,更新inode到磁盤;

(5)    結束日志事務處理;

(6)    結束對文件系統的寫操作;

 

 

 

ioctl源碼分析之設置/獲取Inode標志位

1.  EXT4_IOC_GETFLAGS獲取inode標志位

Ext4 的EXT4_IOC_GETFLAGS命令用於獲取inode已設置的標志信息,將獲取的inode標志位的信息的結果傳給ioctl的第三個參數unsigned int arg:

 ioctl(fd, EXT4_IOC_GETFLAGS, arg )

2.  EXT4_IOC_SETFLAGS設置inode標志位

Ext4 的EXT4_IOC_SETFLAGS命令用於設置inode的標志信息,將要設置的最終inode標志位的信息作為ioctl 的第三個參數unsigned int arg傳入ioctl函數中:

 ioctl(fd, EXT4_IOC_SETFLAGS, arg )

雖然Inode具有大量的標志,但是最終用戶可設置的inode標志只有五個,其他標志由內核默認設置。用戶可直接設置的5個inode標志是:

#define EXT4_SYNC_FL             0x00000008 /* 同步更新 */

#define EXT4_NOATIME_FL             0x00000080 /* 不更新文件訪問時間 */

#define EXT4_APPEND_FL               0x00000020 /* 文件僅能追加寫 */

#define EXT4_IMMUTABLE_FL        0x00000010 /* 不可改變的文件 */

#define EXT4_DIRSYNC_FL             0x00010000 /* 目錄同步操作(僅目錄可用) */

3.  設置inode標志位的操作過程

(1)    首先確定用戶對文件具有訪問權限且對文件所在的文件系統具有寫權限(因為設置inode標志成功會引起元數據更新操作)。並將用戶設置的inode標志flags (ioctl命令的第三個參數)拷貝到內核空間。

(2)    獲取inode互斥鎖,然后判斷inode是否是配額文件,如果是配額文件,則不可對其設置inode標志;直接跳出

(3)    接着會對用戶要求設置的inode標志flags進行適當的修改:

修改的原因有很多:

1)屏蔽不適合給定類型的inode的標志。比如要求設置的標志中含有與文件類型不匹配的標志(將目錄的標志設置給普通文件可不行),則將不匹配的標志清除掉;

2)如果用戶要求設置標識中含有EXT4_APPEND_FL 或 EXT4_IMMUTABLE_FL標志,判斷是否需要是否具有權限修改JOURNAL_DATA 標志。要檢查用戶是否有相應的capability。如果不具備,那么也無法設置inode標志;

3)如果inode原有標識中已設置 EXT4_EXTENTS_FL 標志,則不支持清除extents標志,如果用戶要求設置的新標識中無使用extents的標志,則退出;

如果原inode中未設置使用extents的標志,而要設置的inode標志中含使用extents的標志,並從設置的inode標志中去掉使用extents的標志,並在隨后遷移文件支持extents方式

4)如果用戶要求設置的標識中含有EXT4_EOFBLOCKS_FL標志且inode原有標志中沒有EXT4_EOFBLOCKS_FL標志,則退出;

如果用戶要求設置的標識中無EXT4_EOFBLOCKS_FL標志且inode原有標志中EXT4_EOFBLOCKS_FL標志,則把inode的全部數據塊索引信息清除;

5)發起日志事務操作,准備更新元數據塊——inode數據。

6)重新設置用戶設置的flags:首先,保存用戶設置的flags中那些用戶可修改的標志,屏蔽掉用戶不可修改的標志;然后,加上原有inode中用戶不可修改的標志;

7)設置inode標志,更改inode相關信息(inode更改時間),更新inode內容到磁盤;

8)結束日志事務處理;

9)如果新設置了EXT4_JOURNAL_DATA_FL標志,則刷新日志內容,設置日志標志

10)         遷移inode,使其支持extents。用於將用間接映射方式組織的文件數據塊用extents的方式組織。

(4)    釋放inode互斥鎖,結束對文件系統的寫操作,返回

 

 

 

ioctl源碼分析之擴展EXT4文件系統最后一個塊組大小

1.  擴展Ext4文件系統最后一個塊組的大小

Ext4 的EXT4_IOC_GROUP_EXTEND命令用於擴展文件系統最后一個塊組的大小,它通過擴展文件系統最后一個塊組的方式來擴展文件系統的大小。能擴展的最大范圍是將文件系統的最后一個塊組擴展為一個完整的塊組(128MB)。用戶可以通過ioctl函數使用Ext4文件系統的Ioctl命令 EXT4_IOC_GROUP_EXTEND 將用戶希望擴展后最終的文件系統所具有的(文件系統)數據塊的個數給ioctl的第三個參數unsigned int arg:

 ioctl(fd, EXT4_IOC_GROUP_EXTEND, arg )

2.  EXT4_IOC_GROUP_EXTEND擴展文件系統的限制

利用Ext4 的EXT4_IOC_GROUP_EXTEND命令擴展文件系統最后一個塊組的大小的方式來擴展文件系統的大小,它對文件系統的擴展結果受到以下限制:

(1)文件系統所在分區必須要有多余的空閑空間,且這些空間不屬於當前文件系統;

(2)該ioctl命令不用於縮小文件系統。也就是要求最終擴展到的數據塊個數不能少於文件系統現有數據塊個數;

(3)如果用戶設置希望擴展后最終的文件系統所具有的(文件系統)數據塊的個數(ioctl的第三個參數arg)為0或者與文件系統現有數據塊個數相同,則該命令不做任何事而直接返回。也就是既不對文件系統進行擴展,也不報錯。

(4)用戶指定擴展到的數據塊數目不超過最后一個塊組成為完整塊組時文件系統具有的數據塊數目時,擴展能夠正常進行;

(5)用戶指定擴展到的數據塊數目超過最后一個塊組成為完整塊組時文件系統所具有的數據塊個數時,擴展能正常進行,但實際只擴展到使最后一個塊組成為完整塊組為止;

(6) 用戶指定擴展到的數據塊數目使得文件系統的大小超過16TB(2^32個數據塊)時,則不進行擴展。

3.  擴展文件系統最后一個塊組的操作過程

(1)    首先確定用戶對文件具有訪問權限且對文件所在的文件系統具有寫權限(因為設置inode標志成功會引起元數據更新操作)。並將用戶指定擴展到的數據塊數目(ioctl命令的第三個參數)拷貝到內核空間。

(2)    確認進程對文件系統具有寫權限;

(3)    調用ext4_group_extend()函數擴展文件系統的最后一個塊組。如果文件系統支持日志,那么要在日志中建立擴展文件系統最后一個塊組的日志事務,並設置barrier(作用如同字面意思,barrier之后提交的操作必須要在barrier釋放之后才能處理)。日志事務處理完畢后刷新日志並釋放設置的barrier。擴展文件系統最后一個塊組函數ext4_group_extend()主要執行流程如下:

a)  獲取文件系統現有數據塊個數以及塊組個數;

b)  檢查用戶設置的文件系統最終擴展到的數據塊的數目是否有效。是否有效的依據參見上一小節的(2)、(3)、(4)、(5)、(6)小點的描述;

c)  因為擴展的是最后一個數據塊組,所以先要獲取當前文件系統的最后一個塊組的塊組號和最后一個數據塊在塊組中的偏移offset;

d)  接着要計算擴展最后一個塊組成功,實際需要在最后一個塊組中增加的數據塊的個數。這個時候會按以下步驟處理:

                     i.  如果步驟c)計算的當前文件系統最后一個數據塊的偏移是0,也就意味着最后一個塊組已是完整塊組,那么無法使用擴展最后一個塊組的方法擴展文件系統了,因而報錯退出;

                     ii.  如果步驟c)計算的當前文件系統最后一個數據塊的偏移不為0,那么實際最多可加入的數據塊的個數為EXT4_BLOCKS_PER_GROUP(sb) – offset,即一個完整塊組包含的數據塊的個數減去當前文件系統最后一個數據塊的在塊組中的偏移;

                    iii.  如果加入的數據塊的個數過多,導致數據塊個數溢出,則告警退出。這種情況出現在當前的文件系統已經相當大了,差不多是系統能夠支持的最大文件系統的情況,此時雖然最后一個塊組未完整,但是如果補充使其完整,那么就會導致文件系統數據塊個數溢出。(但是這種情況永遠不會出現,因為傳入的參數不超過32位);

                     iv.  如果加入的數據塊的個數小於允許加入的最大個數,則加入實際的數據塊個數;

                     v. 如果加入的數據塊的個數大於允許加入的最大個數,則僅加入允許加入的最大數據塊個數;

e)  確定了最終增加的數據塊的個數之后呢,接着就要看文件系統所在的設備(分區)大小是否滿足要求了。如果設備剩余空間不夠,那么也只能告警退出了;

f)  接着發起一個日志事務,這個事務要修改三個數據塊(超級塊、數據塊位圖、塊組描述符)的內容;

g)  獲取超級塊sb->s_resize_lock互斥鎖。如果獲取到互斥鎖后,發現當前文件系統的數據塊個數已經發生改變,則意味着已經有其他調整大小的程序運行過,那么先前計算的加入數據塊的個數什么的都已經無效了,只好打印提示信息退出了,同時釋放獲取的互斥鎖;

h)  獲取超級塊sb->s_resize_lock互斥鎖后發現文件系統數據塊個數沒有改變,那么就可以開始執行擴展大小的核心操作了。首先修改與超級塊相關的日志事務(當然如果修改出錯也會告警退出同時釋放互斥鎖),接着統計擴展后文件系統的數據塊的個數,然后標記超級塊為臟。這樣就完成了超級塊的修改,因而可以釋放掉超級塊sb->s_resize_lock互斥鎖。

i)  接下來,就是加入數據塊的操作了。調用ext4_add_groupblocks(handle, sb, o_blocks_count, add)將add個數據塊加入到文件系統的最后一個塊組中。這一步操作成功后,會將文件系統的最后一個塊組的塊組描述符所在的數據塊以及最后一個塊組上的數據塊位圖都標記為臟,然后更新到磁盤。ext4_add_groupblocks()函數的具體操作流程如下:

1. 獲取加入的數據塊所在的塊組號以及加入的數據塊起始數據塊偏移;

2. 檢查加入數據塊的個數是否導致塊組跨界;

3. 讀取塊組中的塊位圖與塊組描述符;

4. 判斷加入數據塊是否會導致出錯。因為除了超級塊和塊組描述符表外,其他元數據的位置不固定,因而可能造成出錯。以下情況會導致加入數據塊出錯:

a) 塊組的數據塊位圖所在的數據塊號數位於加入塊組的起始物理數據塊號與加入后的所有數據塊總和之間——塊組中無數據塊位圖;

b) 塊組的inode位圖所在的數據塊號數位於加入塊組的起始物理數據塊號與加入后的所有數據塊總和之間——塊組中無inode位圖;

c) 加入塊組的起始物理數據塊號位於inode表中間——inode表不完整;

d) 加入數據塊后的最后一個數據塊位於inode表中間——inode表不完整。

5. 在位圖中加入數據塊,然后修改塊組描述符;

6. 清除塊位圖新加入的counts個數據塊的標志,使其為空閑可用狀態;

7. 鎖定塊組,更新塊組描述符的相關內容,如塊組中空閑塊個數,塊組校驗和等。操作成功后解鎖塊組;

8. 如果設置了flex_bg特性,計算塊組所在的flex_bg,進而修改flex_bg描述符信息,也就是空閑數據塊的個數; 

9. 設置塊組需初始化位,重新載入具有新的位圖信息的塊組描述符元數據。這主要是為了更新內存中塊組描述符的信息(更新塊組的空閑數據塊個數、更新塊組中連續2^n個數據塊構成的片段的個數);

10. 將數據塊位圖所在數據塊標記為臟;

11. 將塊組描述符所在數據塊標記為臟。

j) 提交日志事務,結束日志操作。主要是更新前面提到的三個數據塊(超級塊、數據塊位圖、塊組描述符)的日志提交及更新到磁盤;

k) 更新文件系統的備份元數據(主要是超級塊和塊組描述符表的備份)。

(4)  結束對文件系統的寫操作,返回;

 

 

 

ioctl源碼分析之增加塊組擴展EXT4文件系大小

1.  增加塊組擴展Ext4文件系統

Ext4 的EXT4_IOC_GROUP_ADD命令用於增加塊組來擴展文件系統的大小,它通過向文件系統中加入新的塊組的方式來擴展文件系統的大小。單次能擴展的最大范圍是向文件系統中增加一個完整的塊組(128MB,4KB數據塊計算)。用戶可以通過ioctl函數使用Ext4文件系統的Ioctl命令 EXT4_IOC_GROUP_ADD將用戶希望加入的新塊組的相關信息struct ext4_new_group_input的地址傳給ioctl的第三個參數unsigned int arg(因此用戶對當前文件系統必須要有先驗知識):

 ioctl(fd, EXT4_IOC_GROUP_ADD, arg )

2.  EXT4_IOC_GROUP_ADD擴展文件系統的限制

利用Ext4 的EXT4_IOC_GROUP_ADD命令向文件系統中增加新塊組的方式來擴展文件系統的大小,它對文件系統的擴展結果受到以下限制:
1. 文件系統層面的限制,這些限制在函數ext4_group_add()中給出:

(1)如果input對應的塊組所在的位置為GDT中某個數據塊的第一個塊組,且文件系統不是以稀疏方式管理元數據,那么不允許增加塊組,退出;

(2)若加入塊組中包含的數據塊個數使得加入后文件系統后數據塊個數溢出,則不允許增加塊組,退出;

(3)若加入塊組后使得加入后文件系統inode個數溢出,則不允許增加塊組,退出;

(4)如果文件系統沒有預留用於預留塊組描述符的inode,或者文件系統預留GDT數據塊個數為0,那么系統報無預留GDT數據塊無法調整大小的錯誤。 如果以上都沒有問題的話,那么就獲取預留塊組描述符的inode,准備增加塊組。

2.新增加的塊組的自身參數導致的限制,由函數verify_group_input()檢驗這些限制:

(1)新增加的塊組的塊組號是否有效。新加入的塊組號要比系統現有塊組號大1,否則塊組號無效;

(2)現有文件系統最后一個塊組不是完整塊組則不允許增加新塊組;

(3)新加入的塊組中預留數據塊超過塊組中數據塊個數的20%,則不允許增加該新塊組;

(4)新加入的塊組中沒有空閑塊,也就是新加入的塊組的數據塊個數存放的只能是塊組的元數據,甚至是僅僅存放元數據的空間都不夠,則不允許增加該新塊組;

(5)新塊組中最后一個數據塊的內容讀取不了(主要原因是空間不足),則不允許增加該新塊組;

(6)新加入的塊組指定的塊位圖不在新塊組中,則不允許增加該新塊組;

(7)新加入的塊組指定的Inode位圖不在新塊組中,則不允許增加該新塊組;

(8)新加入的塊組指定的Inode表不在新塊組中,則不允許增加該新塊組;

(9)新塊組的塊位圖與Inode位圖是同一個數據塊,則不允許增加該新塊組;

(10)新加入的塊組指定的塊位圖在新塊組的inode表中,則不允許增加該新塊組;

(11)新加入的塊組指定的Inode位圖在新塊組的inode表中,則不允許增加該新塊組;

(12)新加入的塊組指定的塊位圖在新塊組的GDT中,則不允許增加該新塊組;

(13)新加入的塊組指定的Inode位圖在新塊組的GDT中,則不允許增加該新塊組;

(14)新加入的塊組指定的Inode表在新塊組的GDT中,則不允許增加該新塊組;

正是由於這些限制的存在,使得增加一個新塊組沒有像擴展最后一個塊組的操作那樣簡單,所以在增加塊組之前必須做夠充足的准備,以防止以上限制導致增加塊組失敗。獲取這些信息的最簡單方式是使用tune2fs -l 命令:

3. 增加新塊組的操作過程

1. 調用函數setup_new_group_blocks()為新增塊組創建並初始化數據塊位圖、inode位圖以及Inode表。該函數的執行步驟如下:

(1)獲取塊組的第一個數據塊號,獲取塊組中預留GDT數據塊的個數,獲取塊組中塊組描述符表使用的數據塊的個數,這幾個數據對塊組的數據塊位圖有影響;

(2)發起一個文件系統超級塊更新的事務,后續(增加塊組操作成功后)更新超級塊,這個超級塊更新事務就是調整文件系統大小,因為調整大小需要修改超級塊元數據;

(3)獲取s_resize_lock互斥鎖用於調整文件系統大小;

(4)檢查新增加塊組是否有效。塊組號不等於當前系統擁有的塊組個數,表明已經有其他增加塊組的程序運行過或者正在運行,因而不能增加相同的塊組,要退出;

(5)將新塊組中的塊位圖置零;

(6)如果新的塊組中應當含有超級塊的備份,則將塊位圖的第一位置位,標記為已使用;

(7)拷貝所有的GDT數據塊到新塊組的備份元數據塊中,並在塊位圖中將相應的數據塊設置為1;

(8)將新塊組中的預留GDT數據塊中全部寫0填充,並在塊位圖中將相應的數據塊設置為1;

(9)依次在塊位圖中將塊位圖所在的塊的位置1、將inode位圖所在的塊的位置1;

(10)初始化inode表。將新塊組中的inode表數據塊中全部寫0填充,然后將該inode表數據塊對應的塊位圖中的位置1,每次這樣處理一個inode表數據塊;

(11)設置塊位圖中的其他塊(這些塊不存在,因而塊位圖置1);

(12)標記新塊組中塊位圖數據塊為臟;

(13)將新塊組中inode位圖數據塊清零;

(14)設置inode位圖中的其他inode(這些inode不存在,因而inode位圖置1);

(15)標記新塊組中inode位圖數據塊為臟;

(16)釋放s_resize_lock互斥鎖;

(17)提交日志,結束新塊組內元數據更新事務。

2. 添加新塊組后至少要修改超級塊以及一個GDT數據塊. 如果加入塊組超出當前的最后一個GDT數據塊,則還需要修改inode(預留GDT的inode)和GDT數據塊自身.如果加入的塊組具有超級塊/GDT的備份,則需要更新的元數據更多;

3. 獲取s_resize_lock互斥鎖,准備更新文件系統超級塊等元數據;

4. 判斷文件吸引是否已被其他程序調整,如果文件系統已被其他程序調整,則退出;

5. 准備更新超級塊;

6. 更新GDT元數據;

7. 至此,已經建立了新的塊組,現在要激活它,使其可用;

(1) 最關鍵的是 sbi->s_groups_count:只要它還包含舊的值,那么就訪問不到新塊組.因而,首先要為新塊組更新用於新塊組的所有的描述符元數據;然后更新總的磁盤數據塊數目;然后更新塊組數目以激活塊組;最后更新空閑數據塊數目以使得系統可以使用新的磁盤數據塊;

a) 為新塊組更新塊組描述符塊;

b) 為新塊組在內存中創建塊組描述符信息

(2) 接下來,使得新的數據塊和inodes有效。在增加文件系統中的塊組數目之前先進行這一步,這樣一旦塊組激活,它上面的所有數據塊和inodes都已生效;

a) 獲取文件系統最新數據塊個數;

b) 獲取文件系統最新inode個數;

(3) 更新超級塊的s_groups_count元素,即更新全局文件系統域,增加塊組個數;

(4) 更新空閑數據塊個數,然后更新空閑inode個數;

(5) 如果文件系統支持Flex_bg特性,則更改新塊組所在的flex_bg的信息(修改空閑數據塊個數、空閑inode個數);

8. 標記超級塊為臟;

9.  釋放s_resize_lock互斥鎖;

10.  提交日志事務更新超級塊;

11.  更新文件系統中超級塊的拷貝,然后更新文件系統中GDT的拷貝。


免責聲明!

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



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