女人自慰AV免费观看内涵网,日韩国产剧情在线观看网址,神马电影网特片网,最新一级电影欧美,在线观看亚洲欧美日韩,黄色视频在线播放免费观看,ABO涨奶期羡澄,第一导航fulione,美女主播操b

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

如何組織PID命名空間的各種ID?PID命名空間基本概念簡析

冬至子 ? 來源:技術基本功修煉 ? 作者:郭玲 ? 2023-07-18 14:59 ? 次閱讀

Linux 支持以下命名空間類型:

  • Mount (CLONE_NEWNS;2.4.19,2002)
  • UTS (CLONE_NEWUTS; 2.6.19,2006)
  • IPC (CLONE_NEWIPC; 2.6.19,2006)
  • PID (CLONE_NEWPID; 2.6.24,2008)
  • Network(CLONE_NEWNET;2.6.29,2009)
  • User (CLONE_NEWUSER;3.8,2013)
  • Cgroup(CLONE_NEWCGROUP;4.6,2016)

命名空間 API 由三個系統調用(clone()、unshare()和setns())以及許多/proc文件組成。CLONE_NEW* 常量包括:

CLONE_NEWIPC,CLONE_NEWNS , CLONE_NEWNET , CLONE_NEWPID ,CLONE_NEWUSERCLONE_NEWUTS

int clone(int (*child_func)(void *), void *child_stack, int flags, void *arg);

有二十多個不同的CLONE_*標志 控制clone()操作的各個方面,包括父進程和子進程是否共享資源,例如虛擬內存、打開的文件描述符和信號配置。

如果在調用中指定了CLONE_NEW* 之一,則會創建相應類型的 新命名空間 ,并且新進程將成為該****命名空間的成員;可以在flags中指定多個 CLONE_NEW* 。

在本文中,我們將研究 clone系統調用的 PID 命名空間部分,以及內核如何組織 PID 命名空間的各種ID。本文分析基于內核版本 linux-5.15.60。

一、PID命名空間基本概念

PID命名空間隔離的全局資源是“進程ID編號”空間。這意味著“不同PID命名空間”中的進程可以具有“相同的進程ID”。PID命名空間用于“在主機系統之間遷移的容器”,同時保持容器內部進程的相同進程ID。

與傳統Linux(或UNIX)系統上的進程一樣,在PID命名空間中的進程ID是唯一的,并且從 PID 1開始按順序分配。同樣地,與傳統Linux系統一樣,PID 1——init進程是特殊的:它是在命名空間內創建的第一個進程,并且在命名空間內執行某些管理任務。

通過調用帶有 CLONE_NEWPID 標志的clone()函數可以“創建一個新的PID命名空間”。我們將展示一個簡單的示例程序,使用clone()函數創建一個新的PID命名空間,并使用該程序來解釋PID命名空間的一些基本概念。

主程序使用clone()函數創建一個新的PID命名空間,并顯示生成子進程的PID:

child_pid = clone(childFunc,
              child_stack + STACK_SIZE,   /* Points to start of downwardly growing stack */
              CLONE_NEWPID | SIGCHLD, argv[1]);
printf("PID returned by clone(): %ldn", (long) child_pid);

新創建的子進程在childFunc()中開始執行,該函數接收clone()調用的最后一個參數(argv[1])作為它的參數。這個參數后面再解釋。childFunc()函數顯示由clone()創建的子進程的進程ID和父進程ID,并最后執行標準的sleep程序:

printf("childFunc(): PID = %ldn", (long) getpid());
printf("ChildFunc(): PPID = %ldn", (long) getppid()); 
   ...
execlp("sleep", "sleep", "1000", (char *) NULL);

當我們運行這個程序時,輸出的前幾行如下:

[root@haha demo]# ./pidns_init_sleep /proc30
PID returned by clone(): 25070
childFunc(): PID = 1
childFunc(): PPID = 0
Mounting
procfs at /proc30

前兩行輸出顯示了從兩個不同PID命名空間的角度來看子進程的PID:調用clone()的“調用者的命名空間”和“子進程所在的命名空間”。

換句話說,子進程有兩個PID:在父命名空間中為 25070,在clone()調用創建的新PID命名空間中為1。下一行輸出顯示了子進程在所在PID命名空間中的父進程ID(即getppid()返回的值)。

父進程PID為0,展示了PID命名空間操作的一個小特殊情況。

