深入理解overlayfs(二):使用與原理分析

在初步瞭解overlayfs用途之後,本文將介紹如何使用overlayfs以及理解該文件系統所特有的一些功能特性。由於目前主線內核對overlayfs正在不斷的開發和完善中,因此不同的內核版本改動可能較大,本文儘量與最新的內核版本保持一致,但可能仍會存在細微的出入。

內核版本:Linux-4.14

示例環境:pi3


掛載文件系統

掛載文件系統的基本命令如下:

mount -t overlay overlay -o lowerdir=lower1:lower2:lower3,upperdir=upper,workdir=work merged

其中"lower1:lower2:lower3"表示不同的lower層目錄,不同的目錄使用":"分隔,層次關係依次爲lower1 > lower2 > lower3(注:多lower層功能支持在Linux-4.0合入,Linux-3.18版本只能指定一個lower dir);然後upper和work目錄分別表示upper層目錄和文件系統掛載後用於存放臨時和間接文件的工作基目錄(work base dir),最後的merged目錄就是最終的掛載點目錄。若一切順利,在執行以上命令後,overlayfs就成功掛載到merged目錄下了。

掛載選項支持(即"-o"參數):
1)lowerdir=xxx:指定用戶需要掛載的lower層目錄(支持多lower,最大支持500層);
2)upperdir=xxx:指定用戶需要掛載的upper層目錄;
3)workdir=xxx:指定文件系統的工作基礎目錄,掛載後內容會被清空,且在使用過程中其內容用戶不可見;
4)default_permissions:功能未使用;
5)redirect_dir=on/off:開啓或關閉redirect directory特性,開啓後可支持merged目錄和純lower層目錄的rename/renameat系統調用;
6)index=on/off:開啓或關閉index特性,開啓後可避免hardlink copyup broken問題。

其中lowerdir、upperdir和workdir爲基本的掛載選項,redirect_dir和index涉及overlayfs爲功能支持選項,除非內核編譯時默認啓動,否則默認情況下這兩個選項不啓用,這裏先按照默認情況進行演示分析,後面這兩個選項單獨說明。

示例:現在以ext4文件系統作爲基礎文件系統掛載overlayfs


首先創建overlayfs文件系統的基礎目錄5個,然後分別在兩個lower目錄和upper目錄下創建不同文件foo1、foo2和foo3,最後在這3個目錄下分別創建同名目錄dir並同時在dir目錄下創建同名文件aa和bb。在掛載overlayfs文件系統之後,在merge目錄下能夠看到foo1、foo2和foo3,這就是overlayfs的上下層合併;在merge/dir目錄下看到來自lower1的文件和aa來自upper層的文件bb,位於最底層lower2中的文件aa被lower1中的同名文件覆蓋,位於lower1中的文件bb被upper中的同名文件覆蓋,這就是overlayfs的“上下層同名目錄合併與同名文件覆蓋”特性,對應的組織結構如下圖所示:


圖1 overlayfs基本掛載示例

上下層同名文件覆蓋和上下層同名目錄合併的原理:

用戶在overlayfs的merge目錄中查看文件時,會調用內核的getdents系統調用。一般情況下該系統調用會調用文件系統接口,它僅會遍歷當前目錄中的所有目錄項並返回給用戶,所以用戶能夠看到這個目錄下的所有文件或子目錄。但在overlayfs中,如果目錄不是僅來自單獨的一層(當前時多層合併的或者其中可能存在曾經發生過合併的跡象),它會逐級遍歷掃描所有層的同名目錄,然後把各層目錄中的內容返回給用戶,因此用戶就會感覺到上下層同名目錄合併;與此同時,如果在遍歷掃描的過程中發現了同名的文件,它會判斷該文件來自那一層,從而忽略來自lower層的文件而只顯示來自upper層的文件,因此用戶會感覺到上下層同名文件覆蓋。

掛載文件系統的特性與限制條件:

1、用戶可以不指定upperdir和workdir,但同時必須保證lowerdir >= 2層,此時的文件系統爲只讀掛載(這也是隻讀掛載overlayfs的唯一方法);如果用戶指定upperdir,則必須保證upperdir所在的文件系統是可讀寫的,同時還需指定workdir,並且workdir不能和upperdir是父子目錄關係。

