unshare 系統呼叫

本文件描述了新的系統呼叫 unshare()。本文件提供了該功能的概述、需要它的原因、如何使用它、其介面規範、設計、實現以及如何對其進行測試。

變更日誌

版本 0.1 初始文件,Janak Desai (janak@us.ibm.com), 2006 年 1 月 11 日

目錄

  1. 概述

  2. 優點

  3. 成本

  4. 要求

  5. 功能規範

  6. 高階設計

  7. 低階設計

  8. 測試規範

  9. 未來工作

1) 概述

大多數傳統作業系統核心都支援將執行緒抽象為程序中的多個執行上下文。這些核心提供特殊的資源和機制來維護這些“執行緒”。Linux 核心以一種巧妙而簡單的方式,不區分程序和“執行緒”。核心允許程序共享資源,因此它們可以實現傳統的“執行緒”行為,而無需核心中的額外資料結構和機制。以這種方式實現執行緒的力量不僅來自於它的簡單性,還來自於允許應用程式設計師在傳統執行緒的所有或全無共享資源的限制之外工作。在 Linux 上,在建立使用 clone 系統呼叫的執行緒時,應用程式可以選擇性地選擇哪些資源線上程之間共享。

unshare() 系統呼叫向 Linux 執行緒模型添加了一個原語,允許執行緒選擇性地“取消共享”在建立時共享的任何資源。 unshare() 由 Al Viro 於 2000 年 8 月在 Linux 核心郵件列表中概念化,作為 Linux 上 POSIX 執行緒討論的一部分。 unshare() 增強了 Linux 執行緒對於希望控制共享資源而不建立新程序的應用程式的有用性。 unshare() 是 Linux 上可用原語集合的自然補充,它實現了程序/執行緒作為虛擬機器的概念。

2) 優點

unshare() 對於大型應用程式框架(如 PAM)非常有用,在這些框架中,建立新程序來控制程序資源的共享/取消共享是不可能的。由於使用 fork 或 clone 建立新程序時預設共享名稱空間,因此如果非執行緒應用程式需要與預設共享名稱空間分離,unshare() 甚至可以使它們受益。以下列出了可以使用 unshare() 的兩個用例。

2.1 每個安全上下文名稱空間

unshare() 可以用於使用核心的每個程序名稱空間機制來實現多例項化目錄。多例項化目錄,例如每個使用者和/或每個安全上下文的 /tmp、/var/tmp 例項或每個安全上下文的使用者主目錄例項,在處理這些目錄時隔離使用者程序。使用 unshare(),PAM 模組可以輕鬆地在登入時為使用者設定私有名稱空間。具有標記系統保護配置檔案的通用標準認證需要多例項化目錄,但是,隨著 Linux 核心中共享樹功能的可用性,即使是常規 Linux 系統也可以從在登入時設定私有名稱空間和多例項化 /tmp、/var/tmp 以及系統管理員認為合適的其他目錄中受益。

2.2 取消共享虛擬記憶體和/或開啟的檔案

考慮一個客戶端/伺服器應用程式,其中伺服器透過建立共享虛擬記憶體和開啟的檔案等資源的程序來處理客戶端請求。如果沒有 unshare(),伺服器必須在建立服務請求的程序時決定需要共享的內容。 unshare() 允許伺服器在服務請求期間分離上下文的部分內容。對於大型和複雜的中介軟體應用程式框架,在程序建立後進行 unshare() 的能力可能非常有用。

3) 成本

為了不重複程式碼並處理 unshare() 在活動任務上工作的事實(與 clone/fork 在新分配的非活動任務上工作相反),unshare() 不得不對 clone/fork 系統呼叫使用的 copy_* 函式進行小的重組更改。更改現有的、經過良好測試且穩定的程式碼來實現一項可能不會在開始時得到廣泛執行的新功能是有代價的。但是,透過適當的設計和程式碼審查以及為 LTP 建立 unshare() 測試,這項新功能的優勢可以超過其成本。

4) 要求