正如我們后面詳細介紹的那樣,PID命名空間形成了一個層次結構:一個進程只能看到“自己所在的PID命名空間”和 嵌套在該PID命名空間下的“子命名空間中”的進程。

由于由clone()“創建的子進程的父進程”處于不同的命名空間中,子進程無法“看到”父進程;因此,getppid()將父進程PID報告為零。

要解釋pidns_init_sleep的最后一行輸出,我們需要回到一個我們在討論childFunc()函數實現時跳過的代碼片段。

在Linux系統上,每個進程都有一個特殊的目錄路徑"/proc/PID",其中PID表示進程的ID。這個目錄包含了描述該進程的虛擬文件。

這個機制被稱為PID命名空間模型。在一個PID命名空間中,只有屬于該命名空間或其子命名空間的進程的信息會顯示在對應的"/proc/PID"目錄中。

[root@haha linux-5.15.60]# mount |grep "proc on /proc"
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
proc on /proc2 type proc (rw,relatime)
proc on /proc2 type proc (rw,relatime)
proc on /proc10 type proc (rw,relatime)
proc on /proc20 type proc (rw,relatime)
proc on /proc30 type proc (rw,relatime)
[root@haha linux-5.15.60]#

但是,要使與PID命名空間對應的"/proc/PID"目錄可見,需要將proc文件系統掛載到該PID命名空間。我們可以在一個PID命名空間內的shell中,運行 mount命令來實現:

mount -t proc proc /mount_point

另外,也可以使用mount()系統調用來掛載procfs,我們程序的childFunc()函數就是這樣的:

char *mount_point = arg;
  if (mount_point != NULL) {
      mkdir(mount_point, 0555);       /* Create directory for mount point */
      if (mount("proc", mount_point, "proc", 0,NULL) == -1)
          errExit("mount");
      printf("Mounting procfs at %sn", mount_point);
   }

在我們的shell會話中,在/proc上掛載的procfs將顯示父PID命名空間中可見的進程的PID子目錄,而在/proc30 上掛載的procfs將顯示駐留在子PID命名空間中的進程的PID子目錄。

讓我們回到運行pidns_init_sleep的shell會話。我們停止程序并使用ps命令在父命名空間的上下文中檢查父進程和子進程的一些細節。

圖片

上述輸出的最后一行中的"PPID"值(25069)顯示“執行sleep的進程”的父進程是執行pidns_init_sleep的進程。

通過使用readlink命令來顯示/proc/PID/ns/pid符號鏈接,我們可以看到這兩個進程位于不同的PID命名空間中:

[root@haha demo]# readlink /proc/25069/ns/pid
pid:[4026531836]
[root@haha demo]# readlink /proc/25070/ns/pid
pid:[4026537948]
[root@haha demo]#

此時,我們還可以使用新掛載的procfs來獲取有關新PID命名空間中進程的信息,從該命名空間的角度來看。首先,我們可以使用以下命令獲取該命名空間中的PID列表:

[root@haha demo]# ls -d /proc30/[1-9]*
/proc30/1

如上所示,PID命名空間只包含一個進程,其PID(在該命名空間內)為1。我們還可以使用/proc/PID/status文件作為另一種方法,獲取關于該進程的一些相同信息,就像我們之前在shell會話中看到的那樣:

[root@haha demo]# cat /proc30/1/status | egrep '^(Name|PP*id)'
Name: sleep
Pid:   1
PPid:  0
[root@haha
demo]#

文件中的PPid字段為0,與getppid()報告子進程的父進程ID為0的事實相匹配。(子命名空間看不到父命名空間的進程)

二、嵌套的PID命名空間

如前所述,PID(進程標識符)命名空間以父子關系的層級嵌套方式存在。在一個PID命名空間內,可以看到同一命名空間中的所有其他進程,以及屬于后代命名空間的所有進程。

在這里,“看到”意味著能夠進行基于特定PID的系統調用(例如,使用kill()向進程發送信號)。子PID命名空間中的進程無法看到僅存在于父PID命名空間(或更遠的祖先命名空間)中的進程。

一個進程在PID命名空間層級中的每一層都會有一個PID,從其所在的PID命名空間一直到根PID命名空間。調用getpid()始終報告與進程所在命名空間相關聯的PID。