2、常見的文件系統中,upperdir所在的文件系統不能是nfs、cifs、gfs2、vfat、ocfs2、fuse、isofs、jfs和另一個overlayfs等文件系統,而lowerdir所在的文件系統可以是nfs、cifs這樣的遠程文件系統,也可以是另一個overlayfs。因爲upperdir是可以寫入的,所以需要避免一些特性上的不兼容(例如vfat是大小寫不敏感的文件系統),而lowerdir是隻讀文件系統,相對要求會低一些。

3、用戶應該儘量避免多個overlayfs使用同一個upperdir或workdir,儘管默認情況下是可以掛載成功的,但是內核還是會輸出告警日誌來提示用戶。

4、用戶指定的lowerdir最多可以支持500層。雖然如此,但是由於mount的掛載選項最多支持1個page的輸入(默認大小爲4KB),所以如果指定的lowerdir數量較多且長度較長,會有溢出而導致掛載失敗的風險(目前內核的-o掛載選項不支持超過1個內存頁,即4KB大小)。

5、指定的upperdir和workdir所在的基礎文件系統的readdir接口需要支持dtype返回參數,否則將會導致本應該隱藏的whiteout文件(後文介紹)暴露,當然目前ext4和xfs等主流的文件系統都是支持的,如果不支持那內核會給出警告提示但不會直接拒絕掛載。

6、指定的upperdir和workdir所在的基礎文件系統需要支持xattr擴展屬性,否則在功能方面會受到限制,例如後面的opaque目錄將無法生成,並且redirect dir特性和index特性也無法使用。

7、如果upperdir和各lowerdir是來自同一個基礎文件系統,那在文件觸發copyup前後,用戶在merge層通過ls命令或stat命令看到的Device和inode值保持不變,否則會發生改變。


刪除文件和目錄

刪除文件和目錄,看似一個簡單的動作,對於overlayfs實現卻需要考慮很多的場景且分很多步驟來進行。下面來分以下幾個場景開分別討論:

(1)要刪除的文件或目錄來自upper層,且lower層中沒有同名的文件或目錄

這種場景比較簡單,由於upper層的文件系統是可寫的,所有在overlayfs中的操作都可以直接體現在upper層所對應的文件系統中,因此直接刪除upper層中對應的文件或目錄即可。

示例:


這裏在upper目錄下創建了文件file和目錄dir,然後在掛載overlayfs後從merge目錄下刪除它們,可見在upper目錄下也同時被直接刪除。

(2)要刪除的文件或目錄來自lower層,upper層不存在覆蓋文件

由於lower層中的內容對於overlayfs來說是隻讀的,所以並不能像之前那樣直接刪除lower層中的文件或目錄,因此需要進行特殊的處理,讓用戶在刪除之後即不能正真的執行刪除動作又要讓用戶以爲刪除已經成功了。

Overlayfs針對這種場景設計了一套“障眼法”——Whiteout文件。Whiteout文件在用戶刪除文件時創建,用於屏蔽底層的同名文件,同時該文件在merge層是不可見的,所以用戶就看不到被刪除的文件或目錄了。whiteout文件並非普通文件,而是主次設備號都爲0的字符設備(可以通過"mknod <name> c 0 0"命令手動創建),當用戶在merge層通過ls命令(將通過readddir系統調用)檢查父目錄的目錄項時,overlayfs會自動過過濾掉和whiteout文件自身以及和它同名的lower層文件和目錄,達到了隱藏文件的目的,讓用戶以爲文件已經被刪除了。

示例:


這裏再lower層中創建文件file和目錄dir,然後在掛載文件系統之後從merge層刪除它們,然後檢查lower層中的文件依然存在並沒有被刪除,於此同時在upper層中創建了兩個同名whiteout文件,它們的文件類型爲c,即表示爲字符設備,同時主次設備號爲0,0。

3)要刪除的文件是upper層覆蓋lower層的文件,要刪除的目錄是上下層合併的目錄

