Mount namespaces and shared subtrees

掛載命名空間是創建每-用戶和每-容器文件系統樹的強大而靈活的工具。本文中,我們將仔細研究共享子樹特性,它可通過自動、可控的方式在掛載命名空間之間傳播掛載和卸載事件。

引言

掛載命名空間是第一個添加到 Linux 的命名空間類型,出現在 2002 年的 Linux 2.4.19 中。它們可隔離命名空間中的進程所看到的掛載點列表。換言之,每個掛載命名空間都有自己的掛載點列表,這意味着不同命名空間中的進程可以看到並操作單個目錄層次結構的不同視圖。

當系統首次啓動時,有一個單一的掛載命名空間,即所謂的“初始命名空間”。帶 CLONE_NEWNS 標誌的 clone()(在新命名空間中創建新子進程)或 unshare()(將調用方移到新命名空間中)可創建新的掛載命名空間。當新的裝掛載名空間被創建時,它將接收從 clone() 或 unshare() 的調用者的命名空間複製的掛載點列表。

在 clone() 或 unshare() 之後,可以在每個命名空間中獨立地添加和刪除掛載點(通過 mount() 和 umount() )。對掛載點列表的更改(默認情況下)僅對進程所在的掛載命名空間中的進程可見;這些更改在其他掛載命名空間中不可見。

掛載命名空間有多種用途。例如,可以提供文件系統的每個用戶視圖。還有其它用途,可以爲新的 PID 命名空間掛載 /proc 文件系統,而不會對其它進程造成副作用,還可通過 chroot() 將進程隔離到單個目錄層次結構中。在某些用例中,掛載命名空間與綁定掛載一起使用。

共享子樹

掛載命名空間實現後,用戶空間的程序員就遇到了一個可用性問題:掛載命名空間在命名空間之間提供了太多的隔離。例如,假設一個新磁盤加載到一個光盤驅動器中。在原來實現中,使該磁盤在所有掛載命名空間中可見的唯一方法是在每個命名空間中分別掛載該磁盤。但在許多情況下,最好僅執行一個掛載操作,就可使磁盤在系統上的所有掛載命名空間(或某些子集)中可見。

因此,共享子樹特性被添加到 Linux 2.6.15 中(在 2006 年初,即大約掛載命名空間實現了三年後)。共享子樹的主要優點是允許在命名空間之間自動、可控地傳播裝載和卸載事件。這意味着,例如,在一個掛載命名空間中掛載一個光盤可能會使得所有其他命名空間中都掛載該光盤。

在共享子樹特性下,每個掛載點都用“傳播類型”標記,該類型決定在此掛載點下創建和刪除的掛載點是否傳播到其他掛載點。有四種不同的傳播類型:

  • MS_SHARED:此掛載點與同一“對等組”中的其它掛載點共享掛載和卸載事件(下面將更詳細地描述)。當在此掛載點下添加或刪除掛載點時,此更改將傳播到對等組,因此掛載或卸載也會發生在每個對等掛載點。傳播也會反向進行,因此對等掛載上的掛載和卸載事件也會傳播到此掛載點。
  • MS_PRIVATE:與共享裝載點相反。裝載點不會將事件傳播到任何對等方,也不會從任何對等方接收傳播事件。
  • MS_SLAVE:這種傳播類型介於共享掛載和私有掛載之間。從裝載有一個主掛載—一個共享對等組,其成員將掛載和卸載事件傳播到從裝載。但是,從裝載不會將事件傳播到主掛載對等組。
  • MS_UNBINDABLE:該掛載點不可綁定。與私有裝載點一樣,此裝載點不會將事件傳播到對等方或接收來自對等方傳播的事件。此外,此掛載點不能作爲綁定掛載操作的源。

值得對上面幾點進行擴展。首先,傳播類型是一個每–掛載點–配置。在命名空間中的,某些掛載點可能標記爲共享,而其他掛載點則標記爲私有(或從屬或不可綁定)。

要強調的第二點是,傳播類型決定了在掛載點下掛載和卸載事件的傳播。因此,如果在共享掛載 X 下創建了子掛載 Y,則該子掛載將傳播到對等組中的其它掛載點。但是,X 的傳播類型不會影響在 Y 下創建和刪除的掛載點;Y 下的事件的傳播與否取決於對 Y 傳播類型的定義。類似地,當 X 被卸載時,卸載事件是否會傳播取決於其父掛載點爲其定義的傳播類型。

順便說一句,也許值得澄清的是,“事件”這個詞在這裏被用作一個抽象的術語,意思是“發生了什麼事”。事件傳播的概念並不意味着在掛載點之間傳遞某種類型的消息。相反,它的思想是,一個掛載點上的某個掛載或卸載操作觸發了一個或多個其它掛載點上的相同操作。

最後,掛載既可以是主對等組的從屬,也可以與它自己的對等組(即所謂的主從和共享掛載)共享事件。在這種情況下,掛載可能從主服務器接收傳播事件,然後這些事件將傳播到其對等服務器。

對等組

對等組是一組掛載點,它們互相傳播掛載和卸載事件。當傳播類型是共享的掛載點在創建新命名空間時被複制或作爲綁定掛載的源時,對等組會獲得新成員。(對於綁定掛載,其細節比我們這裏描述的要複雜得多;具體可查看內核源文件 Documentation/filesystems/sharedsubtree.txt )。在這兩種情況下,新掛載點都會成爲現有掛載點的對等組的成員。相反,掛載點在卸載時不再是對等組的成員,無論是顯式的還是隱式的,如當掛載命名空間的最後一個成員進程終止或移動到另一個命名空間。

例如,假設在運行於最初掛載命名空間中的 shell 中,我們將根掛載點設爲私有,並創建兩個共享掛載點:

