说明
为了管理进程相关的ID,内核提供了如下的支持:
1. PID分配器,用于加速ID的分配;
2. 用于实现通过ID及其类型查找进程的task_struct的函数;
3. 用于将ID的内核表示形式和用户空间可见的数值进行转换的函数;
4. 上述功能使用所需的数据结构。
数据结构
首先需要讨论的是PID命名空间,在前面已经介绍过,PID命名空间包含有关进程ID的信息。具体的结构体如下(位于include\linux\pid_namespace.h):
struct pid_namespace {
struct kref kref;
struct pidmap pidmap[PIDMAP_ENTRIES];
int last_pid;
struct task_struct *child_reaper;
struct kmem_cache *pid_cachep;
int level;
struct pid_namespace *parent;
#ifdef CONFIG_PROC_FS
struct vfsmount *proc_mnt;
#endif
};
部分成员说明如下:
child_reaper:每个PID命名空间都具有一个进程,其作用相当于init进程,这个成员就指向该进程;
parent:指向父命名空间的指针;
level:表示当前命名空间在命名空间层次结构中的深度,初始命名空间的level是0;level值大的命名空间中的ID对level值小的命名空间来说是可见的;
另外还有最重要的两个结构体(位于):
/*
* struct upid is used to get the id of the struct pid, as it is
* seen in particular namespace. Later the struct pid is found with
* find_pid_ns() using the int nr and struct pid_namespace *ns.
*/
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;
};
struct pid
{
atomic_t count;
/* lists of tasks that use this pid */
struct hlist_head tasks[PIDTYPE_MAX];
struct rcu_head rcu;
int level;
struct upid numbers[1];
};
upid表示特定命名空间中可见的信息,而pid表示内核对PID的内部表示。
需要注意结构体pid表示的是进程相关的ID,而不仅限于进程ID(PID),两者是有区别的,前者包含后者。
首先介绍upid:
nr:表示ID的数值;
ns:指向该ID所属命名空间的指针;
pid_chain:是用内核的标准方法实现了的散列溢出链表;
介绍介绍pid:
count:引用计数,不多做介绍;
tasks:每个数组项是一个散列表头,对应到一种ID类型,具体的ID类型如下:
enum pid_type
{
PIDTYPE_PID,
PIDTYPE_PGID,
PIDTYPE_SID,
PIDTYPE_MAX
};
散列表的内容就是一个个的进程,它与进程中的成员pids挂钩:
/* PID/PID hash table linkage. */
struct pid_link pids[PIDTYPE_MAX];
这里的pid_link的结构体如下:
struct pid_link
{
struct hlist_node node;
struct pid *pid;
};
其中pid指向这里的pid结构实例,而node用作散列表元素。
关于散列表在这里暂时不介绍,只需要知道通过散列表可以快速的定位我们需要的pid等结构体。
为什么一个进程ID需要用tasks来表示?
这里其实有两层含义,因为tasks的表示本身就是二维的。
1. 首先因为每个进程包含不同类型的ID,这里有PID、PGID和SID(没有线程组ID,因为它无非就是线程组组长的PID)。
2. 其次是同一个ID可能被用于多个进程(这里指的应该是比如进程组ID(PGID),它就会被组内的所有进程使用),所有共享同一个给定ID的task_struct实例,都需要通过列表连接起来。
level:表示可以看到该进程的命名空间的数目,即包含该进程的命名空间在命名空间层次结构中的深度;
numbers:它是一个不定长的数组,存放所有的upid实例,如果一个进程只包含在全局命名空间中,那么真的就只需要一个元素。
rcu:这也是散列相关的成员,暂不介绍。
函数
获取task_struct关联的pid:
static inline struct pid *task_pid(struct task_struct *task)
{
return task->pids[PIDTYPE_PID].pid;
}
static inline struct pid *task_tgid(struct task_struct *task)
{
return task->group_leader->pids[PIDTYPE_PID].pid;
}
static inline struct pid *task_pgrp(struct task_struct *task)
{
return task->group_leader->pids[PIDTYPE_PGID].pid;
}
static inline struct pid *task_session(struct task_struct *task)
{
return task->group_leader->pids[PIDTYPE_SID].pid;
}
通过pid获取命名空间局部的ID:
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;
}
查看pid所属命名空间所在的局部PID:
static inline pid_t pid_vnr(struct pid *pid)
{
pid_t nr = 0;
if (pid)
nr = pid->numbers[pid->level].nr;
return nr;
}
查看pid对应初始命名空间(即init进程所在命名空间)的全局PID:
static inline pid_t pid_nr(struct pid *pid)
{
pid_t nr = 0;
if (pid)
nr = pid->numbers[0].nr;
return nr;
}
这里仅仅是将level为0而已。
通过局部数组PID和关联的命名空间,确定pid实例(即PID的内核表示):
struct pid * fastcall 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;
}
这里实际上看不到细节,但是因为upid的实例保存pid数据结构的numbers成员中,所以能够遍历得到。
获取pid->tasks[type]散列表中的第一个task_struct实例:
struct task_struct * fastcall pid_task(struct pid *pid, enum pid_type type)
{
struct task_struct *result = NULL;
if (pid) {
struct hlist_node *first;
first = rcu_dereference(pid->tasks[type].first);
if (first)
result = hlist_entry(first, struct task_struct, pids[(type)].node);
}
return result;
}
/*
* Must be called under rcu_read_lock() or with tasklist_lock read-held.
*/
struct task_struct *find_task_by_pid_type_ns(int type, int nr,
struct pid_namespace *ns)
{
return pid_task(find_pid_ns(nr, ns), type);
}
那如何找到后面的呢?以及它们有什么用呢?目前书上没有介绍。
通过全局数字PID查找进程:
struct task_struct *find_task_by_pid(pid_t nr)
{
return find_task_by_pid_type_ns(PIDTYPE_PID, nr, &init_pid_ns);
}
它其实是对前面函数的简单包装。其中的init_pid_ns是初始命名空间:
/*
* PID-map pages start out as NULL, they get allocated upon
* first use and are never deallocated. This way a low pid_max
* value does not cause lots of bitmaps to be allocated, but
* the scheme scales to up to 4 million PIDs, runtime.
*/
struct pid_namespace init_pid_ns = {
.kref = {
.refcount = ATOMIC_INIT(2),
},
.pidmap = {
[ 0 ... PIDMAP_ENTRIES-1] = { ATOMIC_INIT(BITS_PER_PAGE), NULL }
},
.last_pid = 0,
.level = 0,
.child_reaper = &init_task,
};
EXPORT_SYMBOL_GPL(init_pid_ns);
PID分配器
用于生成唯一的PID。
struct pid *alloc_pid(struct pid_namespace *ns)
由于进程可能在多个命名空间中可见,所以对于每个命名空间都需要生成一个局部PID,所以这里会接受一个pid_namespace的入参。
为了跟踪已经分配和仍然可用的PID,内核使用了一个大的位图,其中每个PID由一个比特标识。