当系统中某个进程使用新的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号的结构是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]的空间。