我們可以使用這里顯示的程序(multi_pidns.c)來展示進程在每個可見的命名空間中具有不同的PID。為簡潔起見,我們將簡單地解釋程序的功能,而不是逐行解析其代碼。

該程序以嵌套PID命名空間中的子進程遞歸方式創建一系列子進程。在調用程序時指定的命令行參數確定要創建多少個子進程和PID命名空間:

./multi_pidns 5

除了創建一個新的子進程,每個遞歸步驟還在一個唯一命名的掛載點上掛載procfs文件系統。在遞歸的最后,最后一個子進程執行了sleep程序。上述命令行輸出如下:

[root@haha demo]# ls -d /proc4/[1-9]* 
/proc4/1  /proc4/2  /proc4/3  /proc4/4  /proc4/5
[root@haha demo]# ls -d /proc3/[1-9]* 
/proc3/1  /proc3/2  /proc3/3  /proc3/4
[root@haha demo]# ls -d /proc2/[1-9]* 
/proc2/1  /proc2/2  /proc2/3
[root@haha demo]# ls -d /proc1/[1-9]* 
/proc1/1  /proc1/2
[root@haha demo]# ls -d /proc0/[1-9]* 
/proc0/1

查看每個procfs中的PID,我們可以看到每個連續的procfs "級別"包含的PID越來越少,這也表示了每個PID命名空間只顯示屬于該PID命名空間或其后代命名空間的進程。

讓我們看下在所有可見的命名空間中,遞歸結束時的PID:

[root@haha demo]# grep -H 'Name:.*sleep'/proc?/[1-9]*/status
/proc0/1/status:Name:   sleep
/proc1/2/status:Name:   sleep
/proc2/3/status:Name:   sleep
/proc3/4/status:Name:   sleep
/proc4/5/status:Name:   sleep
[root@haha demo]#

換句話說,在最深層嵌套的 PID 命名空間 ( /proc0 ) 中,執行sleep的進程的 PID 為 1,而在創建的最頂層 PID 命名空間 ( /proc4 ) 中,該進程的 PID 為 5。

三、內核實現PID命名空間

要了解內核如何組織和管理進程ID,首先要知道進程ID 的類型:

內核中進程ID 的類型用 pid_type 來描述,它定義在 includelinuxpid.h 中

enum pid_type {
  PIDTYPE_PID,
  PIDTYPE_TGID,
  PIDTYPE_PGID,
  PIDTYPE_SID,
  PIDTYPE_MAX,
};
  • PID 是內核唯一區分每個進程的ID。使用 fork 或 clone 系統調用時生成的進程將被內核分配一個新的唯一 PID 值。
  • TGID 是線程組ID。在一個進程中,如果使用 clone_THREAD 標志來調用 clone創建的進程,那么它就是該進程的一個線程(即輕量級進程,Linux沒有嚴格的進程概念),它們在一個線程組中。同一線程組中所有進程都有相同的TGID,但由于是不同的進程,所以它們的PID不同;線程的領導者(也稱為主線程)的TGID 與其 PID 相同。
  • PGID 獨立進程可以組成進程組(使用 setpgrp 系統調用),進程組可以簡化向組內所有進程發送信號的操作。例如,通過管道連接的連接屬于同一個進程組。進程組ID 稱為 PGID。進程組中所有的進程都有相同的 PGID,等于組長的 PID。
  • SID 可以將多個進程組組成一個會話組(使用 setsid 系統調用),可用于終端編程。會話組中所有進程都有相同的SID,該SID 存儲在 task_struct 的 session 成員中。

PID命名空間的層級關系如下:有 4 個命名空間。父命名空間派生兩個子命名空間,其中一個子命名空間派生另一個子命名空間。

圖片

由于每個命名空間是相互隔離的,所以每個命名空間可以有一個 PID 為1的進程。由于命名空間的層次性,父命名空間是知道子命名空間的存在的,所以子命名空間需要映射到父命名空間,

因此上圖中 第 1 級 的兩個兩個子命名空間中的 6 個進程 都映射到 其父命名空間的 PID 號 5~ 10.

系統使用 struct task_struct 表示一個進程,進程中存儲了全局ID 和 本地ID。

全局ID ---- 內核本身和初始命名空間中的唯一ID。 系統啟動時 init 進程屬于初始命名空間。全局ID 包括 pid_t pid 和 pid_t tgid 。默認情況下 pid_t 用 int 表示。

本地ID ---- 對于一個特定的命名空間來說,它在其命名空間中分配的ID就是本地ID。本地ID 用 struct pid * thread_pid 表示。

圖片

PID 數據結構

成員 tasks 是一個數組,每個數組項是一個哈希表頭,對應一個ID 類型,因此一個ID 可用于多個進程(比如多個進程的進程組相同)。

struct upid {
  int nr;// ID 的具體值
  struct pid_namespace* ns;
};
struct pid {
  refcount_t count;// 引用數, 一個PID 可能用于多個進程
  unsigned int level;
  spinlock_t lock;
   /* lists of tasks that use this pid */
  struct hlist_head tasks[PIDTYPE_MAX];
  struct hlist_head inodes;
   /* wait queue for pidfd notifications */
  wait_queue_head_twait_pidfd;
  struct rcu_head rcu;
   struct upid numbers[1]; // 柔性數組,特定命名空間可見的信息, 數組大小為level
};

PID 命名空間結構

struct pid_namespace {
  struct idr idr;
  struct rcu_head rcu;
  unsigned int pid_allocated; // 已分配多少個pid
  struct task_struct* child_reaper; // 指向當前命名空間的 init 進程,每個命名空間都有一個相當于全局init進程的進程
  struct kmem_cache* pid_cachep; // 指向分配pid 的slab地址
  unsigned int level;// 當前命名空間的級別。初始命名空間的級別為0,其子命名空間級別為1,依次遞增。
  struct pid_namespace* parent; // 指向父命名空間
#ifdefCONFIG_BSD_PROCESS_ACCT
  struct fs_pin* bacct;
#endif
  struct user_namespace* user_ns;
  struct ucounts* ucounts;
  int reboot;/* group exit code if this pidns was rebooted */
  struct ns_common ns;
} __randomize_layout;

假設一個進程組中有A、B 兩個進程,且進程組組長為A,進程A 是在 2 級命名空間中創建的,它的pid為45 ,映射到1級命名空間,分配給它的pid為123;然后它被映射到級別 0 的命名空間,分配給它的 pid 是 27760。

進程A 創建了一個線程 A1, 那么 A, A1, B 的命名空間和進程的關系如下圖所示:

  • 進程 A 的成員 struct pid* thread_pid 是內核對進程標識符的內部表示方式。
  • struct pid 以哈希鏈表的方式存儲,可以通過數字pid值快速找到它和它所引用的進程。
  • struct pid 保存了 嵌套的多個命名空間的指針 和 進程在此命名空間的進程標識符 nr。
  • 命名空間使用基數樹保存當前命名空間的 所有 struct pid,基數樹的索引就是 進程在此命名空間的進程標識符。

圖片

最后有個問題:如何通過PID 快速找到 task_struct?

內核代碼通過 find_task_by_vpid 來實現這個功能,其實通過上面這張圖就可以得出結論,簡單的步驟如下:

首先,通過 pid 和 命名空間nr,在基數樹上找到對應的 struct pid;

然后,通過 pid_type 在 struct pid 找到對應的節點struct hlist_node;

最后,根據內核的 container_of 機制 和 struct hlist_node 可以找到 struct task_struct 結構體。

struct task_struct* find_task_by_vpid(pid_t vnr) {
  return find_task_by_pid_ns(vnr,task_active_pid_ns(current));
}


struct task_struct* find_task_by_pid_ns(pid_t nr, struct pid_namespace* ns) {
  RCU_LOCKDEP_WARN(!rcu_read_lock_held(), "find_task_by_pid_ns() needs rcu_read_lock() protection");
  return pid_task(find_pid_ns(nr, ns),PIDTYPE_PID);
}


struct pid* find_pid_ns(int nr, struct pid_namespace* ns) {
  return idr_find(&ns- >idr, nr);
}


struct task_struct* pid_task(struct pid* pid, enum pid_type type) {
  struct task_struct* result = NULL;
  if (pid)
{
      structhlist_node* first;
      first = rcu_dereference_check(hlist_first_rcu(&pid- >tasks[type]),
          lockdep_tasklist_lock_is_held());
      if (first)
          result =hlist_entry(first, struct task_struct, pid_links[(type)]);
   }
  return result;
}
#define hlist_entry(ptr, type, member) container_of(ptr,type,member)
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 存儲器
    +關注

    關注

    38

    文章

    7636

    瀏覽量

    166449
  • Linux系統
    +關注

    關注

    4

    文章

    603

    瀏覽量

    28321
  • PID控制
    +關注

    關注

    10

    文章

    461

    瀏覽量

    41030
