转载于博客园:http://www.cnblogs.com/lucelujiaming/p/9480822.html

在linux中,命名空间主要提供一种轻量级的资源虚拟,可以从不同方面来查看系统的全局属性,不同命名空间可以互相不干扰,为进程的一部分嗯。例如:同一个进程pid,可以被多个进程共享使用,可以查看同一用户的所有资源消费情况等等。

总体定义

定义如下:

  1. struct nsproxy {
  2. atomit_t count;//指向同一个nsproxy的进程个数
  3. struct uts_namespace *uts_ns;//运行的内核
  4. struct ipc_namespace *ipc_ns;//进程通信ipc
  5. struct mnt_namespace *pid_ns;//文件系统
  6. struct user_namespace *user_ns;//用户的资源限制信息
  7. struct net *net_ns;//网络
  8. }

在创建进程(fork,clone系统调用)时,需要设置一些标志来指明命名空间的创建与否

  1. #define CLONE_NEWUTS 0x04000000
  2. #define CLONE_NEWIPC    0x08000000
  3. #define CLONE_NEWUSER 0x10000000
  4. #define CLONE_NEWPID 0x20000000
  5. #define CLONE_NEWNET 0x40000000

同时在每个任务的定义中也包含命名空间的相关域:

  1. struct task_struct{
  2.     …..
  3.     //指针形式,这样可以被多个进程共享,修改一个命名空间,其它进程就可见了
  4.     struct nsproxy *nsproxy;
  5.     …….
  6. }

值得注意的是,命名空间需要在编译其间进行选择,如果没有指明,则所有的属性都是全局的,即只存在一个命名空间,全局命名空间为init_proxy,指向每个子系统的对象:

  1. struct nsproxy init_nsproxy = INIT_NSPROXY(init_nsproxy);
  2. #define INIT_NSPROXY(nsproxy) {
  3.     .pid_ns     =     &init_pid_ns,    \
  4.     .count    =     ATOMIC_INIT(1), \
  5.     .uts_ns    =    &init_uts_ns,     \
  6.     .mnt_ns    =     NULL,         \
  7.     INIT_NET_NS(net_ns),             \
  8.     INIT_IPC_NS(ipc_ns),             \
  9.     .user_ns    =     &init_user_ns,     \
  10.  }

相对应的系统调用有unshare,可以将父子进程的命名空间进行分开或共享。

UTS命名空间

定义如下:

  1. struct uts_namespace {
  2.     struct kref kref;//使用计数
  3.     struct new_utsname name;
  4. }
  5. struct new_utsname
  6. {
  7.     char sysname[65];//系统名称
  8.     char nodename[65];//主机名
  9.     char release[65];//内核版本号
  10.     char version[65];//内核版本日期
  11.     char machine[65];//体系结构
  12.     char domainname[65];
  13. }

测试结果如下:

  1. sys:Linux
  2. node:ubuntu-laptop
  3. release:2.6.32-33-generic
  4. version:#68-Ubuntu SMP Fri Jun 17 16:25:19 UTC 2011
  5. machine:i686
  6. domain:(none)

init进程初始化时,utsname赋值如下:

  1. struct uts_namespace init_uts_ns     =     {
  2.     .kref      = {
  3.         .refcount         = ATOMIC_INIT(2),
  4.     },
  5.     .name = {
  6.         .sysname        = UTS_SYSNAME,
  7.         .nodename        = UTS_NODENAME,
  8.         .release        = UTS_RELEASE,
  9.         .version         = UTS_VERSION,
  10.         .machine        = UTS_MACHINE,
  11.         .domainname    = UTS_DOMAINNAME,
  12.     },
  13. }

这些宏常量定义在内核的各个地方,有些通过编译内核形成的,如UTS_RElEASE,定义在文件

<utsrelease.h>中。有些域可以进行修改,但是有些域不能修改,如UTS_SYSNAME只能是Linux,而UTS_NODENAME则可以进行修改。

User namespace

当需要一个新用户命名空间时,当前用户命名空间就会被复制,并与当前任务的nsproxy相关联,声明如下:

  1. struct user_namespace {
  2.     struct kref         kref;
  3.     struct hlist_head uidhash_table[UIDHASH_SZ];
  4.     struct user_struct *root_user;
  5. }

其中root_user用于记录单个用户资源情况,而uidhash_table将所有user_struct连接起来.

  1. struct user_struct {
  2.     atomic_t __count;//该结构体引用计数
  3.     atomic_t processes;//该用户拥有的进程数量
  4.     atomic_t sigpending;//该用户拥有的悬而未决的信号量数目
  5.     ….
  6.     unsigned long locked_shm;//锁住的共享页个数
  7.     //hash表所维护的信息
  8.     struct hlist_node uidhash_node;
  9.     uid_t uid;
  10.     ……
  11. }