該場景就理論上來講其實是前兩個場景的合併,overlayfs即需要刪除upper層對應文件系統中的文件或目錄,也需要在對應位置創建同名whiteout文件,讓upper層的文件被刪除後不至於lower層的文件被暴露出來。

示例:


這裏在upper目錄和lower目錄中都創建了文件file和目錄dir,在掛載overlayfs後從merge目錄刪除它們,然後檢查lower層中的文件依然不變,同時upper層中的原有文件已經被替換成兩個同名的whitout文件了。


創建文件和目錄

創建文件和目錄同刪除類似,overlayfs也需要針對不同的場景進行不同的處理。下面分以下幾個場景進行討論:

1)全新的創建一個文件或目錄

這個場景最爲簡單,如果在lower層中和upper層中都不存在對應的文件或目錄,那直接在upper層中對應的目錄下新創建文件或目錄即可。

示例:


這裏在一個全爲空的overlayfs中,掛載後通過merge目錄中創建文件file和目錄dir,它們直接被創建到了upper層對應的文件系統中,而lower層不受任何影響。

2)創建一個在lower層已經存在且在upper層有whiteout文件的同名文件

該場景對應前文中的場景2或場景3,在lower層中之前已經存在同名的文件或目錄了,同時upper層也有whiteout文件將其隱藏(顯然是通過merge層刪除它了),所以用戶在merge層看不到它們,可以新建一個同名的文件。這種場景下,overlayfs需要刪除upper層中的用新建的文件替換原有的whiteout文件,這樣在merge層中看到的文件就是來自upper層的新文件了。

示例:


這裏先在lower目錄中創建文件file,然後在upper目錄中創建同名的whiteout文件用於隱藏lower層中的文件(注意:此處僅是爲了演示,正常使用中用戶應避免自己創建whiteout文件),掛載文件系統後通過merge目錄新建文件file。新建文件後,在lower目錄中的原有文件不變,upper目錄中的whiteout文件已經被替換成了新創建的文件file,用戶在merge中看見的也即是這個新創建的文件。

3)創建一個在lower層已經存在且在upper層有whiteout文件的同名目錄

該場景和場景2的唯一不同是將文件轉換成目錄,即原lower層中存在一個目錄,upper層中存在一個同名whiteout文件用於隱藏它(同樣的,它是之前被用戶通過merge層刪除了的),然後用戶在merge層中又重新創建一個同名目錄。依照overlayfs同名目錄上下層合併的理念,如果此處不做任何特殊的處理而僅僅是在upper層中新建一個目錄,那原有lower層該目錄中的內容會暴露給用戶。因此,overlayfs針對這種情況引入了一種屬性——Opaque屬性,它是通過在upper層對應的目錄上設置"trusted.overlay.opaque"擴展屬性值爲"y"來實現(所以這也就需要upper層所在的文件系統支持xattr擴展屬性),overlayfs在讀取上下層存在同名目錄的目錄項時,如果upper層的目錄被設置了opaque屬性,它將忽略這個目錄下層的所有同名目錄中的目錄項,以保證新建的目錄是一個空的目錄。如下圖所示:


實際示例演示:


這裏首先在lower目錄中創建一個目錄dir,並在其中創建一個文件foo,然後在upper層創建whiteout文件dir用於隱藏lower目錄中的目錄dir,掛載文件系統後通過merge目錄新建目錄dir。觀察該新建的目錄爲空,lower層中的foo文件並沒有暴露出來,然後查看upper層中的原有whiteout文件已經被替換層新建目錄dir,同時它被設置了overlayfs的opaque屬性。


此時如果我們刪除這個opaque屬性(注意需要離線刪除,不能在掛載時操作所有基礎文件系統目錄),底層目錄dir中的foo文件就會暴露出來。


寫時複製(copy-up)特性

用戶在寫文件時,如果文件來自upper層,那直接寫入即可。但是如果文件來自lower層,由於lower層文件無法修改,因此需要先複製到upper層,然後再往其中寫入內容,這就是overlayfs的寫時複製(copy-up)特性。

示例:


