1. 程式人生 > >深入理解overlayfs(二):使用與原理分析

深入理解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

轉自:

https://blog.csdn.net/luckyapple1028/article/details/78075358