sh1# mount --make-private /
sh1# mount --make-shared /dev/sda3 /X
sh1# mount --make-shared /dev/sda5 /Y

shell 中的“#” 表明,在 shell 會話中使用各種掛載命令來創建掛載點並更改其傳播類型時,必須使用特權。

然後,在第二個終端上,我們使用 unshare 命令創建一個新的掛載命名空間,在其中運行 shell:

sh2# unshare -m --propagation unchanged sh

(-m 選項創建一個新的掛載命名空間;後面將解釋 --propagation unchanged 的用途。)

返回到第一個終端,創建一個對 /X 掛載點的綁定掛載:

sh1# mkdir /Z
sh1# mount --bind /X /Z

按照這些步驟,我們將看到下圖所示的情況。
在這裏插入圖片描述
在這種情況下,有兩個對等組:

  • 第一個對等組包含掛載點 X、X’(掛載點 X 的副本)和 Z(對最初命名空間中源掛載點 X 的綁定掛載)。
  • 第二個對等組包含掛載點 Y 和 Y’(掛載點 Y 的副本)。

請注意,在創建第二個命名空間後纔在最初命名空間中創建的綁定掛載 Z,並沒有被複制到第二個命名空間,因爲父掛載(/)被標記爲私有。

通過 /proc/pid/mountinfo 檢查傳播類型和對等組

/proc/pid/mountinfo 文件(記錄在 proc(5) 手冊頁中)顯示了有關進程 PID 所在掛載命名空間中的掛載點的信息。位於同一掛載命名空間中的所有進程都將在此文件中看到相同的視圖。此文件旨在提供比舊的、不可擴展的 /proc/pid/mounts 文件更多的掛載點信息。此文件中的每個記錄中都包含一組(可能爲空)“可選字段”,這些字段顯示每個掛載的傳播類型和對等組(用於共享掛載)信息。

對於共享裝載,/proc/pid/mountinfo 中相應記錄中的可選字段將包含 shared:N 形式的標記。這裏,shared 標記表示掛載正與對等組共享傳播事件。對等組由 N 標識,N 是唯一標識對等組的整數值。這些 ID 從 1 開始編號,當一個對等組不存在後還可循環使用。同一對等組的所有掛載點在 /proc/pid/mountinfo 文件中的 shared:N 標記相同。

因此,例如,如果我們在上面示例中的第一個 shell 中列出 /proc/self/mountinfo 的內容,將看到以下內容(使用 sed 過濾掉一些不相關的信息):

sh1# cat /proc/self/mountinfo | sed 's/ - .*//'
61 0 8:2 / / rw,relatime
81 61 8:3 / /X rw,relatime shared:1
124 61 8:5 / /Y rw,relatime shared:2
228 61 8:3 / /Z rw,relatime shared:1

從該輸出中,我們首先看到根掛載點是私有的,因爲可選字段中沒有任何標記。我們還看到,掛載點 /X 和 /Z 位於同一對等組(ID 爲1),這意味着這兩個掛載下的掛載和卸載事件將互相傳播。 /Y 是另一個對等組(ID 2)中的共享裝載,根據定義,它不會與對等組 1 中的掛載點相互傳播事件。

還可通過 proc/pid/mountinfo 文件查看掛載點之間的父子關係。每個記錄中的第一個字段是掛載點的 ID。第二個字段是父掛載的 ID。從上面的輸出中,我們可以看到掛載點 /X、/Y 和 /Z 都是根掛載的子項,因爲它們的父 ID 都是 61。

在第二個 shell(在第二個命名空間)中運行相同的命令,我們看到:

sh2# cat /proc/self/mountinfo | sed 's/ - .*//'
147 146 8:2 / / rw,relatime
221 147 8:3 / /X rw,relatime shared:1
224 147 8:5 / /Y rw,relatime shared:2

再次,我們看到根掛載點是私有的。然後我們看到 /X 是對等組 1 中的共享掛載,與最初掛載命名空間中的掛載 /X 和 /Z 相同。最後,我們看到 /Y 是對等組 2 中的共享裝載,與最初掛載名空間中的掛載 /Y 相同。最後要注意的是,在第二個命名空間中複製的掛載點有自己的 ID,與最初命名空間中相應掛載的 ID 不同。

默認值討論

由於情況有點複雜,到目前爲止,我們避免討論新掛載點的默認傳播類型。從內核的角度來看,新掛載的默認值如下:

  • 如果掛載點有父親(即非根掛載點),並且父親的傳播類型是 MS_SHARED,則新掛載點的傳播類型也是 MS_SHARED。
  • 否則,新掛載的傳播類型爲 MS_PRIVATE。

根據這些規則,如果根掛載是 MS_PRIVATE,默認情況下,所有子掛載也將是 MS_PRIVATE。不過,MS_SHARED 是一個更好的默認值,是更常用的傳播類型。因此,systemd 將所有掛載點的傳播類型設置爲 MS_SHARED。所以,在大多數現代 Linux 發行版上,默認的傳播類型是 MS_SHARED。不過,util-linux 的 unshare 特性卻不這樣認爲。在創建新的掛載命名空間時,unshare 假定用戶需要完全隔離的命名空間,並通過執行以下命令(該命令遞歸地將根目錄下的所有掛載標記爲私有)將所有掛載點設置爲私有:

mount --make-rprivate /

爲了防止出現這種情況,我們可以在創建新命名空間時使用其它選項:

unshare -m --propagation unchanged <cmd>

結束語

在本文中,我們介紹了掛載命名空間和共享子樹的“理論”。我們現在有足夠的信息來證明和理解各種傳播類型的語義;這將是後續文章的主題。


原文:https://lwn.net/Articles/689856/
公衆號:Geek樂園
雲+社區:https://cloud.tencent.com/developer/column/4124

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