這裏首先在lower目錄中新建文件file,並往其中寫入內容,掛載文件系統後,通過merge目錄寫入新的內容,觀察merge目錄下foo文件的內容,包含來原有的和新寫入的,同時觀察upper目錄中,也同樣存在一個新的從lower目錄複製上來的文件foo,內容同merge目錄中看到的一致。

當然,overlayfs的copy-up特性並不僅僅在往一個來自lower層的文件寫入新內容時觸發,還有很多的場景會觸發,簡單總結如下:

1)用戶以寫方式打開來自lower層的文件時,對該文件執行copyup,即open()系統調用時帶有O_WRITE或O_RDWR等標識;

2)修改來自lower層文件或目錄屬性或者擴展屬性時,對該文件或目錄觸發copyup,例如chmod、chown或設置acl屬性等;

3)rename來自lower層文件時,對該文件執行copyup;

4)對來自lower層的文件創建硬鏈接時,對鏈接原文件執行copyup;

5)在來自lower層的目錄裏創建文件、目錄、鏈接等內容時,對其父目錄執行copyup;

6)對來自lower層某個文件或目錄進行刪除、rename、或其它會觸發copy-up的動作時,其對應的父目錄會至下而上遞歸執行copy-up。


Rename文件和目錄

用戶在使用mv命令移動或rename文件時,mv工具首先會嘗試調用rename系統調用直接由內核完成文件的renmae操作,但對於個別文件系統內核如果不支持rename系統調用,那由mv工具代勞,它會首先複製一個一模一樣的文件到目標位置,然後刪除原來的文件,從而模擬達到類似的效果,但是這有一個很大的缺點就是無法保證整個rename過程的原子性。

對於overlayfs來說,文件的rename系統調用是支持的,但是目錄的rename系統調用支持需要分情況討論。前文中看到在掛載文件系統時,內核提供了一個掛載選項"redirect_dir=on/off",默認的啓用情況由內核的OVERLAY_FS_REDIRECT_DIR配置選項決定。在未啓用情況下,針對單純來自upper層的目錄是支持rename系統調用的,而對於來自lower層的目錄或是上下層合併的目錄則不支持,rename系統調用會返回-EXDEV,由mv工具負責處理;在啓用的情況下,無論目錄來自那一層,是否合併都將支持rename系統調用,但是該特性非向前兼容,目前內核中默認是關閉的,用戶可手動開啓。下面針對目錄的幾種場景來分別進行演示和說明:

1)關閉redirect dir特性

在關閉redirect dir特性的情況下分別對來自lower、upper和合並的目錄進行reanme操作,查看overlayfs如何進行處理。


這裏在upper目錄下創建單純來自upper層的目錄up_src,在lower目錄下創建單純來自lower層的目錄lo_src,在lower目錄和upper目錄目錄下分別創建目錄me_src表示上下層合併的目錄,各個目錄下都創建了子目錄和文件dir(x)和file(x)。在掛載overlay文件系統後,在merge層通過mv命令rename各個目錄,完成後觀察lower和merge層中的內容不變但upper層中分別創建了兩個whiteout文件lo_src和me_scr用於屏蔽lower目錄中的目錄,然後查看lo_dst和me_dst中的內容,來自lower目錄下的子目錄dir、dirb和文件file、fileb都被copyup了,通過strace跟蹤其流程(省略了文件屬性和其他保護性的操作):

rename("merge/lo_src", "merge/lo_dst")  = -1 EXDEV (Invalid cross-device link)
mkdir("merge/lo_dst", 0700)             = 0
rename("merge/lo_src/dir", "merge/lo_dst/dir") = -1 EXDEV (Invalid cross-device link)
mkdir("merge/lo_dst/dir", 0700)         = 0
rename("merge/lo_src/file", "merge/lo_dst/file") = 0
unlinkat(4, "dir", AT_REMOVEDIR)        = 0
unlinkat(AT_FDCWD, "merge/lo_src", AT_REMOVEDIR) = 0

可以看出,mv工具首先調用rename系統調用嘗試對lo_src目錄進行rename,但是失敗並返回-EXDEV,於是它就創建了lo_dst目錄然後依次對子目錄dir和文件file進行處理,子目錄dir的處理方式同lo_src類似,文件file可以直接rename成功(copyup),最後刪除原始的目錄dir和lo_src即可。這一系列的模擬動作後,在merge層最後呈現給用戶的結果是預期的,但由於是多個系統調用下發,整個過程非原子,如果操作執行過程中發生了系統奔潰,那在系統恢復後,用戶就可能會發現lo_src和lo_dst同時存在的情況(這類似與跨文件系統rename)。