收藏 人收藏

    評論

    相關推薦
    熱點推薦

    鴻蒙TypeScript學習第19天【命名空間

    命名空間一個最明確的目的就是解決重名問題。
    的頭像 發表于 04-17 15:43 ?1246次閱讀
    鴻蒙TypeScript學習第19天【<b class='flag-5'>命名</b><b class='flag-5'>空間</b>】

    C++筆記008:C++命名空間 namespace的作用和使用解析

    。因此引入命名空間(namespace)這個概念,專門用于解決上面的問題,就像在“A”這個名字前面加上額外的附加信息一樣(額外的附加信息…..這句是不是病句),命名
    發表于 08-11 12:30

    Linux的命名空間機制

    Linux命名空間概述
    發表于 03-18 14:40

    命名空間的實現

    (1) 在用fork或clone系統調用創建新進程時,有特定的選項可以控制是與父進程共享命名空間,還是建立新的命名空間。(2) unshare系統調用將進程的某些部分從父進程分離,其中
    發表于 05-24 06:21

    hbase shell創建命名空間

    一.hbase shell創建命名空間hbase shellcreate_namespace "gofish"二.python實現hbase增刪改查# -*- coding
    發表于 07-28 06:45

    python常規包與命名空間

    python常規包與命名空間包1. 常規包在 Python 3.3 之前或者說 Python 2 中,一個包想要被導入使用,那么該包內必須要有 __init__.py 文件,這個文件是 Python
    發表于 03-11 15:46

    nvs_open和nvs_get從不存在的命名空間中工作會有何影響?

    我有一些設備在它們的 nvs 存儲中有不同的命名空間和分區來存儲不同的值,但共享相同的代碼。在我的代碼中,當特定命名空間不存在時,我已經做了例外處理:代碼:esp_err_t ret
    發表于 03-01 09:03

    nvs_open和nvs_get從不存在的命名空間中工作是怎么回事?

    我有一些設備在它們的 nvs 存儲中有不同的命名空間和分區來存儲不同的值,但共享相同的代碼。在我的代碼中,當特定命名空間不存在時,我已經做了例外處理:esp_err_t ret = n
    發表于 04-14 06:30

    模糊PID控制和空間矢量調制的通用變頻器設計

    模糊PID控制和空間矢量調制的通用變頻器設計
    發表于 04-13 15:42 ?27次下載

    集群模式_Data_ONTAP_中的命名空間

    集群模式_Data_ONTAP_中的命名空間
    發表于 12-28 11:17 ?0次下載

    C++中命名空間的幾大用法

    譯者注:可能很多程序員對C++已經非常熟悉,但是對命名空間經常使用到的地方還不是很明白,這篇文章就針對命名空間這一塊做了一個敘述。 命名
    發表于 09-28 18:31 ?0次下載

    基于PID調節相關的15個基本概念詳解

    PID入門讀此文,必須熟透于心的15個PID基本概念
    的頭像 發表于 01-08 09:14 ?6915次閱讀
    基于<b class='flag-5'>PID</b>調節相關的15個<b class='flag-5'>基本概念</b>詳解

    關于工業控制PID系統中的十五個基本概念

    PID調節系統PID功能由PID調節器或DCS系統內部功能程序模塊實現,了解與PID調節相關的一些基本概念,有助于
    發表于 11-13 14:21 ?2064次閱讀

    一文了解C++的命名空間

    在C++中,變量、函數和類都是大量存在的,這些變量、函數和類的名稱將都存在于全局命名空間中,會導致很多沖突, 使用命名空間的目的是對標識符的名稱進行本地化,以避免
    的頭像 發表于 06-29 14:48 ?2485次閱讀
    一文了解C++的<b class='flag-5'>命名</b><b class='flag-5'>空間</b>

    PID剛入門?新手必看的15個PID基本概念

    PID調節系統PID功能由PID調節器或DCS系統內部功能程序模塊實現,了解與PID調節相關的一些基本概念,有助于
    的頭像 發表于 09-25 19:40 ?2789次閱讀
    <b class='flag-5'>PID</b>剛入門?新手必看的15個<b class='flag-5'>PID</b><b class='flag-5'>基本概念</b>!