[toc]
进程的表示
task_struct和ID基础概念
- 在linux中,线程和进程在内核中都是用task_struct结构体来表示的,只是当一组task_struct运行在一个进程中时,内核让它们共享某些资源(比如用于管理内存的struct mm)。每个task_sturct都有自己的PID(process id),当然task_struct作为线程时,PID也就是其TID(thread id)
- 运行在同一个进程中的task_struct,有统一个线程组ID(TGID),如果进程没有使用线程,则其PID和TGID相同。线程组中的主线程被称作组长(group leader)。所有线程的task_struct的group_leader成员会指向组长task_struct
- 独立进程可以合并成进程组(使用setpgrp系统调用),在终端中用管道执行一组命令时就是这种情况。进程组成员的task_struct的pgrp属性值都是相同的,即进程组组长的PID。进程组简化了向组的所有成员发送信号的操作
1 2 3 4 5 6 7 8 9
|
<sched.h> struct task_struct { ... pid_t pid; pid_t tgid; ... }
|
namespace
linux支持一个叫namespace的特性,其核心是期望将某些资源和进程做隔离,例如隔离文件系统、隔离IPC信息或者隔离网络。隔离的效果就是,某些资源对某一组进程可见,对另一组进程不可见;不同namespace中的进程所执行的动作,也是互相不可见的。在内核中,隔离是通过各种namespace实现的。namespace可以嵌套,也就是namespace中可以创建自己的子namespace。
很显然,为了对资源进行隔离,PID也是需要做隔离的(否则,不同namespace中的进程可以看到其他进程)。内核采用如下逻辑做PID隔离,子namespace中的PID信息各自独立,互相不可见,但是父namespace对子namespace中的PID信息完全可见;子namespace中的某个进程,除了在子namespace中有自己的PID外,还要在其所有的上层namespace中都映射一个PID。
在namespace架构下PID的表示方法
涉及的数据结构
在namespace结构下,为了描述PID信息,内核总共涉及了以下数据结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
|
struct pid // pid { atomic_t count; unsigned int level; struct hlist_head tasks[PIDTYPE_MAX]; struct rcu_head rcu; struct upid numbers[1]; };
struct upid { int nr; struct pid_namespace *ns; struct hlist_node pid_chain; };
struct task_struct { ... 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
内核在对进程的管理过程中,需要解决以下几个问题:
- 通过一个ID值来找到一个进程的task_struct
- 快速的为新创建进程分配一个可用的ID值,ID值是可以重复使用的;
- 前两个操作在所有可以看到目标进程的namespace都可以执行,而且进程ID管理必须满足namespace的嵌套设计;
为了应对复杂的管理需求,内核用struct pid表示一个进程PID信息。内核代码对struct pid有如下注释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| * What is struct pid? * * A struct pid is the kernel's internal notion of a process identifier. * It refers to individual tasks, process groups, and sessions. While * there are processes attached to it the struct pid lives in a hash * table, so it and then the processes that it refers to can be found * quickly from the numeric pid value. The attached processes may be * quickly accessed by following pointers from struct pid. * * struct pid是内核对pid的内部表示,其指向一个独立的task、进程组或者会话。 * 当有进程和某个struct pid关联上的时候,struct pid 可以挂在一个hash表上, * 这样通过值就是可快速找到struct pid进而找到对应的进程。 * * Storing pid_t values in the kernel and referring to them later has a * problem. The process originally with that pid may have exited and the * pid allocator wrapped, and another process could have come along * and been assigned that pid. * * 如果仅在内核中保存一个ID值来引用task_struct,这种方式有一些问题。拥有某个ID值的进程 * 可能exit,然后pid重新将该ID值分配给新创建的进程,内核可能会混淆通过该ID值查询到的task_struct * 是属于就进程还是新创建的进程 * * Referring to user space processes by holding a reference to struct * task_struct has a problem. When the user space process exits * the now useless task_struct is still kept. A task_struct plus a * stack consumes around 10K of low kernel memory. More precisely * this is THREAD_SIZE + sizeof(struct task_struct). By comparison * a struct pid is about 64 bytes. * 上面的内容大概是说,通过保存task_struct指针来引用用户进程,会造成资源浪费问题。因为内核在某些情况下有 * 查询已经exit进程的ID的必要,如果为了查询其ID就保留其task_struct的话,就显得太浪费了。 * * Holding a reference to struct pid solves both of these problems. * It is small so holding a reference does not consume a lot of * resources, and since a new struct pid is allocated when the numeric pid * value is reused (when pids wrap around) we don't mistakenly refer to new * processes. * 通过保存一个struct pid指针的形式可以解决上述问题。当一个ID值被重用的时候,可以新创建一个struct pid。 * 首先它足够小,不会消耗过多的资源(已经退出的进程可以保留其struct pid)。其次,通过ID值查询task_struct, * 内核不会混淆具体引用的是旧进程还是新进程了。 */
|
各个数据结构的联系
下图描述的是前述各个数据结构之间的逻辑联系。首先task_sturctk可以和struct_pid因为某种做绑定。例如,可能这个struct pid就是属于task_struct的PID表述,则可用PID_TYPE_PID做绑定。又或者这个struct pid的拥有者是task_struct的group leader,则可用PID_TYPE_PGID绑定。当task_struct和struct pid绑定的时候,struct pid会通过task_struct的struct pid_link pids成员将task_struct连接到struct pid的struct hlist_head tasks链表上。当然,整个过程都是区分绑定类型的。
struct pid中包含一个struct upid numbers[1]数组,虽然其规格为1,但是由于位于结构体末尾,其长度可以根据实际内存区域做变长数组用。struct pid的 unsigned int level成员记录了具体的struct upid numbers成员有效个数。struct upid numbers中的成员用于命名空间,从上到下,每一级一个。每个struct upid numbers成员既会被hash到一个全局hash表中,hash过程会用到struct upid中的nr值和namespace指针,这样可以实现与之相反的查找过程。struct upid中的nr值也就是某个task_struct在具体命名空间中的ID值。
管理代码说明
- task_struct可以通过某个方式和task_pid绑定
1 2 3 4 5 6 7 8
| int fastcall attach_pid(struct task_struct *task, enum pid_type type, struct pid *pid) { struct pid_link *link; link = &task->pids[type]; link->pid = pid; hlist_add_head_rcu(&link->node, &pid->tasks[type]); return 0; }
|
- 内核提供了一组辅助函数,可以找到目标进程对应的struct pid,有了struct pid就可以通过container_of找到目标task_struct 了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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; }
|
1 2 3 4 5 6 7 8 9 10 11 12
| 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; }
|
- 通过ID值和类型,从某个namespace中找到目标进程的task_struct
1 2 3 4
| 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); }
|
- 下面为linux为新进程创建struct pid的裁剪代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| 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; ... tmp = ns; for (i = ns->level; i >= 0; i--) { nr = alloc_pidmap(tmp); ... pid->numbers[i].nr = nr; pid->numbers[i].ns = tmp; tmp = tmp->parent; } pid->level = ns->level; ...
for (i = ns->level; i >= 0; i--) { upid = &pid->numbers[i]; hlist_add_head_rcu(&upid->pid_chain, &pid_hash[pid_hashfn(upid->nr, upid->ns)]); } ... return pid; }
|