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的具体分配情况

时间: 2024-08-02 13:47:59

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

linux内核中驱动开发常见的类似多态

题意:求一个无向图的,去掉两个不同的点后最多有几个连通分量. 思路:枚举每个点,假设去掉该点,然后对图求割点后连通分量数,更新最大的即可.算法相对简单,但是注意几个细节: 1:原图可能不连通. 2:有的连通分量只有一个点,当舍去该点时候,连通分量-1: 复习求割点的好题! #include<iostream> #include<cstdio> #include<vector> using namespace std; int n,m; vector<vector&

linux内核中与进程相关的数据结构(基于linux-mainline-rc4)

1.进程描述符    struct task_struct {  volatile long state; ....... struct list_head tasks; ....... struct mm_struct *mm, *active_mm; ....... struct vm_area_struct *vmacache[VMACACHE_SIZE]; ...... pid_t pid; pid_t tgid; .......   }所在文件:include/linux/sched.

Linux内核中常用String库函数实现

//只列举了部分常用的strcpy,strcmp,strcat,strchr,strstr,strpbrk...  char *strcpy(char *dest, const char *src) { char *tmp = dest; while ((*dest++ = *src++) != '\0') /* nothing */; return tmp; } char *strncpy(char *dest, const char *src, size_t count) { char *t

Linux内核中的jiffies及其作用介绍及jiffies等相关函数详解

在LINUX的时钟中断中涉及至二个全局变量一个是xtime,它是timeval数据结构变量,另一个则是jiffies,首先看timeval结构struct timeval{time_t tv_sec; /***second***/susecond_t tv_usec;/***microsecond***/}到底microsecond是毫秒还是微秒?? 1秒=1000毫秒(3个零),1秒=1000 000微秒(6个零),1秒=1000 000 000纳秒(9个零),1秒=1000 000 000

route-显示并设置Linux内核中的网络路由表

route命令 网络配置 route命令用来显示并设置Linux内核中的网络路由表,route命令设置的路由主要是静态路由.要实现两个不同的子网之间的通信,需要一台连接两个网络的路由器,或者同时位于两个网络的网关来实现. 语法 route(选项)(参数) 选项 -A:设置地址类型: -C:打印将Linux核心的路由缓存: -v:详细信息模式: -n:不执行DNS反向查找,直接显示数字形式的IP地址: -e:netstat格式显示路由表: -net:到一个网络的路由表: -host:到一个主机的路

C语言在linux内核中do while(0)妙用之法

为什么说do while(0) 妙?因为它的确就是妙,而且在linux内核中实现是相当的妙,我们来看看内核中的相关代码: #define db_error(fmt, ...) do { fprintf(stderr, "(error): "); fprintf(stderr, fmt, ##__VA_ARGS__); } while (0) 这只是个普通的调试信息的输出,有人便会认为,你这不是多此一举吗?去掉do while(0)不一样也实现了吗?其实不然,我们看看例子就清楚了,尽管很

Linux内核中的中断栈与内核栈的补充说明【转】

转自:http://blog.chinaunix.net/uid-12461657-id-3487463.html 原文地址:Linux内核中的中断栈与内核栈的补充说明 作者:MagicBoy2010 中断栈与内核栈的话题更多地属于内核的范畴,所以在<深入Linux设备驱动程序内核机制>第5章“中断处理”当中,基本上没怎么涉及到上述内容,只是在5.4节有些许的文字讨论中断栈在中断嵌套情形下可能的溢出问题. 本贴在这个基础上对内核栈与中断栈的话题做些补充,讨论基于x86 32位系统,因为64位系

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

本文主要介绍外部中断驱动模块的编写,包括:1.linux模块的框架及混杂设备的注册.卸载.操作函数集.2.中断的申请及释放.3.等待队列的使用.4.工作队列的使用.5.定时器的使用.6.向linux内核中添加外部中断驱动模块.7.完整驱动程序代码.linux的内核版本为linux2.6.32.2. 一.linux模块的框架以及混杂设备相关知识 1.内核模块的框架如下图所示,其中module_init()(图中有误,不是modules_init)只有在使用insmod命令手动加载模块时才会被调用,

Linux内核中的软中断、tasklet和工作队列详解

[TOC] 本文基于Linux2.6.32内核版本. 引言 软中断.tasklet和工作队列并不是Linux内核中一直存在的机制,而是由更早版本的内核中的"下半部"(bottom half)演变而来.下半部的机制实际上包括五种,但2.6版本的内核中,下半部和任务队列的函数都消失了,只剩下了前三者. 介绍这三种下半部实现之前,有必要说一下上半部与下半部的区别. 上半部指的是中断处理程序,下半部则指的是一些虽然与中断有相关性但是可以延后执行的任务.举个例子:在网络传输中,网卡接收到数据包这