前面看了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[];
  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的具体分配情况

Linux内核中namespace之PID namespace的更多相关文章

  1. Linux内核中流量控制

    linux内核中提供了流量控制的相关处理功能,相关代码在net/sched目录下:而应用层上的控制是通过iproute2软件包中的tc来实现, tc和sched的关系就好象iptables和netfi ...

  2. linux内核中task_struct与thread_info及stack三者的关系

    在linux内核中进程以及线程(多线程也是通过一组轻量级进程实现的)都是通过task_struct结构体来描述的,我们称它为进程描述符.而thread_info则是一个与进程描述符相关的小数据结构,它 ...

  3. Linux 内核中的 Device Mapper 机制

    本文结合具体代码对 Linux 内核中的 device mapper 映射机制进行了介绍.Device mapper 是 Linux 2.6 内核中提供的一种从逻辑设备到物理设备的映射框架机制,在该机 ...

  4. 向linux内核中添加外部中断驱动模块

    本文主要介绍外部中断驱动模块的编写,包括:1.linux模块的框架及混杂设备的注册.卸载.操作函数集.2.中断的申请及释放.3.等待队列的使用.4.工作队列的使用.5.定时器的使用.6.向linux内 ...

  5. Linux内核中双向链表的经典实现

    概要 前面一章"介绍双向链表并给出了C/C++/Java三种实现",本章继续对双向链表进行探讨,介绍的内容是Linux内核中双向链表的经典实现和用法.其中,也会涉及到Linux内核 ...

  6. Linux内核中的fastcall和asmlinkage宏

    代码中看见:#define _fastcall 所以了解下fastcall -------------------------------------------------------------- ...

  7. Linux内核中的GPIO系统之(3):pin controller driver代码分析

    一.前言 对于一个嵌入式软件工程师,我们的软件模块经常和硬件打交道,pin control subsystem也不例外,被它驱动的硬件叫做pin controller(一般ARM soc的datash ...

  8. (十)Linux内核中的常用宏container_of

    Container_of在Linux内核中是一个常用的宏,用于从包含在某个结构中的指针获得结构本身的指针,通俗地讲就是通过结构体变量中某个成员的首地址进而获得整个结构体变量的首地址. Containe ...

  9. Apparmor——Linux内核中的强制访问控制系统

      AppArmor 因为最近在研究OJ(oline judge)后台的安全模块的实现,所以一直在研究Linux下沙箱的东西,同时发现了Apparmor可以提供访问控制. AppArmor(Appli ...

随机推荐

  1. 一款基于的jQuery仿苹果样式焦点图插件

    这次我们要分享的这款jQuery焦点图非常特别,它的外观特别简单,但是又相当大气.焦点图的整体样式是仿苹果样式的,由于jQuery的运用,我们只要点击图片下方的缩略图即可达到图片切换的焦点图特效,这款 ...

  2. Django 2 创建app

    python manage.py startapp polls 创建model 创建完model以后使用 查看sql python manage.py sql polls 然后使用 python ma ...

  3. C语言 · 打印1~100间的质数(素数)

    算法提高 c++_ch02_04   时间限制:1.0s   内存限制:256.0MB      问题描述 输出1~100间的质数并显示出来.注意1不是质数. 输出格式 每行输出一个质数. 2 3 . ...

  4. Desugar Scala(16) -- Lower Bound

    欢迎关注我的新博客地址:http://cuipengfei.me/ Lower bound,不知道这个词的确切中文翻译是怎样的.我们直接看例子吧. 1 2 3 class Pair[T](val fi ...

  5. Pycharm 建立工程,包含多个工程目录

  6. 【转】【Linux】Linux 下zip包的压缩与解压

    linux zip 命令详解 功能说明:压缩文件. 语 法:zip [-AcdDfFghjJKlLmoqrSTuvVwXyz$][-b <工作目录>][-ll][-n <字尾字符串& ...

  7. Java的图形界面依然是跨平台的

    Awt:抽象窗口工具箱,它由三部分组成: ①组件:界面元素: ②容器:装载组件的容器(例如窗体): ③布局管理器:负责决定容器中组件的摆放位置. 图形界面的应用分四步: ① 选择一个容器: ⑴wind ...

  8. jQuery编程中的一些核心方法简介

    调用 jQuery 对象的方法很简单: $('h1').remove(); 大多数 jQuery 方法都是像上面这样被调用的,这些方法都位于 $.fn 命名空间内,这些方法称为 jQuery 对象方法 ...

  9. CPictureEx类

    CPictueEx类不仅可以显示GIF(包括GIF动画),还可以显示JPEG.BMP.WMF.ICO.CUR等. 参考:https://www.codeproject.com/Articles/142 ...

  10. JAVASCRIPT 浏览器兼容性问题及解决方案列表

    JAVASCRIPT 浏览器兼容性问题及解决方案列表(1)获取HTML元素只兼容IE:document.all.hello hello 兼容所有: document.getElementById(“h ...