Linux 命名空間


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

taskpid_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則表示每一層levelupid實例,這里的數組只包含一個元素,如果系統只有一個進程,這是可行的,但如果包含多個進程的話,就需要進行分配更多的空間,這個放在結構體中最后一個元素就是方便擴容。

其中PIDTYPE_MAX定義如下:

 

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

 

 

線程組id沒有包含在內,因為它與thread group leaderpid一樣,沒有必要放在里面。

一個任務可以包含多個命名空間,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中的數值nrpid對象的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.     
  6.     //從指定的task[type]中取出函數指針    
  7.     link = &task->pids[type];
  8.     //將task賦值pid
  9.     link ->pid = pid;
  10.     //將該task放入pid的tasks鏈表中
  11.     hlist_add_head_rcu(&link->node,&pid->tasks[type]);
  12.     
  13.     return 0;
  14. }

 

 

內核提供了大量的函數來進行pidtask之間的映射管理及維護,主要包括兩部分:

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_vnrid所屬的命名空間中返回局部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_nrinit進程中獲取全局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,相反釋放時只需修改10

 

  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.     
  6.     pid = last + 1;
  7.     
  8.     if(pid >= pid_max)
  9.          pid = RESERVED_PIDS;
  10.     //從pid中獲取具體位數的偏移量,位圖即每一位一個pid
  11.     offset = pid & BITS_PER_PAGE_MASK;
  12.     map = &pid_ns->pidmap[pid/BITS_PER_PAGE];
  13.     max_scan= (pid_max + BITS_PER_PAGE – 1)/BITS_PER_PAGE - !offset;
  14.     for(i = 0;i<= max;++i)
  15.     {
  16.             //如果內存頁沒有分配就分配一項
  17.             if(unlikely(!map->page)) {
  18.             void *page = kzalloc(PAGE_SIZE,GFP_KERNEL);
  19.             spin_lock_irq(&pidmap_lock);
  20.             if(map->page) kfree(page);
  21.             else map->page = page;
  22.             spin_unlock_irq(&pidmap_lock);
  23.             if(unlikely(!map->page))
  24.                 break;
  25.         }
  26.         if(likely(atomic_read(&map->nr_free))){
  27.         do{
  28.             //掃描到空位即可
  29.             if(!test_and_set_bit(offset,map->page)){
  30.                 atomic_dec(&map->nr_free);
  31.                 pid_ns->last_pid = pid;
  32.                 return pid;
  33.             }
  34.             offset = find_next_offset(map,offset);
  35.             pid = mk_pid(pid_ns,map,offset);
  36.         }while(offset < BITS_PER_PAGE && pid<pid_max &&
  37.              (i != max_scan || pid<last ||
  38.              !((last+1)&&BITS_PER_PAGE_MASK)));
  39.         }
  40.          if(map < &pid_ns->pidmap[(pid_max-1)/BITS_PER_PAGE]) {
  41.             ++map;
  42.             offset = 0;
  43.     }else {
  44.         map = &pid_ns->pidmap[0];
  45.         offset = RESERVED_PIDS;
  46.         if(unlikely(last = offset)) break;    
  47.     }
  48.     //通過一個掃描來的偏移量,生成一個pid
  49.     pid = mk_pid(pid_ns,map,offset);    
  50.     }
  51.     return -1;
  52. }

 

 

 

參考資料

linux2.6.24內核源碼

http://lxr.linux.no

professional Linux architecture

understanding linux kernel 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM