用go写一个docker(5)-linux的namespace(下)

上篇我们用go语言体验了namespace,今天了解下namespace的实现。

namespace的目的是资源隔离,即资源都在,但不能让你看到。比如进程a只属于namespace A,则不能让a看到namespace B的资源,除非把a拉到namespace B中。
namespace隔离的资源有:

系统的hostname
网络资源(网卡信息,路由信息等)
进程信息(有哪些进程,父进程子进程间的关系等)
用户信息(有哪些用户,组,用户的权限是什么)
文件系统信息(有什么可用的文件系统等)
等等...

那Linux是如何实现隔离的呢?我理解如下:

namespace记录上述资源
每个namespace记录的资源都不一样,都有自己的ns号
把进程绑定到对应ns号中,实现进程的资源隔离(/proc/PID/ns,PID指对应的进程号)
通过clone(),unshare(),setns()系统调用来操作namespace

这三个系统调用的功能如下:

clone() : 创建一个新的进程,通过传namespace的flags参数(NEW*标识符)来指定对应的namespace类型。
unshare() : 把某进程加入到新的namespace。
setns() : 把某进程加入到某个namespace。

我们来看看这三个系统调用。

clone()

clone和fork,vfork都是可以用来创建进程的,但clone可以传对应的flags来指定子进程的资源,它的参数如下:

int clone(int (*child_func)(void *), void *child_stack, int flags, void *arg);

child_func : 传入子进程运行的程序主函数。
child_stack : 传入子进程使用的栈空间。
flags : 表示使用哪些 CLONE_* 标志位。
args : 用于传入用户参数。
使用clone创建进程时,一旦指定了flags标志位,相对应类型的 namespace 就会被创建,新创建的进程也会成为该 namespace 中的一员。
clone() 的原型并不是最底层的系统调用,而是封装过的,真正的系统调用内核实现函数为 do_fork(),形式如下:

long do_fork(unsigned long clone_flags,
          unsigned long stack_start,
          unsigned long stack_size,
          int __user *parent_tidptr,
          int __user *child_tidptr)

其中的clone_flags就是我们说的CLONE_NEW*系列的标志位

ushare()

ushare()用于把某进程加入到新的namespace,参数如下:

int unshare(int flags);

unshare() 与 clone() 类似,但它运行在现有进程上,不需要创建一个新进程。
通过指定的 flags 参数 CLONE_NEW* 创建一个新的 namespace,
将调用者加入该 namespace。
最后实现的效果其实就是将调用者从当前的 namespace 分离,然后加入一个新的 namespace。
linux本身也提供了一个ushare命令,可以直接使用。

setns()

setns()用于加入某个已存在的namespace,参数如下:

int setns(int fd, int nstype);

fd 表示要加入的 namespace 的文件描述符,可以通过打开其中一个符号链接来获取,也可以通过打开 bind mount 到其中一个链接的文件来获取。
stype 让调用者可以去检查 fd 指向的 namespace 类型,值可以设置为 CLONE_NEW*和0。填 0 表示不检查,如果调用者已经明确知道自己要加入了 namespace 类型,或者不关心 namespace 类型,就可以使用该参数来自动校验。

UTS namespace 实现方式

以UTS为例,我们来看看namespace的实现。
我们知道,在linux中,只要符合task_struct的结构体,都能被系统调度。在该结构体中有个nsproxy字段,该字段保存着namespace的相关信息。
(/usr/src/linux-headers-4.15.0-88/include/linux/sched.h)

 818         /* Namespaces: */
 819         struct nsproxy                  *nsproxy;

该结构体定义如下
(/usr/src/linux-headers-4.15.0-88/include/linux/nsproxy.h):

 31 struct nsproxy {
 32         atomic_t count;
 33         struct uts_namespace *uts_ns;
 34         struct ipc_namespace *ipc_ns;
 35         struct mnt_namespace *mnt_ns;
 36         struct pid_namespace *pid_ns_for_children;
 37         struct net           *net_ns;
 38         struct cgroup_namespace *cgroup_ns;
 39 };

以hostname命令为例,hostname本质调用的是gethostname,它的实现如下(kernel/sys.c):

SYSCALL_DEFINE2(gethostname, char __user *, name, int, len)
{
  int i, errno;
  struct new_utsname *u;
​
  if (len < 0)
    return -EINVAL;
  down_read(&uts_sem);
  u = utsname();
  i = 1 + strlen(u->nodename);
  if (i > len)
    i = len;
  errno = 0;
  if (copy_to_user(name, u->nodename, i))
    errno = -EFAULT;
  up_read(&uts_sem);
  return errno;
}

utsname的关键实现如下:
(/usr/src/linux-headers-4.15.0-88/include/linux/utsname.h)

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

可以看到最终还是落到了结构体nsproxy中,通过这样就实现了可以给不同的namespace设置不同的hostname。

谢谢阅读。

参考:

https://zhuanlan.zhihu.com/p/120136568
https://www.cnblogs.com/sparkdev/p/9377072.html
https://blog.csdn.net/21cnbao/article/details/60326419

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值