<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          容器三把斧之 | namespace原理與實(shí)現(xiàn)

          共 10255字,需瀏覽 21分鐘

           ·

          2021-08-10 14:18

          namespace介紹

          namespace(命名空間) 是Linux提供的一種內(nèi)核級別環(huán)境隔離的方法,很多編程語言也有 namespace 這樣的功能,例如C++,Java等,編程語言的 namespace 是為了解決項(xiàng)目中能夠在不同的命名空間里使用相同的函數(shù)名或者類名。而Linux的 namespace 也是為了實(shí)現(xiàn)資源能夠在不同的命名空間里有相同的名稱,譬如在 A命名空間 有個(gè)pid為1的進(jìn)程,而在 B命名空間 中也可以有一個(gè)pid為1的進(jìn)程。

          有了 namespace 就可以實(shí)現(xiàn)基本的容器功能,著名的 Docker 也是使用了 namespace 來實(shí)現(xiàn)資源隔離的。

          Linux支持6種資源的 namespace,分別為(文檔):

          TypeParameterLinux Version
          Mount namespacesCLONE_NEWNSLinux 2.4.19
          UTS namespacesCLONE_NEWUTSLinux 2.6.19
          IPC namespacesCLONE_NEWIPCLinux 2.6.19
          PID namespacesCLONE_NEWPIDLinux 2.6.24
          Network namespacesCLONE_NEWNETLinux 2.6.24
          User namespacesCLONE_NEWUSERLinux 2.6.23

          在調(diào)用 clone() 系統(tǒng)調(diào)用時(shí),傳入以上的不同類型的參數(shù)就可以實(shí)現(xiàn)復(fù)制不同類型的namespace。比如傳入 CLONE_NEWPID 參數(shù)時(shí),就是復(fù)制 pid命名空間,在新的 pid命名空間 里可以使用與其他 pid命名空間 相同的pid。代碼如下:

          #define _GNU_SOURCE
          #include <sched.h>
          #include <stdio.h>
          #include <unistd.h>
          #include <sys/types.h>
          #include <sys/wait.h>
          #include <signal.h>
          #include <stdlib.h>
          #include <errno.h>

          char child_stack[5000];

          int child(void* arg)
          {
              printf("Child - %d\n", getpid());
              return 1;
          }

          int main()
          {
              printf("Parent - fork child\n");
              int pid = clone(child, child_stack+5000, CLONE_NEWPID, NULL);
              if (pid == -1) {
                  perror("clone:");
                  exit(1);
              }
              waitpid(pid, NULL0);
              printf("Parent - child(%d) exit\n", pid);
              return 0;
          }

          輸出如下:

          Parent - fork child
          Parent - child(9054) exit
          Child - 1

          從運(yùn)行結(jié)果可以看出,在子進(jìn)程的 pid命名空間 里當(dāng)前進(jìn)程的pid為1,但在父進(jìn)程的 pid命名空間 中子進(jìn)程的pid卻是9045。

          namespace實(shí)現(xiàn)原理

          為了讓每個(gè)進(jìn)程都可以從屬于某一個(gè)namespace,Linux內(nèi)核為進(jìn)程描述符添加了一個(gè) struct nsproxy 的結(jié)構(gòu),如下:

          struct task_struct {
              ...
              /* namespaces */
              struct nsproxy *nsproxy;
              ...
          }

          struct nsproxy {
              atomic_t count;
              struct uts_namespace  *uts_ns;
              struct ipc_namespace  *ipc_ns;
              struct mnt_namespace  *mnt_ns;
              struct pid_namespace  *pid_ns;
              struct user_namespace *user_ns;
              struct net            *net_ns;
          };

          從 struct nsproxy 結(jié)構(gòu)的定義可以看出,Linux為每種不同類型的資源定義了不同的命名空間結(jié)構(gòu)體進(jìn)行管理。比如對于 pid命名空間 定義了 struct pid_namespace 結(jié)構(gòu)來管理 。由于 namespace 涉及的資源種類比較多,所以本文主要以 pid命名空間 作為分析的對象。

          我們先來看看管理 pid命名空間 的 struct pid_namespace 結(jié)構(gòu)的定義:

          struct pid_namespace {
              struct kref kref;
              struct pidmap pidmap[PIDMAP_ENTRIES];
              int last_pid;
              struct task_struct *child_reaper;
              struct kmem_cache *pid_cachep;
              unsigned int level;
              struct pid_namespace *parent;
          #ifdef CONFIG_PROC_FS
              struct vfsmount *proc_mnt;
          #endif
          };

          因?yàn)?nbsp;struct pid_namespace 結(jié)構(gòu)主要用于為當(dāng)前 pid命名空間 分配空閑的pid,所以定義比較簡單:

          • kref 成員是一個(gè)引用計(jì)數(shù)器,用于記錄引用這個(gè)結(jié)構(gòu)的進(jìn)程數(shù)
          • pidmap 成員用于快速找到可用pid的位圖
          • last_pid 成員是記錄最后一個(gè)可用的pid
          • level 成員記錄當(dāng)前 pid命名空間 所在的層次
          • parent 成員記錄當(dāng)前 pid命名空間 的父命名空間

          由于 pid命名空間 是分層的,也就是說新創(chuàng)建一個(gè) pid命名空間 時(shí)會記錄父級 pid命名空間 到 parent 字段中,所以隨著 pid命名空間 的創(chuàng)建,在內(nèi)核中會形成一顆 pid命名空間 的樹,如下圖(圖片來源):

          pid-namespace

          第0層的 pid命名空間 是 init 進(jìn)程所在的命名空間。如果一個(gè)進(jìn)程所在的 pid命名空間 為 N,那么其在 0 ~ N 層pid命名空間 都有一個(gè)唯一的pid號。也就是說 高層pid命名空間 的進(jìn)程對 低層pid命名空間 的進(jìn)程是可見的,但是 低層pid命名空間 的進(jìn)程對 高層pid命名空間 的進(jìn)程是不可見的。

          由于在 第N層pid命名空間 的進(jìn)程其在 0 ~ N層pid命名空間 都有一個(gè)唯一的pid號,所以在進(jìn)程描述符中通過 pids 成員來記錄其在每個(gè)層的pid號,代碼如下:

          struct task_struct {
              ...
              struct pid_link pids[PIDTYPE_MAX];
              ...
          }

          enum pid_type {
              PIDTYPE_PID,
              PIDTYPE_PGID,
              PIDTYPE_SID,
              PIDTYPE_MAX
          };

          struct upid {
              int nr;
              struct pid_namespace *ns;
              struct hlist_node pid_chain;
          };

          struct pid {
              atomic_t count;
              struct hlist_head tasks[PIDTYPE_MAX];
              struct rcu_head rcu;
              unsigned int level;
              struct upid numbers[1];
          };

          struct pid_link {
              struct hlist_node node;
              struct pid *pid;
          };

          這幾個(gè)結(jié)構(gòu)的關(guān)系如下圖:

          pid-namespace-structs

          我們主要關(guān)注 struct pid 這個(gè)結(jié)構(gòu),struct pid 有個(gè)類型為 struct upid 的成員 numbers,其定義為只有一個(gè)元素的數(shù)組,但是其實(shí)是一個(gè)動態(tài)的數(shù)據(jù),它的元素個(gè)數(shù)與 level 的值一致,也就是說當(dāng) level 的值為5時(shí),那么 numbers 成員就是一個(gè)擁有5個(gè)元素的數(shù)組。而每個(gè)元素記錄了其在每層 pid命名空間 的pid號,而 struct upid 結(jié)構(gòu)的 nr 成員就是用于記錄進(jìn)程在不同層級 pid命名空間 的pid號。

          我們通過代碼來看看怎么為進(jìn)程分配pid號的,在內(nèi)核中是用過 alloc_pid() 函數(shù)分配pid號的,代碼如下:

          struct pid *alloc_pid(struct pid_namespace *ns)
          {
              struct pid *pid;
              enum pid_type type;
              int i, nr;
              struct pid_namespace *tmp;
              struct upid *upid;

              pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL);
              if (!pid)
                  goto out;

              tmp = ns;
              for (i = ns->level; i >= 0; i--) {
                  nr = alloc_pidmap(tmp);    // 為當(dāng)前進(jìn)程所在的不同層級pid命名空間分配一個(gè)pid
                  if (nr < 0)
                      goto out_free;

                  pid->numbers[i].nr = nr;   // 對應(yīng)i層namespace中的pid數(shù)字
                  pid->numbers[i].ns = tmp;  // 對應(yīng)i層namespace的實(shí)體
                  tmp = tmp->parent;
              }

              get_pid_ns(ns);
              pid->level = ns->level;
              atomic_set(&pid->count, 1);
              for (type = 0; type < PIDTYPE_MAX; ++type)
                  INIT_HLIST_HEAD(&pid->tasks[type]);

              spin_lock_irq(&pidmap_lock);
              for (i = ns->level; i >= 0; i--) {
                  upid = &pid->numbers[i];
                  // 把upid連接到全局pid中, 用于快速查找pid
                  hlist_add_head_rcu(&upid->pid_chain,
                          &pid_hash[pid_hashfn(upid->nr, upid->ns)]);
              }
              spin_unlock_irq(&pidmap_lock);

          out:
              return pid;

              ...
          }

          上面的代碼中,那個(gè) for (i = ns->level; i >= 0; i--) 就是通過 parent 成員不斷向上檢索為不同層級的 pid命名空間 分配一個(gè)唯一的pid號,并且保存到對應(yīng)的 nr 字段中。另外,還會把進(jìn)程所在各個(gè)層級的pid號添加到全局pid哈希表中,這樣做是為了通過pid號快速找到進(jìn)程。

          現(xiàn)在我們來看看怎么通過pid號快速找到一個(gè)進(jìn)程,在內(nèi)核中 find_get_pid() 函數(shù)用來通過pid號查找對應(yīng)的 struct pid 結(jié)構(gòu),代碼如下(find_get_pid() -> find_vpid() -> find_pid_ns()):

          struct pid *find_get_pid(pid_t nr)
          {
              struct pid *pid;

              rcu_read_lock();
              pid = get_pid(find_vpid(nr));
              rcu_read_unlock();

              return pid;
          }

          struct pid *find_vpid(int nr)
          {
              return find_pid_ns(nr, current->nsproxy->pid_ns);
          }

          struct pid *find_pid_ns(int nr, struct pid_namespace *ns)
          {
              struct hlist_node *elem;
              struct upid *pnr;

              hlist_for_each_entry_rcu(pnr, elem,
                      &pid_hash[pid_hashfn(nr, ns)], pid_chain)
                  if (pnr->nr == nr && pnr->ns == ns)
                      return container_of(pnr, struct pid,
                              numbers[ns->level]);

              return NULL;
          }

          通過pid號查找 struct pid 結(jié)構(gòu)時(shí),首先會把進(jìn)程pid號和當(dāng)前進(jìn)程的 pid命名空間 傳入到 find_pid_ns() 函數(shù),而在 find_pid_ns() 函數(shù)中通過全局pid哈希表來快速查找對應(yīng)的 struct pid 結(jié)構(gòu)。獲取到 struct pid 結(jié)構(gòu)后就可以很容易地獲取到進(jìn)程對應(yīng)的進(jìn)程描述符,例如可以通過 pid_task() 函數(shù)來獲取 struct pid 結(jié)構(gòu)對應(yīng)進(jìn)程描述符,由于代碼比較簡單,這里就不再分析了。


          瀏覽 299
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  男女福利视频 | 日韩一级操逼黄片 | 看黑人免费操逼 | 狼友精品在线观看 | 在线看黄片网站 |