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

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)

分享創造快樂