当设置新的用户时,用户命名空间创建如下:

  1. static struct user_namespace *clone_user_ns(struct user_namespace *old_ns)
  2. {
  3.     struct user_namespace *ns;
  4.     struct user_struct *new_user;
  5.     int n;
  6.     
  7.     ns = kmalloc(sizeof(struct user_namespace),GFP_KERNEL);
  8.     if(!ns) 
  9.         return ERR_PTR(-ENOMEM);
  10.     
  11.     kref_init(&ns->kref);
  12.     
  13.     for(n=0; n< UIDHASH_SZ;++n)
  14.      INIT_HLIST_HEAD(ns->uidhash_table+n);
  15.     
  16.     /*insert into new root user*/
  17.     ns->root_user = alloc_uid(ns,0);
  18.     if(!ns->root_user) {
  19.         kfree(ns);
  20.         return ERR_PTR(-ENOMEM);
  21.     }
  22.  
  23.     new_user = alloc_uid(ns,current->uid);
  24.     if(!new_user) {
  25.         free_uid(ns->root_user);
  26.         kfree(ns);
  27.         return ERR_PTR(-ENOMEM);        
  28.     }
  29.         switch(new_user);
  30.         return ns;
  31. }

命名空间增加了pid管理的复杂性,pid命名空间按照树型层次化管理。当一个新的命名空间创建,该命名空间内所有的pid都对其父命名空间可见,但是子命名空间却不能看到父命名空间中pid,这样就意味着有些任务包含有多个pid:父命名空间,子命名空间。这样就产生了两种类型的pid:

Global pid:内核本身包含有效的pid,init任务中可见的全局唯一的pid,也就是系统唯一的pid。

Local pid:在该命名空间内部的pid,全局不是唯一的,在不同的命名空间中,可能存在相同的pid。

task的pid_namespace描述

task_struct 中结构体部分结构如下:

  1. struct task_struct {
  2.     …..
  3.     pid_t pid;
  4.     pid_t tgid;
  5.     struct task_struct *group_leader;
  6.     struct list_head thread_group;
  7.     …..
  8. }

而session id和进程组id没有直接包含在task_struct内部,而是存放在signal结构体中

  1. struct signal_struct {
  2.     ……..
  3.     struct task_struct *curr_target;
  4.     union {
  5.         pid_t pgrp __deprecated;
  6.         pid_t __pgrp;
  7. };
  8.     union {
  9.         pid_t session __deprecated;
  10.         pid_t __session;
  11. };
  12.     ……..
  13. }

对应的修改函数有:set_task_session,set_task_pgrp。

pid的管理

对应的pid_namespace定义如下:

  1. [include/linux/pid_namespace.h]
  2. //每个命名空间都包含一个所有pid的分配图,系统中各个命名空间互不交叉
  3. struct pid_namespace {
  4.     struct kref kref;//进程的引用计数
  5.     struct pidmap pidmap[PID_ENTRIES];//pid分配图
  6.     int last_pid;
  7.     struct task_struct *child_reaper;//当前命名空间中waitpid角色的task
  8.     struct kmem_cache *pid_cachep;
  9.     int level;//当前pid_namespace的深度
  10.     struct pid_namespace *parent;//父pid_namespace
  11. }

