使用pid namespace后host pid与容器pid的关系

当系统中某个进程使用新的pid namespace后,就会有两个pid号,一个是这个进程在host中的pid号,一个是这个进程在其容器空间的pid号,那么这两个pid号之间是什么关系呢?如何内核中通过其中一个pid号查找到另外一个号呢?如何通过任意一个pid号找到进程的task_struct?

 

先看一些相关的内核结构,内核描述一些功能的结构都是相互联系的,搞清楚这些结构的联系,就可以了解它的框架。

struct task_struct {

......

/* PID/PID hash table linkage. */

struct pid_link pids[PIDTYPE_MAX];

......

}

enum pid_type

{

PIDTYPE_PID,

PIDTYPE_PGID,

PIDTYPE_SID,

PIDTYPE_MAX

};

struct pid_link

{

struct hlist_node node;

struct pid *pid;

};

struct pid

{

atomic_t count;

unsigned int level;

/* lists of tasks that use this pid */

struct hlist_head tasks[PIDTYPE_MAX];

struct rcu_head rcu;

struct upid numbers[1];

};

struct upid {

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

int nr;

struct pid_namespace *ns;

struct hlist_node pid_chain;

};

下图是上面结构体的关联图,可能比上面struct描述直观一点。

 

使用pid namespace后host pid与容器pid的关系_第1张图片

如上图所示,表示一个进程的pid号的结构是struct pid,具体pid号在struct upid number[]数组中,数组的大小是不确定的,默认数组大小是1,就是我们通常不使用pid namespace看到的进程号或者线程号,当使用pid namespace后,struct upid number[]数组大小就不为1,数组大小为struct pid中的level+1,默认level为0,level为1表示当前进程为1级pid namespace,level为0表示当前进程除了默认的host pid namespace外,没有新的namecpace。当我们基于host创建一个容器使用pid namespace时,level为1,即当前进程的struct pid中的struct upid number[]数组有两个元素,struct upid number[0]描述host pid, struct upid number[1]描述container pid。

struct upid中有三个元素,pid_chain 为一个hlist_node,会插入pid_hash表,nr 和ns用于合成hash表的键值。也就是当我们知道进程的pid号和pid号所属的struct pid_namespace *ns后,就可以查找到进程的struct upid,然后找到struct pid,然后找到task_struct,这样关于进程的信息我们就都能获取的到。

 

结合上面的原理,看一些通过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;

}

上面函数入参是struct pid 和struct pid_namespace,返回值是pid值,通过里面的upid = &pid->numbers[ns->level]可知,函数返回的是最新的pid 命名空间的pid值。如果要获取最初的host pid namespace的pid,应该是upid = &pid->numbers[0];

 

 

 

struct task_struct *find_task_by_pid_ns(pid_t nr, struct pid_namespace *ns)

{

rcu_lockdep_assert(rcu_read_lock_held(),

   "find_task_by_pid_ns() needs rcu_read_lock()"

   " protection");

return pid_task(find_pid_ns(nr, ns), PIDTYPE_PID);

}

上面函数通过pid号和ns,查找到进程对应的task_struct

struct pid *find_pid_ns(int nr, struct pid_namespace *ns)

{

struct upid *pnr;

 

hlist_for_each_entry_rcu(pnr,

&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;

}

上面函数中通过nr和ns通过哈希表找到struct upid,再通过upid找到struct pid

 struct task_struct *pid_task(struct pid *pid, enum pid_type type)

{

struct task_struct *result = NULL;

if (pid) {

struct hlist_node *first;

first = rcu_dereference_check(hlist_first_rcu(&pid->tasks[type]),

      lockdep_tasklist_lock_is_held());

if (first)

result = hlist_entry(first, struct task_struct, pids[(type)].node);

}

return result;

}

上面函数通过struct pid和pid_type找到task_struct,由上面用法可知,type一般为PIDTYPE_PID。

 

通过上面的代码学习,发现struct pid中的struct upid numbers[1] 在定义的时候就一个,为什么使用的时候,在数alloc_pid函数中,struct upid numbers可以有多个?

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;

pid->level = ns->level;

for (i = ns->level; i >= 0; i--) {

nr = alloc_pidmap(tmp);

if (nr < 0)

goto out_free;

 

pid->numbers[i].nr = nr;

pid->numbers[i].ns = tmp;

tmp = tmp->parent;

}

    ......

 

}

那是因为在给struct pid 分配空间的时候, kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL),多分配了空间,所以alloc_pid中可以使用pid->numbers[i].nr = nr,i的值可以大于0。kmem_cache_alloc属于slab内存分配,分配一个一个固定大小内存的对象,那么对象大小在哪规定的呢?在create_pid_namespace函数create_pid_cachep(level + 1)调用时创建。create_pid_cachep函数中kmem_cache_create(pcache->name,sizeof(struct pid) + (nr_ids - 1) * sizeof(struct upid)可知,每次给一个进程分配的struct pid分配的空间加了由于pid namespace的使用增加的pid->numbers[i]的空间。

 

你可能感兴趣的:(linux)