說明
爲了管理進程相關的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由一個比特標識。