《深入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值小的命名空间来说是可见的;

扫描二维码关注公众号,回复: 12027738 查看本文章

另外还有最重要的两个结构体(位于):

/*
 * 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由一个比特标识。

猜你喜欢

转载自blog.csdn.net/jiangwei0512/article/details/105335475