init_ns定义如下:

  1. [kernel/pid.c]
  2. struct pid_namespace init_pid_ns = {
  3. .kref = {
  4. .refcount = ATOMIC_INIT(2),
  5. },
  6. //可用pid:4KB 4*1024*8=32768
  7. .pidmap = {
  8. .pidmap = {
  9. [ 0 … PIDMAP_ENTRIES – 1] = {
  10. ATOMIC_INIT(BITS_PER_PAGE),NULL}
  11. },
  12. .last_pid = 0,
  13. .level = 0,
  14. .child_reaper = &init_task,//init_task监听该命名空间的所有task
  15. }

pid的管理主要集中在两个数据结构:struct pid为内核形式的pid,struct upid代表在特定命名空间中可见的信息,定义如下:

  1. [include/linux/pid.h]
  2. struct upid
  3. {
  4.     int nr;//真正的pid值
  5.     struct pid_namespace *ns;//该nr属于哪个pid_namespace
  6.     struct hlist_node pid_chain;//所有upid的hash链表 find_pid
  7. }
  8. //一个pid可以属于不同的级别,每一级别又包含一个upid
  9. struct pid
  10. {
  11.     atomic_t count;//引用计数
  12.     struct hlist_head tasks[PIDTYPE_MAX];//该pid被使用的task链表
  13.     struct rcu_head rcu;//互斥访问
  14.     int level;//该pid所能到达的最大深度
  15.     struct upid numbers[1];//每一层次(level)的upid
  16. }

一个进程可以在多个命名空间中,但是每个命名空间的local id却不相同,numbers则表示每一层level的upid实例,这里的数组只包含一个元素,如果系统只有一个进程,这是可行的,但如果包含多个进程的话,就需要进行分配更多的空间,这个放在结构体中最后一个元素就是方便扩容。

其中PIDTYPE_MAX定义如下:

  1. enum pid_type
  2. {
  3.     PIDTYPE_PID,
  4.     PIDTYPE_PGID,
  5.     PIDTYPE_SID,
  6.     PIDTYPE_MAX
  7. }

线程组id没有包含在内,因为它与thread group leader的pid一样,没有必要放在里面。

一个任务可以包含多个命名空间,task_struct的结构体中显示如下:

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

pid_link就是链接所有的pid:

  1. struct pid_link {
  2. struct hlist_node node;//由于每个task包含多个pid(多个命名空间可见),指向的是自己
  3. struct pid    *pid;
  4. }

而实现upid中的数值nr到pid对象的hash映射如下:

  1. static struct hlist_head *pid_hash;//双向hash链表
  2. pid_hash是一个hlist_head数组,大小根据机器的内存配置,从16到4096,初始化代码如下:
  3. [kernel/pid.c]
  4. void __init pidhash_init(void)
  5. {
  6.     int i, pidhash_size;
  7.     //#define PAGE_SHIFT 12
  8.     unsigned long megabytes = nr_kernel_pages >> (20-PAGE_SHIFT);
  9.     
  10.     pidhash_shift = max(4,fls(megabytes * 4));
  11.     pidhash_shift = min(12,pidhash_shift);
  12.     //16项到4096项
  13.     pidhash_size = 1 << pidhash_shift;
  14.     
  15.     ……..
  16.     pid_hash = alloc_bootmem(pidhash_size*sizeof(*(pid_hash)));
  17.     if(!pid_hash)
  18.         panic(“Could not alloc pidhash”);
  19.     for(i=0;i< pidhash_size;++i)
  20.      INIT_HLIST_HEAD(&pid_hash[i]);
  21. }

当struct pid已经分配,需要链接到具体task中时,执行代码如下:

  1. int fastcall attach_pid(struct task_struct *task,enum pid_type type,
  2.                 struct pid *pid)
  3. {
  4.     struct pid_link *link;
  5.     //从指定的task[type]中取出函数指针    
  6.     link = &task->pids[type];
  7.     //将task赋值pid
  8.     link ->pid = pid;
  9.     //将该task放入pid的tasks链表中
  10.     hlist_add_head_rcu(&link->node,&pid->tasks[type]);
  11.     return 0;
  12. }

内核提供了大量的函数来进行pid到task之间的映射管理及维护,主要包括两部分:

1.指定局部数值pid,对应的命名空间,查找到对应的进程。

这种情况主要有以下几种情况:

1.获取与pid相关联的task,task_pid,task_tgid,task_pgrp,task_session用于不同类型的ID。

  1. //每一task有四种不同类型的type,每一种type中包含有一个pid
  2. static inline struct pid *task_pid(struct task_struct *task)
  3. {
  4.     return task->pids[PIDTYPE_PID].pid;
  5. }

其它的也与上面的类似。

2.通过pid和命名空间namespace来查找进程的pid_t

  1. [kernel/pid.c]
  2. pid_t pid_nr_ns(struct pid *pid,struct pid_namespace *ns)
  3. {
  4.     struct upid *upid;
  5.     pid_t nr = 0;
  6.     //指定的命名空间深度必须比pid高
  7.     if(pid && ns->level <= pid->level)
  8.     {
  9.         upid = & pid->numbers[pid->level];
  10.         if(upid->ns == ns)
  11.             nr = upid->nr;    
  12.     }
  13.         return nr;
  14. }

内核还采用了另外的方法来访问进程的pid:

1.pid_vnr从id所属的命名空间中返回局部pid,如:

  1. pid_t task_pid_vnr(struct task_struct*);
  2. pid_t task_tgid_vnr(struct task_struct*);
  3. pid_t task_pgrp_vnr(struct task_struct*);
  4. pid_t task_session_vnr(struct task_struct*);

这些函数都是通过pid_nr_ns来实现的

2.pid_nr从init进程中获取全局pi

这两个函数实际都通过指明level级别(0表示全局)调用了函数pid_nr_ns,它则是通过nr_ns系列函数来进行访问,如:

  1. pid_t task_pid_nr_ns(struct task_struct *,struct pid_namespace *);
  2. pid_t task_tgid_nr_ns(struct task_struct *,struct pid_namespace*);
  3. pid_t task_pgrp_nr_ns(struct task_struct*,struct pid_namespace*);
  4. pid_t task_session_nr_ns(struct task_struct *,struct pid_namespace *);

这些函数也是通过pid_nr_ns来实现的。

另外,还可以通过pid中的数值id-nr和命名空间来获取对应的pid,如下:

  1. struct pid *fastcall find_pid_ns(int nr,struct pid_namespace *ns)
  2. {
  3.     struct hlist_node *elem;
  4.     struct upid *pnr;
  5.     //通过nr进行hash查找到对应的struct upid,pid_hash为全局唯一
  6.     hlist_for_each_entry_rcu(pnr,elem,
  7.         &pid_hash[pid_hashfn(nr,ns)],pid_chain)
  8.      if(pnr->nr == ns)
  9.     //通过pid中的成员变量upid来查找对应的struct pid
  10.     return container_of(pnr,struct pid,    numbers[ns->level]);
  11. }

2.指定一个进程,id类型,及命名空间,查找到对应的进程pid,如find_task系列函数:

  1. [kernel/pid.c]
  2. struct task_struct *find_task_by_pid_type_ns(int type,int nr,
  3.          struct pid_namespace *ns);
  4. struct task_struct *find_task_by_pid(pid_t nr);
  5. struct task_struct *find_task_by_vpid(pid_t vnr);
  6. struct task_struct *find_task_by_pid_ns(pid_t nr,struct namespace *ns);

这些函数都是通过find_task_by_pid_type_ns来实现的

  1. struct task_struct *find_task_by_pid_type_ns(int type,int nr,
  2.             struct pid_namspace *ns)
  3. {
  4.     return pid_task(find_pid_ns(nr,ns),type);
  5. }

而pid_task实现如下:

  1. struct task_struct *fastcall pid_task(struct pid *pid,enum pid_type type)
  2. {
  3.     struct task_struct *result = NULL;
  4.     if(pid){
  5.         struct hlist_node *first;
  6.         //获取指定type的task_struct
  7.         first = rcu_dereference(pid->tasks[type].node);
  8.         if(first)
  9. //获取first中的pid_link域的pids[(type)].node值
  10. //因为pid_link中node域就是自己所以就直接获取,
  11. //那里指向自己的hlist_head就是为了满足这里的统一
  12.             result = hlist_entry(first,struct task_struct,pids[(type)].node)
  13.     }
  14.         return result;
  15. }

pid的分配

为了记录pid的分配与释放情况,内核使用了一张pid位图,可以从pid位图中的位置来获取对应的pid值,同时将pid值从0改为1,相反释放时只需修改1为0

  1. static int alloc_pidmap(struct pid_namespace *pid_ns)
  2. {
  3.     int i,offset,max_scan,pid,last = pid_ns ->last_pid;//上一次
  4.     struct pidmap *map;
  5.     pid = last + 1;
  6.     if(pid >= pid_max)
  7.          pid = RESERVED_PIDS;
  8.     //从pid中获取具体位数的偏移量,位图即每一位一个pid
  9.     offset = pid & BITS_PER_PAGE_MASK;
  10.     map = &pid_ns->pidmap[pid/BITS_PER_PAGE];
  11.     max_scan= (pid_max + BITS_PER_PAGE – 1)/BITS_PER_PAGE – !offset;
  12.     for(i = 0;i<= max;++i)
  13.     {
  14.             //如果内存页没有分配就分配一项
  15.             if(unlikely(!map->page)) {
  16.             void *page = kzalloc(PAGE_SIZE,GFP_KERNEL);
  17.             spin_lock_irq(&pidmap_lock);
  18.             if(map->page) kfree(page);
  19.             else map->page = page;
  20.             spin_unlock_irq(&pidmap_lock);
  21.             if(unlikely(!map->page))
  22.                 break;
  23.         }
  24.         if(likely(atomic_read(&map->nr_free))){
  25.         do{
  26.             //扫描到空位即可
  27.             if(!test_and_set_bit(offset,map->page)){
  28.                 atomic_dec(&map->nr_free);
  29.                 pid_ns->last_pid = pid;
  30.                 return pid;
  31.             }
  32.             offset = find_next_offset(map,offset);
  33.             pid = mk_pid(pid_ns,map,offset);
  34.         }while(offset < BITS_PER_PAGE && pid<pid_max &&
  35.              (i != max_scan || pid<last ||
  36.              !((last+1)&&BITS_PER_PAGE_MASK)));
  37.         }
  38.          if(map < &pid_ns->pidmap[(pid_max-1)/BITS_PER_PAGE]) {
  39.             ++map;
  40.             offset = 0;
  41.     }else {
  42.         map = &pid_ns->pidmap[0];
  43.         offset = RESERVED_PIDS;
  44.         if(unlikely(last = offset)) break;    
  45.     }
  46.     //通过一个扫描来的偏移量,生成一个pid
  47.     pid = mk_pid(pid_ns,map,offset);    
  48.     }
  49.     return -1;
  50. }

参考资料

linux2.6.24内核源码

http://lxr.linux.no

professional Linux architecture

understanding linux kernel