首页  >  文章  >  后端开发  >  为什么从 Go 调用 Setns 会为 Mnt 命名空间返回 EINVAL?

为什么从 Go 调用 Setns 会为 Mnt 命名空间返回 EINVAL?

Susan Sarandon
Susan Sarandon原创
2024-11-01 05:35:02567浏览

Why Does Calling Setns from Go Return EINVAL for the Mnt Namespace?

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

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn