大家在linux当中使用容器的时候,都会或多或少的听说过命名空间这个东西,今天咱们就来过一下linux当中的pid命名空间。
在linux当中,pid_namespace是按照层级结构来组织的,在建立一个新的命名空间时,该命名空间中的所有pid对父命名空间都是可见的,但是子命名空间却无法看见父命名空间的pid;这样就导致了存在某些进程会有多个pid的情况,凡是可以看到该进程的命名空间,都会为其分配一个pid。这样的话就需要linux内核通过特定的数据结构来管理所谓的pid了。
首先,需要明确的是,每个进程除了pid这个特征值之外,还有其他的id,如下代码:
-
enum pid_type
-
{
-
PIDTYPE_PID,// 进程id
-
PIDTYPE_TGID,// 线程组长id
-
PIDTYPE_PGID,// 进程组长id
-
PIDTYPE_SID,// 会话组长id
-
PIDTYPE_MAX,
-
};
其次,我们必须区分全局id和局部id:
全局id:内核本身和初始命名空间中的唯一id号,对于每一种id类型,都有一个给定的全局id,保证在整个系统中是唯一的。
局部id:属于某个特定的命名空间,不具备全局有效性。对每个id类型,它们在所属的命名空间内部有效,但类型相同、值也相同的id可能出现在不同的命名空间当中。
接着,就需要介绍以下内核管理进程id的相关数据结构了:
-
struct pid_namespace {
-
struct kref kref;
-
...
-
struct task_struct *child_reaper;// 相当于init进程,为孤儿进程调用wait系列函数
-
...
-
unsigned int level;// 当前命名空间在全局当中的层次深度,初始命名空间的level为0,其余递增
-
struct pid_namespace *parent;// 指向父命名空间的指针
-
...
-
};
有了level变量之后,就可以根据level的值来判断该命名空间管理的进程会关联到多少个id(层级关系,子命名空间的id在父命名空间可见)
PID的管理,在内核当中围绕两个数据结构展开:
-
struct upid {
-
int nr;// 在指定命名空间中的id数值
-
struct pid_namespace *ns;// 指向nr所属的命名空间
-
};
-
-
struct pid
-
{
-
refcount_t count;
-
unsigned int level;// 包含该进程的命名空间在命名空间层次结构当中的深度
-
/* lists of tasks that use this pid 所有用这个pid的task都位于下面的散列表*/
-
struct hlist_head tasks[PIDTYPE_MAX];// 每个数组元素都是一个散列表头,对应于一个id类型的task_struct散列表
-
/* wait queue for pidfd notifications */
-
wait_queue_head_t wait_pidfd;
-
struct rcu_head rcu;
-
struct upid numbers[1];// 别看定义只有一个,由于其在结构体最后,可以多分配,直接插入就可以实现数组功能
-
};
内核当中pid相关的变量如下:
-
struct task_struct{
-
...
-
struct pid *thread_pid;
-
struct hlist_node pid_links[PIDTYPE_MAX];
-
...
-
};
这样的话,内核当中的数据结构就能够满足下面两种不同的任务:
1. 给出局部数字id和对应的命名空间,查找此二元组对应的task_struct(依据tasks哈希表)
a). 这里会根据命名空间中的pid_hash(以upid.nr和upid.ns为key进行hash)找到对应的struct pid
b). 找到struct pid之后,根据对应的type,去结构体当中的tasks中找到对应的task_struct
-
struct task_struct *find_task_by_pid_ns(pid_t nr, struct pid_namespace *ns)
-
{
-
RCU_LOCKDEP_WARN(!rcu_read_lock_held(),
-
"find_task_by_pid_ns() needs rcu_read_lock() protection");
-
return pid_task(find_pid_ns(nr, ns), PIDTYPE_PID);
-
}
-
struct task_struct *pid_task(struct pid *pid, enum pid_type type)
-
{
-
struct task_struct *result = NULL;
-
if (pid) {
-
struct hlist_node *first;
-
first = rcu_dereference_check(hlist_first_rcu(&pid->tasks[type]),
-
lockdep_tasklist_lock_is_held());
-
if (first)
-
result = hlist_entry(first, struct task_struct, pid_links[(type)]);
-
}
-
return result;
-
}
2. 给出task_struct、id类型、命名空间,取得命名空间局部id(依据pid当中的numbers数组)
a). 先根据task_struct当中的pid_links找到对应的pid结构,如果是pid,直接thread_pid,其他的需要pid_links来辅助进行
b). 获得pid实例之后,从struct pid的numbers数组中的相关信息,就可以得到对应的数字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;
-
}
文章最后说一下内核为啥用struct pid这么个结构体来表示进程在内核当中的标识吧:
如果只用pid_t变量存储的话,当进程结束后又有新的进程复用pid_t的时候,会有问题;
如果用task_struct 来保存,当进程退出的时候,这个结构还被占用,占用空间太大;
使用一个占用空间小,且独立的结构能够解决上述两个问题,so~~~
阅读(3002) | 评论(0) | 转发(0) |