linux内核轻量级虚拟化之Namespace

来源:互联网 发布:zsysecdesk是什么软件 编辑:程序博客网 时间:2024/06/06 01:05

一.什么是namespace

Namespace,命名空间,顾名思义,就是不同的名字空间,打个简单的比方,进程在a空间是叫a进程,在b空间也就可能叫b进程。为什么要有命名空间呢?主要是满足虚拟化的一些需求。试想,如果有一台机器,不管它是个人pc还是服务器,或是网络交换机,路由器,一般情况下,它也就被一个用户使用。但是如果某一天,另外一个用户也要使用同样的机器,一种方法是再买一个机器,装上同样的linux系统,但是还有一种更好的方法,就是使用容器(container),每个用户有属于自己的容器,而且容器之间相互隔离,而namespace就是实现容器的一种手段。

二.Namespace的数据结构

首先,namespace是针对进程而言的,所以在task_struct中有一个指向结构nsproxy的指针,这个就是进程的名字空间,下面看一下nsproxy的结构:/*

 * A structure to contain pointers to all per-process

 * namespaces - fs (mount), uts, network, sysvipc, etc.

 *

 * 'count' is the number of tasks holding a reference.

 * The count for each namespace, then, will be the number

 * of nsproxies pointing to it, not the number of tasks.

 *

 * The nsproxy is shared by tasks which share all namespaces.

 * As soon as a single namespace is cloned or unshared, the

 * nsproxy is copied.

 */

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;

};

可见一个进程有多维的名字空间,比如UTS,PID,NET,USER等等,其中比较重要和复杂的就是pid namespace了。由于在不同的名字空间里进程有不同的pid,如此一来,进程的pid就分成了两种:一种是global pid,另一种是local pid,所谓的global pid就是内核自己用的pid,也是initial namespace中的pid。Local pid就是派生出来的子命名空间的pid,由此也看出,pid namespace是一种树状的结构:

 

父空间可以看到所有子空间的进程,而两个子空间互相不可见。这一点也可以从pid_namespace中看出:

struct pid_namespace {

struct kref kref;//此pid namespace的引用计数

struct pidmap pidmap[PIDMAP_ENTRIES];//pid的bit表, bit为0表示空闲的pid号

int last_pid;//最后一次分配的pid,用于alloc_pidmap时快速找到下一个空闲的pid

struct task_struct *child_reaper;//用于回收僵尸进程

struct kmem_cache *pid_cachep;//软件cache,用于分配pid结构

int level;//从init pid namespace开始数,第几级

struct pid_namespace *parent;//父pid namespace

#ifdef CONFIG_PROC_FS

struct vfsmount *proc_mnt;

#endif

};

另外还有两个比较重要的数据结构:第一,内核所见的pid

struct pid

{

atomic_t count;

/* lists of tasks that use this pid */

struct hlist_head tasks[PIDTYPE_MAX];//此pid对应的task

struct rcu_head rcu;

int level;//此pid对应的级数,也是下面一个域的真正的数组下标

struct upid numbers[1];从1到level各个namespace中的pid

};

第二,属于具体某个空间的pid

struct upid {

/* Try to keep pid_chain in the same cacheline as nr for find_pid */

int nr;

struct pid_namespace *ns;

struct hlist_node pid_chain;

};

具体数据结构组织如右图:

  

三.Pid namespace的功能

主要需要实现两个功能:

1.指定一个namespace和一个pid 值,找到相应的task_struct结构

2.指定一个task_struct结构和一个namespace,找到对应的pid 值。

第一个功能实现:

首先找到相应的pid结构:

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->tasks找到对应的task结构

第二个的功能实现:先找到pid结构

static inline struct pid *task_pid(struct task_struct *task)

{

         return task->pids[PIDTYPE_PID].pid;

}

然后找到对应的upid结构,从而发现对应namespace的pid值

pid_t pid_nr_ns(struct pid *pid, struct pid_namespace *ns)

{

         struct upid *upid;

         pid_t nr = 0;

         if (pid && ns->level <= pid->level) {

                   upid = &pid->numbers[ns->level];

                   if (upid->ns == ns)

                            nr = upid->nr;

         }

         return nr;

}

四.总结

Namespace是linux内核轻量级虚拟化的重要的部分,可以为后续虚拟化提供基石,很多开源的虚拟化项目例如openvz也利用了linux内核的pid namespace的技术。所以,将其研究透彻很有必要,这样也为以后的研究打下基础。

原创粉丝点击