歡迎光臨
每天分享高質量文章

mount namespace和共享子樹

沒有一個mount namespace是一座孤島

2

本文翻譯自lwn上一個namespace系列文章的一篇,原文連結會在文末給出,各位看官開始進入mount namespace的世界吧!

Mount namespaces用來建立一個使用者或者容器獨立的檔案系統樹,是一個強大且靈活的工具。同時,mount namespaces也有一些比較複雜的特點。本文,作為一系列的namespace文章延續,將對這些複雜性進行解釋。我們將更細緻的說明一下共享子樹(shared subtrees) 這個特徵,透過共享子樹,掛載(mount)和解除安裝(unmount)事件可以透過一種自動,可控的方式在不同的名稱空間之間傳遞。

導論

Mount namespaces 是第一個進入Linux內核的namespace,首次出現在Linux 2.4.19版本。它們隔離了每個行程可以看到的掛載點串列,或者換句話說,每個Mount namespace都有它們自己的掛載點串列,意味著在不同namespace中的行程都可以看到且控制不同的目錄層次結構(目錄樹)。

當系統啟動時,只有一個“ 初始名稱空間”。新的mount名稱空間透過設定clone()系統呼叫中的CLONE_NEWNS來建立,或者透過unshare()系統呼叫將呼叫者遷移到一個新的名稱空間。當一個新的名稱空間被建立時,它將繼承呼叫clone()和unshare()的行程的名稱空間的全部掛載點串列(mount point list)

呼叫clone()或者unshare()之後,透過mount()或者unmount()函式,每個namespace中的掛載點可以被動態的刪除或者增加。預設情況下,對掛載點串列的修改只對處於該名稱空間的行程是可見的,對於其他名稱空間中的行程則是無效的。

Mount namespaces 有多種用途。比如說,它們(指代名稱空間)可以給每個使用者提供一個獨立的檔案系統檢視。其他的用途包括為新建立的pid namespaces掛載一個/proc檔案系統,或者實現類似chroot() 的目錄層次結構的隔離。在一些應用中,mount namespaces是和 bind mounts聯合使用的。

共享子樹(Shared subtrees)

在使用mount namespaces的過程中,使用者空間的程式員可能會遇到一個難題:mount namespaces提供了過度的隔離。設想一下,當我們將一個光碟插入到光碟機中,在初始的mount namespace中,令光碟在所有的namespace中可見的唯一方法是,將光碟掛載到每一個namespace中。在許多情況下,如果能夠透過一次掛載就使得光碟對所有的namespace都是可見的,那麼這將是一件十分絕佳的事情。

由於以上所描述的難題,共享子樹機制被引進了Linux 2.6.15(2006年早期,大約是Mount namespace第一次釋出後的三年)。共享子樹最核心的特徵是允許掛載和解除安裝事件以一種自動的,可控的方式在不同的namespaces間傳遞(propagation)。這就意味著,在一個名稱空間中掛載光碟的同時也會觸發對於其他namespace對同一張光碟的掛載。

在共享子樹中,每個掛載點都都存在一個名為傳遞型別(propagation type)的標記,該標記決定了一個namespace中建立或者刪除的掛載點是否會傳遞到其他的namespaces。

有四種傳遞型別:

  • MS_SHARED:該掛載點和它的 “對等組”(peer group,接下來會詳細解釋它)共享掛載和解除安裝事件。當一個掛載點被刪除或者新增到namespace中,這些事件會被傳遞到它的對等組,這樣掛載和解除安裝事件會發生在所有對等掛載點。傳遞也會發生在相反的方向。也就是說,事件的傳遞是雙向的。

  • MS_PRIVATE:  和共享掛載相反,標記為private的事件不會傳遞到任何的對等組,掛載操作預設使用該標誌。

  • MS_SLAVE:  這個傳遞型別介於shared 和 slave之間,一個slave mount擁有一個master(一個共享的對等組),該對等組中的成員可以將事件傳遞到他的slave mount。但是slave mount不能將事件傳遞給master mount。

  • MS_UNBINDABLE:  該掛載點是不可系結的。

有幾點值得展開說一下。第一點是傳遞型別是一個針對掛載點的設定(per-mount-point setting)。在一個名稱空間中,一些掛載點被標記為 shared,其他的掛載點可以被標記為Private(或者slave, unbindable)。

第二點要說明的是,傳遞型別決定了掛載和解除安裝的傳遞。因此,在一個共享掛載X中建立一個子掛載 Y(表示掛載點Y是掛載點X的子目錄),子掛載會傳遞到對等組的其他掛載中。然而,X的傳遞型別不會影響到在Y中建立或者刪除的掛載點。Y中的事件是否會傳遞到其他的對等組中取決於Y的掛載型別。類似的,當X本身被解除安裝,該解除安裝事件是否會被傳遞,也同樣取決於X的傳遞型別。

另外,有一點值得闡明的是,本文中的“事件(event)”是一個相對抽象的名詞,意思是“有一些事情發生”。事件傳遞的概念並不意味著不同的掛載點之間的訊息傳遞。恰恰相反,事件傳遞應該被闡述為當一個掛載點的掛載或者解除安裝事件發生時會觸發其他掛載點的相同的事件。

