從Go 調用Setns 會為Mnt 命名空間返回EINVAL
背景
目標是使用C 或Go 程式碼輸入容器的mnt 命名空間。然而,當嘗試進入 mnt 命名空間時,Go 程式碼始終從 setns 呼叫傳回 EINVAL。
工作C 代碼
以下C 代碼成功進入所有指定的命名空間:
<code class="c">#include <sched.h> main() { // ... for (i=0; i<5; i++) { setns(fd, 0); // Join the provided namespace } }</code>
Go 代碼失敗
另一方面,等效的Go 代碼為mnt 命名空間返回EINVAL 錯誤:
<code class="go">import ( "syscall" ) func main() { // ... err := syscall.RawSyscall(308, fd, 0, 0) // Calling setns if err != 0 { fmt.Println("setns on", namespaces[i], "namespace failed") } }</code>
答案
問題在於Go 的多線程特性。當 Go 從多執行緒上下文呼叫 setns 時,它對於 mnt 命名空間會失敗,因為它需要單執行緒呼叫者。要解決此問題,必須在 Go 運行時創建任何其他執行緒之前進行 setns 呼叫。
解決方案:單執行緒建構函式技巧
實現此目的的一種方法是使用CGO 建構子技巧,其中在Go 啟動之前執行C 函數:
<code class="c">__attribute__((constructor)) void enter_namespace(void) { setns(...); }</code>
將此建構子加入Go 程式碼中,並硬編碼PID,允許成功進入mnt 命名空間:
<code class="go">/* __attribute__((constructor)) void enter_namespace(void) { ... } */ import "C"</code>
但是,這種方法需要對PID 進行硬編碼,這並不理想。
替代方案:Linux Kernel 4.16 以上
在Linux 核心中4.16 及更高版本中,為setns 引入了一種新模式,允許從多執行緒上下文中安全地調用它。透過使用帶有附加參數 (CLONE_IOCLONE_COMMON) 的 setns 呼叫的修改版本,可以從 Go 進入 mnt 命名空間,即使它是多執行緒的。
以上是為什麼從 Go 呼叫 Setns 會為 Mnt 命名空間回傳 EINVAL?的詳細內容。更多資訊請關注PHP中文網其他相關文章!