1. 进程标识
在Linux中,在同一命名空间中,进程总是会被分配唯一的号码来标识它们,该号码被称作进程ID号,简称PID。对于进程和线程来说,他们都是用同样的结构体task_struct来表示,可以都被看做是进程,在内核中,他们也有自己的进程ID,也就是说会有唯一的进程ID来标识它们;但是对于线程来说,它还会有自己的线程ID,在task_struct 结构体中,用字段tgid来表示,pid代表线程所在线程组组长的PID,对于进程来说,pid和tgid是一样的。
struct task_struct {
...
pid_t pid;----------进程ID
pid_t tgid;---------线程ID
...
}
结合线程组和进程组的知识,现在举例来介绍进程相关PID的关系,假设进程a是进程组组长,进程a用fork产生了进程b、c,然后进程c用clone产生了线程c0、c1、c2,则c、c0、c1和c2组成一个线程组,组长是c,那么他们相关的PID关系如下:
comm | PID | PPID | PGID | TGID |
---|---|---|---|---|
a | 3 | 1 | 1 | |
b | 10 | 3 | 1 | |
c | 11 | 3 | 1 | 11 |
c0 | 20 | 3 | 11 | |
c1 | 21 | 3 | 11 | |
c2 | 22 | 3 | 11 |
2. PID结构体
在linux中,有进程组、会话组这些关系,那么就得有个结构体来把相关进程都串联起来,这个结构体就是struct pid。
struct pid
{
atomic_t count;
unsigned int level;-----------------------命名空间层
/* lists of tasks that use this pid */
struct hlist_head tasks[PIDTYPE_MAX];-----链表
struct rcu_head rcu;
struct upid numbers[1];-------------------对应命名空间的upid
};
上述结构体有个关键的字段是tasks数组,这个数组有PIDTYPE_MAX项,分别是PID(是PIDTYPE_PID的简写,以下相同)、PGID、SID,tasks[PID]链表链接的是创建此pid结构体的进程,tasks[PGID]链接的是这个进程组中的所有进程,tasks[SID]链接的是这个会话组中的所有进程。相关关系如下图:
在上图中有前提条件是:A是一个进程组组长。如果A不是进程组组长,那么进程A的pids[PID]->pid就不会指向pid:A,而是会指向他所在的进程组组长创建的pid:X(X代表进程组组长)。
从图中可以看出进程A用fork创建了两个进程B和C,然后B和C都加入了结构体pid:A的链表tasks[PGID]中,在task_struct结构体中有一个专门的结构体字段pids来管理这些关系
struct task_struct {
...
struct pid_link pids[PIDTYPE_MAX];------有三个数组
...
}
struct pid_link
{
struct hlist_node node;----------链表节点,加入进程组和会话组的结点
struct pid *pid;-----------------pid结构体
};
3. 命名空间
命名空间提供了虚拟化的一种轻量级形式,使得我们可以从不同的方面来查看运行系统的全局属性。
创建进程的时候,根据相应的命名空间,就会创建对应空间的upid,而且会分配一个唯一的数字id,在字段upid->nr中表示。假如进程所在的命名空间有3层,那么就会在每一个空间创建一个upid,并且会把这三个upid都加入全局散列表pid_hash。虽然有3个upid,但是都是对应这同一个struct pid。在最顶端的层level0,是全局层,每一个子空间层最终都会映射到此层,子空间会层层映射父空间。
struct upid {
/* Try to keep pid_chain in the same cacheline as nr for find_vpid */
int nr;----------------------------在对应空间的id号
struct pid_namespace *ns;----------所在的空间
struct hlist_node pid_chain;-------加入到全局pid_hash
};
命名空间与upid的关系如下图: