• Linux内核中namespace之PID namespace


    前面看了LInux PCI设备初始化,看得有点晕,就转手整理下之前写的笔记,同时休息一下!!~(@^_^@)~


    这片文章是之前写的,其中参考了某些大牛们的博客!!

    PID框架的设计

    一个框架的设计会考虑很多因素,相信分析过Linux内核的读者来说会发现,内核的大量数据结构被哈希表和链表链接起来,最最主要的目的就是在于查找。可想而知一个好的框架,应该要考虑到检索速度,还有考虑功能的划分。那么在PID框架中,需要考虑以下几个因素.

    • 如何通过task_struct快速找到对应的pid

    • 如何通过pid快速找到对应的task_struct

    • 如何快速的分配一个唯一的pid

    这些都是PID框架设计的时候需要考虑的一些基本的因素。也正是这些因素将PID框架设计的愈加复杂。

    原始的PID框架

    先考虑的简单一点,一个进程对应一个pid

    struct task_struct {

      .....

      pid_t pid;

      .....

    }

    引入hlist和pid位图

    struct task_struct *pidhash[PIDHASH_SZ];

    这样就很方便了,再看看PID框架设计的一些因素是否都满足了,如何分配一个唯一的pid呢,连续递增?,那么前面分配的进程如果结束了,那么分配的pid就需要回收掉,直到分配到PID的最大值,然后从头再继续。好吧,这或许是个办法,但是是不是需要标记一下那些pid可用呢?到此为此这看起来似乎是个解决方案,但是考虑到这个方案是要放进内核,开发linux的那帮家伙肯定会想近一切办法进行优化的,的确如此,他们使用了pid位图,但是基本思想没有变,同样需要标记pid是否可用,只不过使用pid位图的方式更加节约内存.想象一下,通过将每一位设置为0或者是1,可以用来表示是否可用,第1位的0和1用来表示pid为1是否可用,以此类推.到此为此一个看似还不错的pid框架设计完成了,下图是目前整个框架的整体效果.

     

    用上面大牛的引言来引入今天的主题:

     其实PID namespace带来的好处远不止于此,其中最重要的就是对轻量级虚拟化的支持。当前炙手可热的docker就是基于Linux 内核中命名空间的原理。有了PID命名空间,我们就可以保证进程的隔离性,至少在进程的角度,可以按照namespace的方式去组织,不同namespace的进程互不干扰。这也是笔者要分析下底层虚拟化支持的初衷之一!

    言归正传:

    引入进程PID命名空间后的PID框架

    随着内核不断的添加新的内核特性,尤其是PID Namespace机制的引入,这导致PID存在命名空间的概念,并且命名空间还有层级的概念存在,高级别的可以被低级别的看到,这就导致高级别的进程有多个PID,比如说在默认命名空间下,创建了一个新的命名空间,占且叫做level1,默认命名空间这里称之为level0,在level1中运行了一个进程在level1中这个进程的pid为1,因为高级别的pid namespace需要被低级别的pid namespace所看见,所以这个进程在level0中会有另外一个pid,为xxx.套用上面说到的pid位图的概念,可想而知,对于每一个pid namespace来说都应该有一个pidmap,上文中提到的level1进程有两个pid一个是1,另一个是xxx,其中pid为1是在level1中的pidmap进行分配的,pid为xxx则是在level0的pidmap中分配的. 下面这幅图是整个pidnamespace的一个框架

    可以看到这里出现了一个层次结构,即一个命名空间可以有其子命名空间,子命名空间中的进程同样会出现在父命名空间中,只是进程ID是不同的。看PID的结构:

    1 struct pid
    2 {
    3     atomic_t count;
    4     unsigned int level;
    5     /* lists of tasks that use this pid */
    6     struct hlist_head tasks[PIDTYPE_MAX];
    7     struct rcu_head rcu;
    8     struct upid numbers[1];
    9 };

    count表示这个PID的引用计数,同一个PID结构可以为多个进程所共享;

    level表示该PID所在的层级;

    tasks是一个HASH数组,每一项都是一个链表头。分别是PID链表头,进程组ID表头,会话ID表头;

    rcu用于保护指针引用;

    numbers是一个UPID数组,记录对应层级的命名空间中的UPID,所以可以想到,该PID处于第几层,那么这个数组应该有几项(当然都是从0开始)。

    看下UPID结构:

    1 struct upid {
    2     /* Try to keep pid_chain in the same cacheline as nr for find_vpid */
    3     int nr;
    4     struct pid_namespace *ns;
    5     struct hlist_node pid_chain;
    6 };

    UPID相比之下就简单的多,或者说这才是真正的PID。

    nr表示ID号;

    namespace指向该UPID所在命名空间的namespace结构;

    pid_chain是一个链表,系统会把UPID 的nr和namespace的ns经过某种hash,得到在一个全局的Hash数组中的下标,此UPID便会加入到对应下标的链表中。

    看下进程中对于PID是如何应用的:

    1 struct task_struct{
    2 ...
    3 struct pid_link pids[PIDTYPE_MAX];
    4 ...
    5 }

     进程中包含一个pid_link结构的数组,我们还是先看一个pid_link结构:

    1 struct pid_link
    2 {
    3     struct hlist_node node;
    4     struct pid *pid;
    5 };

    node作为一个节点加入到所有引用同一PID结构的进程链表中;

    pid指向该进程引用的PID结构。

    也许到这里还是显得关系有点紊乱,那么看一下下面的图:

    没错,这个图是我画的!!哈哈,可花了我不少时间了,希望对大家理解进程结构和PID以及UPID之间的关系有所帮助。贴出这个图突然发现没有什么好解释的了,各种关系图中已经表明。不过需要注意的是最下面的UPID我仅仅用一个框框代表了,其实是Numbers指向UPID结构,而让UPID加入到链表中的是结构中的pid_chain;还有一个问题就是UPID那一组链表并不是表示同一PID下的所有UPID,根据内核源代码,这里只是处理冲突的一种方式,而这里画成链表仅仅是为了表示这里是以链表存在的。

    下面说下PID位图:

    每一个namespace都对应一个map,用于分配pid号。这里包含两个成员,nr_free表示可用的ID号数量,第二个是一个指向一个页面的指针。这么设计有其自身的合理性。默认情况下page指向一个页面,而一个页面是4kb的大小,即4096个字节,也就是4096*8bit,每一位表示一个pid号,一个页面就可以表示32768个进程ID,普通情况下绝对是够了,即使特殊情况不够,那么还可以动态扩展,这就是其合理性所在。

    1 struct pidmap {
    2        atomic_t nr_free;
    3        void *page;
    4 };

     这里是pid_namespace结构:

     1 struct pid_namespace {
     2     struct kref kref;
     3     struct pidmap pidmap[PIDMAP_ENTRIES];//命名空间对应的PID位图
     4     int last_pid;//上次分配的PID,便于下次分配
     5     unsigned int nr_hashed;
     6     struct task_struct *child_reaper;
     7     struct kmem_cache *pid_cachep;
     8     unsigned int level;//命名空间所在层级
     9     struct pid_namespace *parent;//指向父命名空间的指针
    10 #ifdef CONFIG_PROC_FS
    11     struct vfsmount *proc_mnt;
    12     struct dentry *proc_self;
    13 #endif
    14 #ifdef CONFIG_BSD_PROCESS_ACCT
    15     struct bsd_acct_struct *bacct;
    16 #endif
    17     struct user_namespace *user_ns;
    18     struct work_struct proc_work;
    19     kgid_t pid_gid;
    20     int hide_pid;
    21     int reboot;    /* group exit code if this pidns was rebooted */
    22     unsigned int proc_inum;
    23 };

     下一节会结合LInux内核源代码分析下PID的具体分配情况

  • 相关阅读:
    Java script基础 回顾
    Application、 session、iewstate,以及repeater 的commang用法
    Response、Request、QueryString,repeater添加,修改,删除数据
    Repeater 使用方法
    web form 复合控件
    weborm 简单控件
    WebForm开发基础
    Asp.Net 基础理论
    winform 进程,线程
    repeater使用
  • 原文地址:https://www.cnblogs.com/ck1020/p/5954135.html
Copyright © 2020-2023  润新知