美文网首页
namespaces 学习笔记2:uts ns 源码实现

namespaces 学习笔记2:uts ns 源码实现

作者: 董泽润 | 来源:发表于2019-10-09 11:20 被阅读0次

TL;DR 最近想看 docker 相关的实现,自然涉及底层 namespace, 所以边做实验边看源码,感兴趣的先看耗子叔的文章

测试案例

uts namespace 主要是用来隔离主机名,但不只这一个功能。先上小例子,也是来自耗子叔

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
#include <sys/mount.h>

/* 定义一个给 clone 用的栈,栈大小1M */
#define STACK_SIZE (1024 * 1024)
static char container_stack[STACK_SIZE];

char* const container_args[] = {
    "/bin/bash",
    NULL
};

int container_main(void* arg)
{
    printf("Container - inside the container!\n");
    sethostname("my-container",10); /* 设置hostname */
    /* 直接执行一个shell,以便我们观察这个进程空间里的资源是否被隔离了 */
    execv(container_args[0], container_args);
    printf("Something's wrong!\n");
    return 1;
}

int main()
{
    printf("Parent - start a container!\n");
    /* 调用clone函数,其中传出一个函数,还有一个栈空间的(为什么传尾指针,因为栈是反着的) */
    int container_pid = clone(container_main, container_stack+STACK_SIZE, SIGCHLD|CLONE_NEWUTS, NULL);
    /* 等待子进程结束 */
    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return 0;
}

和上一篇测试 mount ns 不同,本次 flags 只设置了 CLONE_NEWUTS, 然后在子进程中调用 sethostname 设置容器的主机名。

root@iZhp36ik63t96xhzjh00ujZ:~# gcc uts.c && ./a.out
Parent - start a container!
Container - inside the container!
root@my-contain:~# pwd
/root
root@my-contain:~# exit
exit
Parent - container stopped!
root@iZhp36ik63t96xhzjh00ujZ:~#
root@iZhp36ik63t96xhzjh00ujZ:~#
root@iZhp36ik63t96xhzjh00ujZ:~# pwd
/root

编绎并运行,可以看到容器内主机名己变,退出后宿主机主机名并没有改变。

创建 uts namespace

struct ns_common {
    atomic_long_t stashed;
    const struct proc_ns_operations *ops;
    unsigned int inum;
};

struct new_utsname {
    char sysname[__NEW_UTS_LEN + 1];
    char nodename[__NEW_UTS_LEN + 1];
    char release[__NEW_UTS_LEN + 1];
    char version[__NEW_UTS_LEN + 1];
    char machine[__NEW_UTS_LEN + 1];
    char domainname[__NEW_UTS_LEN + 1];
};

struct uts_namespace {
    struct kref kref; // 引用计数
    struct new_utsname name;
    struct user_namespace *user_ns;
    struct ucounts *ucounts;
    struct ns_common ns;
} __randomize_layout;

核心结构体是 uts_namespace,创建的过程和前文讲的 clone 调用相关,来自 copy_utsname. 另外可以看到 new_utsname 构构体内容是 uts ns 隔离的所有内容,不仅有主机名。

struct uts_namespace *copy_utsname(unsigned long flags,
    struct user_namespace *user_ns, struct uts_namespace *old_ns)
{
    struct uts_namespace *new_ns;

    BUG_ON(!old_ns);
    get_uts_ns(old_ns);

    if (!(flags & CLONE_NEWUTS))
        return old_ns;

    new_ns = clone_uts_ns(user_ns, old_ns);

    put_uts_ns(old_ns);
    return new_ns;
}

如果没有参数 CLONE_NEWUTS, 那就使用旧的 uts, 否则调用 clone_uts_ns 复制一份。

static struct uts_namespace *clone_uts_ns(struct user_namespace *user_ns,
                      struct uts_namespace *old_ns)
{
    struct uts_namespace *ns;
    struct ucounts *ucounts;
    int err;

    err = -ENOSPC;
    ucounts = inc_uts_namespaces(user_ns);
    if (!ucounts)
        goto fail;

    err = -ENOMEM;
    ns = create_uts_ns();
    if (!ns)
        goto fail_dec;

    err = ns_alloc_inum(&ns->ns);
    if (err)
        goto fail_free;

    ns->ucounts = ucounts;
    ns->ns.ops = &utsns_operations;

    down_read(&uts_sem);
    memcpy(&ns->name, &old_ns->name, sizeof(ns->name));
    ns->user_ns = get_user_ns(user_ns);
    up_read(&uts_sem);
    return ns;
    ......
}

这块代码更简单,主要是设置 uts ns 的回调函数结构体 utsns_operations,然后再将 old_ns.new_utsname 复制给新的 uts_ns,此段函数就完成了。

设置主机名

当容器启动后,uts namespace 己经隔离了,此时再设置主机名会与宿主机互不影响。先看下 hostname 系统调用

~# strace hostname
......
uname({sysname="Linux", nodename="my-contain", ...}) = 0
fstat(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 0), ...}) = 0
write(1, "my-contain\n", 11my-contain
)            = 11
exit_group(0)                           = ?
+++ exited with 0 +++

省去无关信息,原来 hostname 是调用 uname 来获取的主机名等所有信息

SYSCALL_DEFINE1(uname, struct old_utsname __user *, name)
{
    struct old_utsname tmp;

    if (!name)
        return -EFAULT;

    down_read(&uts_sem);
    memcpy(&tmp, utsname(), sizeof(tmp));
    up_read(&uts_sem);
    if (copy_to_user(name, &tmp, sizeof(tmp)))
        return -EFAULT;

    if (override_release(name->release, sizeof(name->release)))
        return -EFAULT;
    if (override_architecture(name))
        return -EFAULT;
    return 0;
}

static inline struct new_utsname *utsname(void)
{
    return &current->nsproxy->uts_ns->name;
}

代码也非常简单,通过 utsname 获取当前进程的 uts namespace, 然后把 utsname 结构体复制一份到用户空间。后面两个 override 忽略,为了兼容老的版本。其中 old_utsnamenew_utsname 虽然是两个结构体,但是字段长度大小是一样的,只是为了去掉 hardcode 重新定义一个。

小结

现在看 uts ns 是最简单的一个,比较容易理解。

相关文章

网友评论

      本文标题:namespaces 学习笔记2:uts ns 源码实现

      本文链接:https://www.haomeiwen.com/subject/tqewpctx.html