《深入Linux內核架構》讀書筆記005——管理進程相關ID

說明

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章