Peer groups(對等組)

對等組是一組掛載點,並且一個對等組可以將掛載和解除安裝事件傳遞給另一個對等組。

舉例來說,假設一個執行在初始mount namespace的shell,我們使得根掛載為private,並且建立兩個共享的掛載點:

建掛載點和改變傳遞型別需要root許可權。

然後,在第二個視窗中,我們使用ushare()系統呼叫建立一個新的mount namespace,併在新的namespace中執行一個shell:

-m 表示建立一個新的namespace,–propagation unchanged的意義我們隨後解釋)

傳回到第一個視窗,我們使用系結掛載將/X掛載到/Z。

完成這些步驟之後,名稱空間和對等組的情況如下所示:

這個場景中,有兩個對等組:

  • 在第一個對等組中包含掛載點 X, X’( X的掛載點的複製在第二個名稱空間被建立時),Z(由掛載點X系結掛載得到)。

  • 第二個對等組包含掛載點Y和Y’(掛載點Y的複製品,當第二個名稱空間被建立時)。

註意系結掛載的掛載點Z,該掛載點在第一個名稱空間中,當第二個名稱空間被建立時建立,掛載點Z不會在第二個掛載點內被覆制,因為父掛載點被標記為private。

譯者記:可以看到即便是不同的namespace中的掛載點也是可能處於同一個對等組的。同時,只要一個掛載點是shared型別的,它就會傳遞到所有namespace中的對等組。 另外Peer group的翻譯不是很恰當,希望讀者能夠提出自己的意見。

透過/proc/PID/mountinfo檢視傳遞型別和對等組

/proc/PID/mountinfo顯示的是行程PID所在的mount namespace 的掛載點資訊。所有位於同一名稱空間的行程都將看到相同的檢視。該檔案比/proc/PID/mounts能夠提供更加豐富的資訊。檔案中的每一條記錄都存在一個可選欄位,該欄位顯示了掛載點的傳遞型別和對等組(共享掛載)該欄位可以為空。

對於共享掛載,/proc/PID/mountinfo中的每條記錄的可選欄位都包含一個格式為 shared: N型別的標記。Shared標記表示該掛載點和同一對等組共享傳遞事件。對等組由整數N來標識。這些ID從1開始分配,如果一個對等組內的成員數為0,閑置的ID可能會被回收。

舉例來說,如果我們在第一個shell中列印/proc/self/mountinfo,我們可以看到如下的輸出:

輸出可以看到,在可選欄位的shared標誌為空,表明根掛載點是private。我們也可以看到掛載點/X和/Z屬於同一個對等組(組ID = 1), 意味著掛載和解除安裝事件會在這兩個掛載點之間傳遞。掛載點/Y屬於另一個對等組(組ID = 2)。

/proc/PID/mountinfo檔案讓我們可以得到不同掛載點的親屬關係。每條記錄的第一個欄位是一個掛載點特有的ID。第二個欄位是父掛載點的ID。從上述輸出我們可以看到,掛載點/X /Y /Z的父掛載點ID是61。

在第二個視窗中(第二個namespace)列印/proc/PID/mountinfo, 我們可以看到:

樣的根目錄是私有掛載點。/X屬於第一個對等組,初始名稱空間的/X和/Z同樣屬於第一個對等組。掛載點/Y則屬於另一個對等組。最後一點需要說明的是,第二個namespace中的被覆制過來的掛載點和它的另一個物體擁有不同的ID。

譯者註,看了這麼多,那麼究竟一個對等組都包含那種型別的掛載點呢?可以看到,只要一個掛載點是MS_SHREAD型別的,那麼系統中所有namespace中的相等掛載點都屬於同一個對等組。另外,透過bind mount得到的掛載點也屬於同一個對等組。

關於預設值的爭議

因為情況有一點複雜,目前為止,我們都避免討論傳遞型別的預設值。對於核心來說,一個新的掛載點被建立時的情況如下:

  • 如果一個掛載點有一個父掛載點,並且父掛載點的型別時MS_SHARED,那麼新的掛載點的傳遞型別也是MS_SHARED。

  • 否則,新的掛載點的型別時MS_PRIVATE。

根據這些規則,根掛載點應該時MS_PRIVATE,這樣它的子掛載點預設都是MS_PRIVATE。然而,由於MS_SHARED是更為常用的一種傳遞型別,所以預設值設為MS_SHARED也更為合理。這樣,systemd將所有掛載點的傳遞型別設為MS_SHARED。 因此,在大多數現代Linux發行版中,預設傳遞型別實際上是MS_SHARED。但是,還不能就此作結論。當使用ushare()函式建立一個新的名稱空間時,會將名稱空間內的所有新建立的掛載點的預設值設定為MS_PRIVATE, 如下命令所示,遞迴的將根目錄下的所有掛載點設定為MS_PRIVATE。

了防止unshare將預設值設定為MS_PRIVATE,我們可以使用如下命令建立新的名稱空間:

THE END

本文中,我們講解了mount namespace和共享子樹,大家也可以閱讀其他lwn上其他關於namespace的文章。

namespace系列連結:https://lwn.net/Articles/531114/#series_index

原文連結:

https://lwn.net/Articles/689856/

贊(0)

分享創造快樂