2)打開redirect dir特性

打開redirect dir之後,將支持單純來自lower層和合並目錄的rename系統調用。由於目錄裏可能會包含很多子目錄或文件,overlayfs需要保證rename系統調用的原子性,因此它不能像mv命令那樣將目錄裏的各個子目錄和文件都挨個copyup到upper層中,所以overlayfs設計了一種redirect xattr擴展屬性,其內容是lower層原始目錄的相對路徑(相對lower層掛載根目錄或當前rename目錄的父目錄),設置在upper層中的目標目錄上,並不會copyup原始目錄中的子目錄或文件。用戶通過merge目錄掃描目錄項時,overlayfs在掃描upper層目錄時會檢查它的redirect xattr擴展屬性並找到原始lower層目錄,同時將原始目錄下的目錄項也返回給用戶。如下圖所示:


如圖,用戶在merge層執行”mv DirA DirX“和”mv DirB DirY“之後,原始lower層中的foo文件和bar1文件並不會copyup到目標upper層中的DirX和DirY中,取而代之的時在DirX和DirY中的redirect xattr擴展屬性,用戶在merge層看到的DirX目錄和DirY目錄來自與upper層,但是其中的內容卻部分來自與lower層。其實就本質上來看,redirect dir其實只是一種特殊類型的merge dir,只不過所merge的lower層目錄不在是同名目錄而是從redirect xattr中保存的名字而已。

實際示例如下:


同前面一樣,這裏創建了lo_src和me_src及其子目錄和文件,然後在啓用redirect dir特性的merge目錄下執行mv rename操作,完成後查看upper目錄中的內容,可以看到用於屏蔽lower層原始目錄的兩個whiteout文件同樣被創建,但是不同的是lo_dst目錄中並沒有copyup的file文件和dir目錄,me_dst目錄也同樣沒有copyup的dirb目錄和filea文件,只有原來就存在的dira目錄和filea文件。最後查看lo_dst目錄的redirect擴展屬性和me_dst目錄的擴展屬性分別指向了相對同級父目錄的lo_src目錄和me_src目錄。

原子性保證(Workdir)

前文中介紹了文件目錄的創建、刪除和rename等操作以及寫時複製特性,描述了overlayfs處理這些操作的細節,但是有一點還沒有提到,那就是overlayfs是如何保證這些操作的原子性的。例如,當用戶在刪除上下層都存在的文件時,overlayfs需要刪除upper層的文件然後創建whiteout文件來屏蔽lower層的文件,想要創建同名文件必然需要先刪除原有的文件,這刪除和創建分爲兩個步驟,如何做到原子性以保證文件系統的一致性?我們當然不希望見到文件刪除了但是whiteout文件卻沒有創建的情況。又例如用戶在觸發copyup的時候,文件並不可能在一瞬間就完整的拷貝到upper層中,如果系統崩潰,那在恢復後用戶看到的就是一個被損壞的文件,也同樣需要保證原子性。

對於這個問題,我們來關注前面掛載文件系統指定的workdir目錄,在掛載文件系統後該目錄下會創建一個爲空的work目錄,這個目錄就是原子性保證的關鍵所在,下面針對不同的場景來分析overlayfs是如何使用這個目錄的。

1)刪除upper層文件/目錄並創建whiteout的過程


如上圖所示,以文件爲例,若用戶刪除刪除文件foo,overlayfs首先(1)在workdir目錄下創建用於覆蓋lower層中foo文件的whiteout文件foo,然後(2)將該文件與upper中的foo文件進行rename(對於目錄則爲exchange rename),這樣兩個文件就原子的被替換了(原子性由基礎文件系統保證),即使此時系統崩潰或異常掉電,磁盤上的基礎文件系統中也只會是在work目錄中多出了一個未被及時刪除的foo文件而已(實際命名並不是foo而是一個以#開始的帶有序號的文件,此處依然稱之爲foo是爲了爲了便於說明),並不影響用戶看到的目錄,當再次掛載overlayfs時會在掛載階段被清除,最後(3)將work目錄中的foo文件刪除,這樣整個upper層中的foo文件就被“原子”的刪除了。

2)在whiteout上創建同名文件/目錄的過程