unshare() 反轉了使用 clone(2) 系統呼叫完成的共享,因此 unshare() 應該具有與 clone(2) 類似的介面。也就是說,由於 clone(int flags, void *stack) 中的標誌指定了應該共享的內容,因此 unshare(int flags) 中的類似標誌應該指定應該取消共享的內容。不幸的是,這似乎顛倒了標誌的含義,使其與 clone(2) 中使用的含義不同。但是,沒有更容易的解決方案,既不那麼令人困惑,又允許將來進行增量上下文取消共享而無需更改 ABI。

unshare() 介面應適應將來可能新增的新上下文標誌,而無需重建舊應用程式。如果並且在新增新的上下文標誌時,unshare() 設計應允許根據需要增量取消共享這些資源。

5) 功能規範

名稱

unshare - 分離程序執行上下文的部分內容

概要

#include <sched.h>

int unshare(int flags);

描述

unshare() 允許程序分離當前與其他程序共享的其執行上下文的部分內容。執行上下文的部分內容,例如名稱空間,在使用 fork(2) 建立新程序時預設共享,而其他部分內容,例如虛擬記憶體、開啟的檔案描述符等,可以透過顯式請求在使用 clone(2) 建立程序時共享它們。

unshare() 的主要用途是允許程序控制其共享執行上下文,而無需建立新程序。

flags 引數指定以下常量中的一個或按位或的幾個常量。

CLONE_FS

如果設定了 CLONE_FS,則呼叫者的檔案系統資訊與共享檔案系統資訊分離。

CLONE_FILES

如果設定了 CLONE_FILES,則呼叫者的檔案描述符表與共享檔案描述符表分離。

CLONE_NEWNS

如果設定了 CLONE_NEWNS,則呼叫者的名稱空間與共享名稱空間分離。

CLONE_VM

如果設定了 CLONE_VM,則呼叫者的虛擬記憶體與共享虛擬記憶體分離。

返回值

成功時,返回零。失敗時,返回 -1 並且 errno 為

錯誤
EPERM 非 root 程序(沒有 CAP_SYS_ADMIN 的程序)指定了 CLONE_NEWNS。

ENOMEM 無法分配足夠的記憶體來複制需要取消共享的呼叫者的部分內容

上下文。

EINVAL 指定了無效的標誌作為引數。

符合

unshare() 呼叫是 Linux 特有的,不應在旨在可移植的程式中使用。

參見

clone(2), fork(2)

6) 高階設計

根據 flags 引數,unshare() 系統呼叫分配適當的程序上下文結構,使用當前共享版本中的值填充它,將新複製的結構與當前任務結構關聯,並釋放相應的共享版本。 clone 的輔助函式 (copy_*) 不能直接被 unshare() 使用,原因如下兩個。

clone 在新分配的尚未啟用的任務結構上執行,而 unshare() 在當前活動任務上執行。因此,unshare() 必須在關聯新複製的上下文結構之前獲取適當的 task_lock()

  1. unshare() 必須分配和複製所有正在取消共享的上下文結構,然後才能將它們與當前任務關聯並釋放較舊的共享結構。不這樣做會在嘗試因錯誤而回滾時建立競爭條件和/或 oops。考慮取消共享虛擬記憶體和名稱空間的情況。在成功取消共享 vm 之後,如果系統呼叫在分配新的名稱空間結構時遇到錯誤,則錯誤返回程式碼將必須反轉 vm 的取消共享。作為反轉的一部分,系統呼叫將必須返回到較舊的共享 vm 結構,該結構可能不再存在。

  2. 因此,copy_* 函式中分配和複製當前上下文結構的程式碼被移動到新的 dup_* 函式中。現在,copy_* 函式呼叫 dup_* 函式來分配和複製適當的上下文結構,然後將它們與正在構建的任務結構關聯。另一方面,unshare() 系統呼叫執行以下操作

檢查標誌以強制執行缺失但隱含的標誌

  1. 對於每個上下文結構,如果 flags 引數中設定了適當的位,則呼叫相應的 unshare() 輔助函式來分配和複製新的上下文結構。

  2. 如果在分配和複製中沒有錯誤並且存在新的上下文結構,則鎖定當前任務結構,將新的上下文結構與當前任務結構關聯,並釋放當前任務結構上的鎖。

  3. 適當地釋放較舊的共享上下文結構。

  4. 7) 低階設計

unshare() 的實現可以分為以下 4 個不同的專案

重組現有的 copy_* 函式

  1. unshare() 系統呼叫服務函式

  2. 每個不同程序上下文的 unshare() 輔助函式

  3. 註冊不同架構的系統呼叫號

  4. 7.1) 重組 copy_* 函式

每個 copy 函式,例如 copy_mm、copy_namespace、copy_files 等,都大致有兩個元件。第一個元件分配和複製適當的結構,第二個元件將其連結到作為引數傳遞給 copy 函式的任務結構。第一個元件被拆分為它自己的函式。這些 dup_* 函式分配和複製適當的上下文結構。重組後的 copy_* 函式呼叫它們相應的 dup_* 函式,然後將新複製的結構連結到呼叫 copy 函式的任務結構。

7.2) unshare() 系統呼叫服務函式

檢查標誌強制執行隱含的標誌。如果設定了 CLONE_THREAD,則強制執行 CLONE_VM。如果設定了 CLONE_VM,則強制執行 CLONE_SIGHAND。如果設定了 CLONE_SIGHAND 並且訊號也被共享,則強制執行 CLONE_THREAD。如果設定了 CLONE_NEWNS,則強制執行 CLONE_FS。

  • 對於每個上下文標誌,使用傳遞給系統呼叫的標誌和指向新取消共享結構的指標的引用來呼叫相應的 unshare_* 輔助例程

  • 如果任何新結構由 unshare_* 輔助函式建立,則獲取當前任務上的 task_lock(),修改適當的上下文指標,並釋放任務鎖。

  • 對於所有新取消共享的結構,釋放相應的舊的共享結構。

  • 7.3) unshare_* 輔助函式

對於對應於 CLONE_SYSVSEM、CLONE_SIGHAND 和 CLONE_THREAD 的 unshare_* 輔助函式,返回 -EINVAL,因為它們尚未實現。對於其他輔助函式,檢查標誌值以檢視是否需要取消共享該結構。如果是,則呼叫相應的 dup_* 函式來分配和複製結構並返回指向它的指標。

7.4) 最後

適當地修改特定於架構的程式碼以註冊新的系統呼叫。

8) 測試規範

unshare() 的測試應測試以下內容

有效標誌:測試以檢查訊號和訊號處理程式的 clone 標誌(尚未實現取消共享)是否返回 -EINVAL。

  1. 缺失/隱含的標誌:測試以確保在未指定取消共享檔案系統的情況下取消共享名稱空間是否正確地取消共享名稱空間和檔案系統資訊。

  2. 對於四個(名稱空間、檔案系統、檔案和 vm)支援的取消共享中的每一個,驗證系統呼叫是否正確取消共享適當的結構。驗證單獨取消共享它們以及彼此組合取消共享是否按預期工作。

  3. 併發執行:在 shm 段中的地址上使用共享記憶體段和 futex 來同步大約 10 個執行緒的執行。讓幾個執行緒執行 execve,幾個執行緒執行 _exit,其餘執行緒使用不同的標誌組合取消共享。驗證是否按預期執行取消共享,並且沒有 oops 或掛起。

  4. 9) 未來工作

unshare() 的當前實現不允許取消共享訊號和訊號處理程式。訊號一開始就很複雜,並且取消共享當前執行程序的訊號和/或訊號處理程式甚至更加複雜。如果在將來有允許取消共享訊號和/或訊號處理程式的特定需求,則可以將其增量新增到 unshare(),而不會影響使用 unshare() 的舊應用程式。

©核心開發社群。 | 由 Sphinx 5.3.0 & Alabaster 0.7.16 提供技術支援 | 頁面原始碼