Linux为每个进程分配一个ID作为其命名空间中的标识,ID有多种类型:
进程ID类型在include/linux/pid.h中定义如下:
enum pid_type
{
PIDTYPE_PID,
PIDTYPE_PGID,
PIDTYPE_SID,
PIDTYPE_MAX
};
huangchangwei@ubuntu:~/code_test/thread$ ps -T
PID SPID TTY TIME CMD
99855 99855 pts/15 00:00:00 bash
163829 163829 pts/15 00:00:00 a.out
163829 163830 pts/15 00:00:00 a.out
164038 164038 pts/15 00:00:00 ps
man ps之后,对于PID的解释如下:
pid PID a number representing the process ID (alias tgid).
PID的含义和tgid相同,即线程组ID,task_struct->tgid
spid SPID see lwp. (alias lwp, tid).
lwp LWP light weight process (thread) ID of the dispatchable entity (alias spid, tid). See tid for additional information.
SPID是轻量级线程的标识,即task_struct->pid
pid是Linux在命名空间中唯一标识进程而分配的ID,叫做进程ID号,即PID。在使用fork或者clone系统调用时产生的进程,内核会分配一个新的唯一的PID值。
pid用于内核唯一区分每个进程。
在一个进程中,如果以CLONE_THREAD标志调用clone建立的进程就是该进程的一个线程(轻量级进程),它们位于一个线程组。
线程组中的所有线程的ID叫做线程组ID。
位于相同线程组中的线程有相同的TGID,但是它们的PID各不相同。
独立的进程可以组成进程组(setpgrp系统调用),进程组可以简化向所有组内进程发送信号的操作。
例如用管道连接的进程处在同一进程组内,进程组ID叫做PGID,进程组内的所有进程有相同的PGID,等于组长的PID。
几个进程组可以合并成一个会话组(setsid系统调用)。
会话组中所有进程都有相同的SID,保存在task_struct->session中。
命名空间是比KVM之类更加轻量级的虚拟化技术,是对全局资源的一种抽象。将资源放在不同的容器之中,各容器之间彼此隔离。
由于各个命名空间彼此隔离,相同的PID可以出现在不同的命名空间之内,因此每个命名空间中都有自己的1号进程。
PID命名空间具有层次关系,父命名空间知道子命名空间的存在,因此子命名空间需要映射到父命名空间中去。
由于PID命名空间具有层次关系。在建立一个新的命名空间时,该命名空间中的所有PID对父命名空间都是可见的,但子命名空间无法看到父命名空间的PID。这意味着某些进程有多个PID,凡是能看到该进程的命名空间,都需要为其分配一个对应的PID。
因此,必须要区分局部ID和全局ID。
全局ID 在内核本身和初始化命名空间中惟一的一个ID,系统启动时启动的init进程属于该初始命名空间。系统中每个进程都对应了该命名空间中的一个PID,叫全局ID,保证在整个系统中唯一。
局部ID 对于某个特定的命名空间之内,在其内部分配的ID叫做局部ID,该ID仅在内部有效。
全局PID和TGID保存在结构体task_struct
中,分别是pid
和 tgid
。
pid_t pid; //process id(unique)
pid_t tgid; //thread group id
pid_namespace保存在task_struct中,task_struct->nsproxy->pid_namespace
,在include/linux/pid_namespace.h中进行定义。
struct pid_namespace {
struct kref kref; //指向pid_namespace的引用个数
struct pidmap pidmap[PIDMAP_ENTRIES]; //分配pid的位图
struct rcu_head rcu;
int last_pid;
unsigned int nr_hashed;
struct task_struct *child_reaper; /*
*托管进程,如果父进程先于子进程退出,托管进程会对孤儿进程调用wait4
*每个命名空间都具有的进程,发挥的作用相当于全局的init进程
*/
struct kmem_cache *pid_cachep; //分配pid的slab地址
unsigned int level; //当前命名空间的级别,父进程 level = n,则子进程 level = n+1
struct pid_namespace *parent; //父命名空间
#ifdef CONFIG_PROC_FS
struct vfsmount *proc_mnt;
struct dentry *proc_self;
struct dentry *proc_thread_self;
#endif
#ifdef CONFIG_BSD_PROCESS_ACCT
struct fs_pin *bacct;
#endif
struct user_namespace *user_ns;
struct work_struct proc_work;
kgid_t pid_gid;
int hide_pid;
int reboot; /* group exit code if this pidns was rebooted */
struct ns_common ns;
};
struct pid
{
atomic_t count; // 该pid被不同task_struct引用的次数(不同的命名空间,可以存在相同的pid)
unsigned int level; // 这个pid结构体的深度, root level = 0, root的子空间为1
/* lists of tasks that use this pid */
struct hlist_head tasks[PIDTYPE_MAX]; // 指向与该pid相连的task
struct rcu_head rcu;
struct upid numbers[1]; /* 变长数组, 存储upid结构, 数组大小为 level+1
* numbers[0], numbers[1],..., numbers[level]
* 由于同一个pid从下往上会被映射到多个 namespace 中
* 这里会保存每一个 namespace 下的 upid 信息
*/
};
/*
* 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_vpid */
int nr; //PID具体的值
struct pid_namespace *ns; //指向命名空间的指针
struct hlist_node pid_chain; /* pid哈希列表(pid_hash)中的节点,用于快速通过nr和ns查找到upid
* 在alloc_pid 时将该节点添加到哈希列表中
*/
};
pidmap在pid_namespace结构体中使用,用来标记当前命名空间内pid的使用信息。
struct pid_namespace {
...
struct pidmap pidmap[PIDMAP_ENTRIES]; //分配pid的位图
...
};
pid_link指向了和该task_struct结构体相关的pid结构体。
pid_link在task_struct中的使用:
struct task_struct {
...
/* PID/PID hash table linkage. */
struct pid_link pids[PIDTYPE_MAX];
...
};
enum pid_type
{
PIDTYPE_PID,
PIDTYPE_PGID,
PIDTYPE_SID,
PIDTYPE_MAX
};
pid_link的定义如下:
struct pid_link
{
struct hlist_node node;
struct pid *pid;
};
Linux内核在进行管理时,需要解决以下几个问题:
pid_hash主要用于管理所有的pid信息(不同命名空间,不同pid值)。
当生成pid时,会将该pid中每个命名空间添加到对应的pid_hash列表中。
当知道pid值nr和namespace时,可以通过pid_hash快速查找到对应的upid结构体,进而找到struct pid。
在alloc_pid中添加,简化版如下:
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;
pid->level = ns->level;
// 初始化 pid->numbers[] 结构体
for (i = ns->level; i >= 0; i--) {
nr = alloc_pidmap(tmp); //分配一个局部ID
pid->numbers[i].nr = nr;
pid->numbers[i].ns = tmp;
tmp = tmp->parent;
}
// 初始化 pid->task[] 结构体
for (type = 0; type < PIDTYPE_MAX; ++type)
INIT_HLIST_HEAD(&pid->tasks[type]);
// 将每个命名空间经过哈希之后加入到散列表中
upid = pid->numbers + ns->level;
for ( ; upid >= pid->numbers; --upid) {
hlist_add_head_rcu(&upid->pid_chain,
&pid_hash[pid_hashfn(upid->nr, upid->ns)]);
upid->ns->nr_hashed++;
}
return pid;
}
节点的查找主要在find_pid_ns中使用:
struct pid *find_pid_ns(int nr, struct pid_namespace *ns)
{
struct upid *pnr;
/* pid_hashfn(nr, ns)得到hash列表中的index
* &pid_hash[pid_hashfn(nr, ns)]即为链表头
* 遍历该链表,寻找 nr 和 ns 同时匹配的节点,即可得到struct upid
* struct upid得到struct pid
*/
hlist_for_each_entry_rcu(pnr,
&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;
}