該過程與刪除類似,只是現在在upper層中的是whiteout文件,而在work目錄中是新創建的文件,workdir的使用流程基本一致,不再贅述。

3)刪除上下層合併目錄的過程

由於上下層合併的目錄中可能存在whiteout文件,因此在刪除之前需要保證要刪除的upper層目錄是空的,不能有whiteout文件。


如圖所示,在用戶刪除“空”目錄Dir時,其實在upper層中Dir目錄下存在一個foo的whiteout文件,因此不能直接立即通過場景1的方式進行刪除。首先(1)在work目錄下創建一個opaque目錄,然後(2)將該目錄和upper層的同名目錄進行exchange rename,這樣upper層中的Dir目錄就變成了一個opaque目錄了,它將屏蔽底層的同名Dir目錄。最後(3)將workdir下的Dir目錄裏的whiteout文件全部清空後再刪除Dir目錄本身。這樣就確保了Dir目錄中不存在whiteout文件了,隨後的步驟就同場景一一樣了。需要注意的是,這一些列的流程其實對於upper層來說,包含了(1)原始目錄(2)opaque目錄(3)whiteout文件的這3個狀態,該過程並不是原子的,但在用戶看來只有兩種狀態,一是刪除成功,此時upper層已經變成狀態3,還有一種是未刪除,對應upper層是狀態1或狀態2,所以中間的opaque目錄狀態並不會影響文件系統對用戶的輸出,依然能夠保證文件系統的一致性。

4)文件/目錄copyup的過程

在件的copyup過程中由於文件沒有辦法在一個原子操作中完成的拷貝到upper層中的對應目錄下(不僅僅是數據拷貝耗時,還包含文件屬性和擴展屬性的拷貝動作),所以這裏同樣用到了work目錄作爲中轉站。


這裏以文件copyup爲例,首先(1)根據基礎文件系統時候支持tempfile功能(將使用concurrent copy up來提升併發copyup的效率),若支持則在work目錄下創建一個臨時tmpfile,否則則創建一個真實foo文件,然後從lower層中的foo文件中拷貝數據、屬性和擴展屬性到這個文件中,接下來(2)若支持tempfile則將該temp文件鏈接到upper目錄下形成正真的foo文件,否則在upper目錄下創建一個空的dentry並通過rename將work目錄下的文件轉移到upper目錄下(原子性由基礎層文件系統保證),最後(3)釋放這個臨時dentry。至此,由於非原子部分全部在work目錄下完成,所以文件系統的一致性得到保證。另外,這裏還需要說明的一點是,如果基礎層的文件系統支持flink,則此處的步驟1中的數據拷貝將使用cloneup功能,不用再大量複製數據塊,copyup的時間可以大幅縮短。

Origin擴展屬性和Impure擴展屬性

Overlayfs一共有5中擴展屬性,前文中已經看到了opaque和redirect dir這兩種擴展屬性,這裏介紹origin和impure擴展屬性,這兩種擴展屬性最初是爲了解決文件的st_dev和st_ino值在copyup前後發生變化問題而設計出來的。其中origin擴展屬性全稱爲"trusted.overlay.origin",保存的值爲lower層原文件經過內核封裝後的數據結構進行二進制值轉換爲ASCII碼而成,設置在upper層的copyup之後的文件上,現在只需要知道overlayfs可以通過它獲取到該文件是從哪個lower層文件copyup上來的即可。另一個impure擴展屬性的全程爲"trusted.overlay.impure",它僅作用於目錄,設置在upper層中的目錄上,用於標記該目錄中的文件曾經是從底層copyup上來的,而不是一個純粹來自upper層的目錄。

下面以一個簡單的示例展示它們是如何保證st_ino的一致性的:


這裏首先在lower目錄下創建一個文件file,並在upper目錄下創建目錄dir,在掛載文件系統之後使用mv命令在merge目錄中將文件file rename到目錄dir下,這樣就觸發了文件file的copyup,此後用戶看到的文件將來自upper/dir目錄,但它的inode值在mv前後並沒有發生任何變化。這就得歸功於upper/dir/file上的origin屬性和upper/dir目錄上的impure擴展屬性了,它爲下圖中的兩個場景做了區分:


上圖中左邊的場景,upper目錄下的Dir目錄和File文件在掛載之前就已存在,它們沒有origin和impure擴展屬性,在用戶查詢目錄項時,overlayfs將直接返回upper目錄下file文件的st_ino值。而上圖中右邊的場景就是示例中構造的場景,upper/Dir/File文件從lower目錄中copyup上來,此時爲了保證st_ino的一致性,所以st_ino值還必須給用戶顯示lower目錄中file文件的st_ino,因此File文件上的origin擴展屬性使得overlayfs可以通過它找到lower/File並返回它的st_ino值,而DIr文件上的impure擴展屬性也會使得即使目錄並不是上下層合併的,也會強制其在掃描目錄項時去獲取可能存在的origin st_ino值。

最後總結一下哪些場景會設置和使用origin和impure擴展屬性:

1)在觸發文件或目錄copyup時會設置origin屬性,注意文件不能爲多硬鏈接文件(啓動index特性除外,下一節細述),因爲這樣會導致多個不同的upper層文件的origin屬性指向同一個lower層原始inode,從而導致st_ino重複的問題。

2)在啓動index屬性之後,在掛載文件系統時會檢查並設置upper層根目錄的origin擴展屬性指向頂層lower根目錄,同時檢查並設置index目錄的origin擴展屬性指向upper層根目錄。

3)在overlayfs查找文件(ovl_lookup)時會獲取origin擴展屬性,找到lower層中的原始inode並和當前inode進行綁定,以便後續保證st_ino一致性時使用。

4)在upper層目錄下有文件或子目錄發生copyup、rename或鏈接一個origined的文件,將對該目錄設置impure擴展屬性。

5)在遍歷目錄項時,如果檢測到目錄帶有impure擴展屬性,在掃描其中每一個文件時,都需要檢測origin擴展屬性並嘗試獲取和更新lower層origin文件的st_ino值。

Index特性

前文中看到overlayfs還提供了一個掛載選項“index=on/off”,可以通過勾選內核選項OVERLAY_FS_INDEX默認開啓,該選項和redirect dir選項一樣也不是向前兼容的。該選項在Linux-4.13正式合入內核,目前該選項的功能還在不斷開發中,目前用於解決lower層硬鏈接copyup後斷鏈問題,後續還會用於支持overlayfs提供NFS export和snapshot的功能。我們首先來分析index屬性是如何修復硬鏈接斷鏈的問題,然後再看一下開啓index屬性之後overlayfs會哪些變化。

1)Hard link break問題

設想以下場景,在lower層中有一個文件有2個硬鏈接,分別爲FileA、FileB和FileC,它們共享同一個inode,如下圖所示:


此時若其中一個hardlink FileA發生了copyup,則FileA將成爲一個單獨的文件展現給用戶,而FileB和FileC還將是硬鏈接的關係。具體示例如下:


首先在lower目錄下創建filea,然後依次爲它創建硬鏈接fileb和filec。在掛載overlayfs之後,在merge層中可以看到它們的inode號都爲270031,且鏈接數爲3。在執行touch命令觸發了filea文件的copyup之後,在merge目錄中的filea文件將源自upper目錄,它的inode和upper目錄中的一致,且鏈接數爲1,同時fileb和filec的鏈接數依然爲3。

更進一步,如果此時刪除filea或是在觸發copyup之前就刪除filea,那結果會是如何?顯然不會得到一個滿足一致性的結果,這裏就不詳細演示了,這個問題很明顯是不滿足POSIX標準的。下面來看啓動index屬性之後,硬鏈接文件的copyup過程與結果會有哪些變化:


在開啓index屬性後,在掛載文件系統時會在用戶指定的workdir目錄下創建一個名爲index的目錄用於存放鏈接文件(同work目錄平級),這些鏈接文件的名字和origin擴展屬性一樣是以內核數據結構按照二進制ASCII碼轉換形成,並不是文件的原始名字。當前場景中,當用戶觸發copyup後,首先的一點區別就是複製的位置不再是upper層的parent目錄而是index目錄,overlayfs先按照標準的copyup流程將FileA文件copyup到index目錄下,文件名爲一串二進制數據,可暫時不用關心;隨後對該文件進行鏈接,鏈接文件FileA到正確的目的位置,最後爲FileA文件設置"trusted.overlay.nlink"擴展屬性值爲"U+1"(這是overlayfs 5種擴展屬性中的最後一種),其含義就是在merge層向用戶展現鏈接數時使用upper層對應inode的鏈接數值並+1(當然也會出現其他數值和+-的情況),而此時在upper層中的FileA文件的鏈接數爲2,因此最終展現給用戶的鏈接數爲3,與copyup之前保持一致。實際示例如下:


對照之前沒有啓用index屬性的示例,此時在copyup前後merge目錄中的內容完全一致。同時觀察index目錄創建了一個“名字古怪”的文件,它和upper目錄中的filea文件爲硬鏈接關係,鏈接數爲2,最後查看upper目錄中的filea文件的"trusted.overlay.nlink"擴展屬性值爲"U+1"。

2)Index屬性開啓後對overlay發生的變化

2.1)文件系統掛載時的變化:

(1)明確一個upperdir或workdir無法同時被多個overlayfs所使用,若被複用不再僅僅是內核輸出告警日誌,而是會拒絕掛載,因爲潛在的併發操作會影響index屬性對overlayfs的一致性從而導致不可預期的結果。

(2)要求所有的underlaying文件系統都必須支持export_operations接口,若upperdir所在的文件系統不支持會給出告警(暫時沒有使用該功能所以不強制),而各lowerdir所在的文件系統若不支持則直接拒絕掛載。

(3)如果一套lowerdir、upperdir和workdir已經配套掛載過一次overlayfs,那之後的掛載也必須和之前的配套,否則拒絕掛載,原因是index屬性的開啓很可能之前一次掛載時設置的origin xattr擴展屬性已經固化,若後續掛載不配套則會導致origin xattr變得無效而出現不可預期的結果(通過upper根目錄和index目錄的origin擴展屬性進行驗證)。

2.2)硬鏈接文件的copyup變化:

(1)硬鏈接文件在copyup時首先copyup到index目錄,然後再link到目標目錄,同時會在copyup後的文件中設置nlink擴展屬性用於計算文件的硬鏈接數;

(2)硬鏈接文件在copyup時被可以被設置origin屬性,因爲此時由於已經解決了硬鏈接文件斷鏈問題,不存在多個upper層文件origin屬性指向同一個lower層原始inode的問題了;

(3)在創建硬鏈接和刪除硬鏈接文件時,會觸發重新計算和設置nlink值。

約束與限制

由於overlayfs依賴與底層的基礎文件系統,它的工作方式也也和普通的磁盤文件系統存在着很大的不同,同時在掛載overlayfs之後,基礎層的目錄對用戶也是可見的,爲了避免文件系統的不一致,overlayfs強烈建議用戶在掛載文件系統之後還同步操作基礎層的目錄及其中的內容。與此同時,在overlayfs在被umount的時候,也應該儘量避免手動調整其中的whiteout文件或擴展屬性,否則當文件系統再次掛載後,其狀態和一致性很可能會和預期的不同,甚至出現報錯。

小結

本文介紹了目前overlayfs的常用使用方法和背後的原理與實現原理,包括文件系統的掛載、增刪文件和目錄等操作,詳細描述了文件系統的上下層同名目錄合併、同名文件覆蓋和文件寫時複製3大基本功能,opaque、redirect dir、origin、impure和nlink 5種擴展屬性,以及redirect dir和index這兩項附加特性。下一篇博文將對其中比較關鍵的點更進一步細化,從源碼的角度分析其中的實現細節。

參考文獻

1、Documentation/filesystems/overlayfs.txt

2、Linux kernel source code

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章