`task_struct`和`pid_namespace`之间有什么关系?

Ram*_*hil 5 scheduler scheduled-tasks linux-kernel linux-namespaces

我正在研究一些内核代码并试图理解数据结构是如何链接在一起的.我知道调度程序如何工作的基本思路,以及PID是什么.但我不知道命名空间在这个上下文中是什么,并且无法弄清楚所有这些是如何协同工作的.

我已经阅读了一些解释(包括O'Reilly"理解Linux内核"的部分内容)并理解可能是同一个PID到达两个进程,因为一个已终止并且ID被重新分配.但我无法弄清楚这一切是如何完成的.

所以:

  1. 这个上下文中的命名空间是什么?
  2. task_struct和之间有什么关系pid_namespace?(我已经认为它与它有关pid_t,但不知道如何)

一些参考:

Hig*_*eat 8

也许这些链接可能有所帮助

  1. PID命名空间正在运行
  2. PID命名空间的简要介绍(这个来自系统管理员)

通过第二个链接后,很明显名称空间是隔离资源的好方法.在包含Linux的任何操作系统中,进程是最重要的资源之一.用他自己的话说

是的,就是这样,使用此命名空间可以重新启动PID编号并获得自己的"1"过程.这可以被视为进程标识符树中的"chroot".当你需要在日常工作中处理pids并且卡在4位数字时,它非常方便...

因此,您可以创建自己的私有流程树,然后将其分配给特定用户和/或特定任务.在这个树中,进程不必担心PID与此"容器"之外的PID冲突.因此,将这棵树完全交给另一个"root"用户就好了.那位优秀的家伙用一个很好的小例子来解释这些事情做得非常出色,所以我不会在这里重复一遍.

就内核而言,我可以给你一些指导来帮助你入门.我不是这里的专家,但我希望这在某种程度上可以帮到你.

这篇LWN 文章描述了查看PID的旧方法和更新方法.用它自己的话说:

任务可能具有的所有PID都在中描述struct pid.该结构包含ID值,具有此ID的任务列表,引用计数器和散列列表节点,以存储在散列表中以便更快地搜索.关于任务列表的更多话.基本上,任务有三个PID:进程ID(PID),进程组ID(PGID)和会话ID(SID).PGID和SID可以在任务之间共享,例如,当两个或更多个任务属于同一组时,因此每个组ID处理多于一个任务.使用PID命名空间,这种结构变得具有弹性.现在,每个PID可能有多个值,每个值在一个名称空间中有效.也就是说,任务可以在一个命名空间中具有1024的PID,而在另一个命名空间中具有256.所以,前者struct pid改变了.以下是struct pid在引入PID名称空间之前的 样子:

struct pid {
 atomic_t count;                          /* reference counter */
 int nr;                                  /* the pid value */
 struct hlist_node pid_chain;             /* hash chain */
 struct hlist_head tasks[PIDTYPE_MAX];    /* lists of tasks */
 struct rcu_head rcu;                     /* RCU helper */
};
Run Code Online (Sandbox Code Playgroud)

这就是它现在的样子:

struct upid {
   int nr;                            /* moved from struct pid */
   struct pid_namespace *ns;          /* the namespace this value
                                       * is visible in */
   struct hlist_node pid_chain;       /* moved from struct pid */
};

struct pid {
   atomic_t count;
   struct hlist_head tasks[PIDTYPE_MAX];
   struct rcu_head rcu;
   int level;                     /* the number of upids */
   struct upid numbers[0];
};
Run Code Online (Sandbox Code Playgroud)

如您所见,struct upid now表示PID值 - 它存储在散列中并具有PID值.为了转换struct pid到PID或反之亦然一个可以使用一组像助手 task_pid_nr(),pid_nr_ns(),find_task_by_vpid()等.

虽然有点过时,但这些信息足以让您入门.这里需要提到一个更重要的结构.是的struct nsproxy.这个结构是所有命名空间与其关联的进程相关的焦点.它包含一个指向此进程的子进程将使用PID命名空间的指针.使用可以找到当前进程的PID名称空间task_active_pid_ns.

在内部struct task_struct,我们有一个适当调用的命名空间代理指针nsproxy,它指向此进程的struct nsproxy结构.如果您跟踪创建一个新的进程所需的步骤,你可以找到之间的关系(一个或多个)task_struct,struct nsproxystruct pid.

Linux中的新进程总是从现有进程中分离出来,然后使用execve(或exec系列中的类似函数)替换它的映像.因此,作为一部分do_fork,copy_process被调用.

作为复制父进程的一部分,发生以下重要事情:

  1. task_struct首先使用复制dup_task_struct.
  2. 父进程的命名空间也使用复制copy_namespaces.这也nsproxy为子节点创建了一个新结构,它的nsproxy指针指向这个新创建的结构
  3. 对于非INIT进程(原始全局PID,也称为引导时产生的第一个进程),PID分配一个结构,使用alloc_pid该结构为新fork编辑的进程实际分配新的PID结构.这个函数的简短片段:

    nr = alloc_pidmap(tmp);
    if(nr<0)
       goto out_free;
    pid->numbers[i].nr = nr;
    pid->numbers[i].ns = tmp;
    
    Run Code Online (Sandbox Code Playgroud)

upid通过为其提供新的PID以及它当前所属的命名空间来填充结构.

此外,作为copy process函数的一部分,这个新分配的PID然后链接到相应的task_structvia函数,pid_nr即它的全局ID(它是从INIT名称空间看起来的原始PID nr)存储在字段pidtask_struct.

在最后阶段copy_process,通过该功能在该领域内建立task_struct与该新pid结构之间的链接.pid_linktask_structattach_pid

还有很多,但我希望这至少可以给你一些启发.

注意:我指的是最新的(截至目前)内核版本即.3.17.2.