从 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中文网其他相关文章!