当前位置:首页 > 话题广场 > 教育专区 > 小学

【#NAME?】容器原理之 - namespace

Namespace简介

NEMESS(命名空间)是Linux提供的内核级环境隔离方法,许多编程语言也有namespace等功能,如C、Java等。编程语言的namespace旨在允许在项目中的不同命名空间中使用相同的函数名或类名。

而Linux的 namespace 也是为了实现资源能够在不同的命名空间里有相同的名称,譬如在 A命名空间 有个pid为1的进程,而在 B命名空间 中也可以有一个pid为1的进程。

有了 namespace 就可以实现基本的容器功能,著名的 Docker 也是使用了 namespace 来实现资源隔离的。

Linux支持6种资源的 namespace,分别为(文档):

在调用 clone() 系统调用时,传入以上的不同类型的参数就可以实现复制不同类型的namespace。比如传入 CLONE_NEWPID参数时,就是复制 pid命名空间,在新的 pid命名空间 里可以使用与其他 pid命名空间 相同的pid。代码如下:

#define _GNU_SOURCE #include <; #include <; #include <uni; #include <sy; #include <sy; #include <; #include <; #include <errno.h> char child_stack[5000]; int child(void* arg) { printf("Child - %d\n", getpid()); return 1; } int main() { printf("Parent - fork child\n"); int pid = clone(child, child_stack+5000, CLONE_NEWPID, NULL); if (pid == -1) { perror("clone:"); exit(1); } waitpid(pid, NULL, 0); printf("Parent - child(%d) exit\n", pid); return 0; }

输出如下:

Parent - fork child Parent - child(9054) exit Child - 1

从运行结果可以看出,在子进程的 pid命名空间 里当前进程的pid为1,但在父进程的 pid命名空间 中子进程的pid却是9045。

namespace实现原理

为了让每个进程都可以从属于某一个namespace,Linux内核为进程描述符添加了一个 struct nsproxy 的结构,如下:

struct task_struct { ... /* namespaces */ struct nsproxy *nsproxy; ... } struct nsproxy { atomic_t count; struct uts_namespace *uts_ns; struct ipc_namespace *ipc_ns; struct mnt_namespace *mnt_ns; struct pid_namespace *pid_ns; struct user_namespace *user_ns; struct net *net_ns; };

从 struct nsproxy 结构的定义可以看出,Linux为每种不同类型的资源定义了不同的命名空间结构体进行管理。比如对于 pid命名空间 定义了 struct pid_namespace 结构来管理 。由于 namespace 涉及的资源种类比较多,所以本文主要以 pid命名空间 作为分析的对象。

我们先来看看管理 pid命名空间 的 struct pid_namespace 结构的定义:

struct pid_namespace { struct kref kref; struct pidmap pidmap[PIDMAP_ENTRIES]; int last_pid; struct task_struct *child_reaper; struct kmem_cache *pid_cachep; unsigned int level; struct pid_namespace *parent; #ifdef CONFIG_PROC_FS struct vfsmount *proc_mnt; #endif };

因为 struct pid_namespace 结构主要用于为当前 pid命名空间 分配空闲的pid,所以定义比较简单:

  • kref 成员是一个引用计数器,用于记录引用这个结构的进程数
  • pidmap 成员用于快速找到可用pid的位图
  • last_pid 成员是记录最后一个可用的pid
  • level 成员记录当前 pid命名空间 所在的层次
  • parent 成员记录当前 pid命名空间 的父命名空间

由于 pid命名空间 是分层的,也就是说新创建一个 pid命名空间 时会记录父级 pid命名空间 到 parent 字段中,所以随着 pid命名空间 的创建,在内核中会形成一颗 pid命名空间 的树,如下图(图片来源):

第0层的 pid命名空间 是 init 进程所在的命名空间。如果一个进程所在的 pid命名空间 为 N,那么其在 0 ~ N 层pid命名空间 都有一个唯一的pid号。也就是说 高层pid命名空间 的进程对 低层pid命名空间 的进程是可见的,但是 低层pid命名空间的进程对 高层pid命名空间 的进程是不可见的。

由于在 第N层pid命名空间 的进程其在 0 ~ N层pid命名空间 都有一个唯一的pid号,所以在进程描述符中通过 pids 成员来记录其在每个层的pid号,代码如下:

struct task_struct { ... struct pid_link pids[PIDTYPE_MAX]; ... } enum pid_type { PIDTYPE_PID, PIDTYPE_PGID, PIDTYPE_SID, PIDTYPE_MAX }; struct upid { int nr; struct pid_namespace *ns; struct hlist_node pid_chain; }; struct pid { atomic_t count; struct hlist_head tasks[PIDTYPE_MAX]; struct rcu_head rcu; unsigned int level; struct upid numbers[1]; }; struct pid_link { struct hlist_node node; struct pid *pid; };

这几个结构的关系如下图:

我们主要关注 struct pid 这个结构,struct pid 有个类型为 struct upid 的成员 numbers,其定义为只有一个元素的数组,但是其实是一个动态的数据,它的元素个数与 level 的值一致,也就是说当 level 的值为5时,那么 numbers 成员就是一个拥有5个元素的数组。而每个元素记录了其在每层 pid命名空间 的pid号,而 struct upid 结构的 nr 成员就是用于记录进程在不同层级 pid命名空间 的pid号。

我们通过代码来看看怎么为进程分配pid号的,在内核中是用过 alloc_pid() 函数分配pid号的,代码如下:

struct pid *alloc_pid(struct pid_namespace *ns) { struct pid *pid; enum pid_type type; int i, nr; struct pid_namespace *tmp; struct upid *upid; pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL); if (!pid) goto out; tmp = ns; for (i = ns->level; i >= 0; i--) { nr = alloc_pidmap(tmp); // 为当前进程所在的不同层级pid命名空间分配一个pid if (nr < 0) goto out_free; pid->numbers[i].nr = nr; // 对应i层namespace中的pid数字 pid->numbers[i].ns = tmp; // 对应i层namespace的实体 tmp = tmp->parent; } get_pid_ns(ns); pid->level = ns->level; atomic_set(&pid->count, 1); for (type = 0; type < PIDTYPE_MAX; ++type) INIT_HLIST_HEAD(&pid->tasks[type]); spin_lock_irq(&pidmap_lock); for (i = ns->level; i >= 0; i--) { upid = &pid->numbers[i]; // 把upid连接到全局pid中, 用于快速查找pid hlist_add_head_rcu(&upid->pid_chain, &pid_hash[pid_hashfn(upid->nr, upid->ns)]); } spin_unlock_irq(&pidmap_lock); out: return pid; ... }

上面的代码中,那个 for (i = ns->level; i >= 0; i--) 就是通过 parent 成员不断向上检索为不同层级的 pid命名空间分配一个唯一的pid号,并且保存到对应的 nr 字段中。另外,还会把进程所在各个层级的pid号添加到全局pid哈希表中,这样做是为了通过pid号快速找到进程。

现在我们来看看怎么通过pid号快速找到一个进程,在内核中 find_get_pid() 函数用来通过pid号查找对应的 struct pid结构,代码如下(find_get_pid() -> find_vpid() -> find_pid_ns()):

struct pid *find_get_pid(pid_t nr) { struct pid *pid; rcu_read_lock(); pid = get_pid(find_vpid(nr)); rcu_read_unlock(); return pid; } struct pid *find_vpid(int nr) { return find_pid_ns(nr, current->nsproxy->pid_ns); } struct pid *find_pid_ns(int nr, struct pid_namespace *ns) { struct hlist_node *elem; struct upid *pnr; hlist_for_each_entry_rcu(pnr, elem, &pid_hash[pid_hashfn(nr, ns)], pid_chain) if (pnr->nr == nr && pnr->ns == ns) return container_of(pnr, struct pid, numbers[ns->level]); return NULL; }

通过pid号查找 struct pid 结构时,首先会把进程pid号和当前进程的 pid命名空间 传入到 find_pid_ns() 函数,而在 find_pid_ns() 函数中通过全局pid哈希表来快速查找对应的 struct pid 结构。获取到 struct pid 结构后就可以很容易地获取到进程对应的进程描述符,例如可以通过 pid_task() 函数来获取 struct pid 结构对应进程描述符,由于代码比较简单,这里就不再分析了。

1.《【#NAME?】容器原理之 - namespace》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。

2.《【#NAME?】容器原理之 - namespace》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。

3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/jiaoyu/2101295.html

上一篇

中班10以内的减法教案看这里!把童年还给孩子!当“双减”拉下急刹车 义务教育如何做到“减”向均衡?

下一篇

关于中班2的减法教案我想说"1元10节课“和”2万元一年" 学前儿童该上什么课?

【#NAME?】Excel公式中常见的错误值

【#NAME?】Excel公式中常见的错误值

#NAME?相关介绍,使用Exce电子表格的人可能会遇到各种问题,经常会发现表单中有错误的值的信息。 比如#N/A!、#VALUE!、#DIV/0!等等。这都代表了什么信息呢?出现这些错误该如何解决呢? 下面就介绍几种ex...

#NAME?看这里!Python 的特殊变量 __name__

#NAME?看这里!Python 的特殊变量 __name__

#NAME?相关介绍,您可以在许多python代码中看到__name__变量。 对于 Python 的初次使用用户来说可能对这个变量不是非常熟悉。 这样理解就好了,__name__ 这个变量就是一个标识 Python 程序...

关于#NAME?我想说Excel出现“NAME”提示怎么办 如何解决Excel“NAME”提示

关于#NAME?我想说Excel出现“NAME”提示怎么办 如何解决Excel“NAME”提示

#NAME?相关介绍,使用Excel时出现“#NAME”错误消息提示。 这通常是因为使用了公式无法识别的文本。 解决这种情况,选择“插入”→“名称”→“定义”,打开“定义名称”。如果列举的没有所需要的名称,可以在“在当前工...

#NAME?,干货看这篇!「Python」__name__ 是什么?

#NAME?,干货看这篇!「Python」__name__ 是什么?

#NAME?相关介绍,前言 在我们浏览一下 python 文件或者自己写 python 代码的时候,时常会在代码的最后加上这样的一行代码 if __name__ == &#39;__main__&#39;: func_na...

#NAME??我来告诉你答案Excel中“NAME?”二三事

#NAME??我来告诉你答案Excel中“NAME?”二三事

#NAME?相关介绍,通常出现#NAME?符号的意思是出现了excel不能识别的东西,比如输入错误的公式、没有加引号的文本等等。 达到当天最大量API KEY 超过次数限制 用Excel筛选一列,这类里面出现了#NAME?...

#NAME??我来告诉你答案Excel出现“NAME”提示怎么办 如何解决Excel“NAME”提示

#NAME??我来告诉你答案Excel出现“NAME”提示怎么办 如何解决Excel“NAME”提示

#NAME?相关介绍,在使用Excel的过程中,出现了“#NAME”的错误信息提示。 这种情况一般是由于在公式使用了无法识别的文本。 达到当天最大量API KEY 超过次数限制解决这种情况,选择“插入”→“名称”→“定义”...

#NAME??终于找到答案了Excel中“NAME?”二三事

#NAME??终于找到答案了Excel中“NAME?”二三事

#NAME?相关介绍,通常会出现#NAME吗?符号表示excel无法识别的输入错误公式、没有引号的文本等。 用Excel筛选一列,这类里面出现了#NAME?,如下 进行筛选,筛选#NAME?, 筛选的结果: 直接筛选是筛选...

#NAME?看这里!Excel公式中常见的错误值

#NAME?看这里!Excel公式中常见的错误值

#NAME?相关介绍,使用Exce电子表格的人可能会遇到各种问题,经常会发现表单中有错误的值的信息。 比如#N/A!、#VALUE!、#DIV/0!等等。这都代表了什么信息呢?出现这些错误该如何解决呢? 下面就介绍几种ex...