UNIX進程總是會分配一個號碼用於在其命名空間總唯一地標識它們,該號碼稱作進程ID號,簡稱PID。
1、進程ID
但每個進程除了PID外,還有其他的ID,有下列幾種可能的類型:
(1)處於某個線程組中的所有進程都有統一的線程組ID(TGID)。若進程沒有使用線程,則其PID和TGID相同。線程組中主進程被稱作組長(group leader)。通過clone創建的所有線程的task_struct的group_leader成員,會指向組長task_struct實例。
(2)獨立進程可以合並為進程組(使用setpgrp系統調用)。進程組簡化了向組的所有成員發送信號的操作,有助於各種系統程序設計應用,用管道連接的進程包含在同一個進程組。
(3)幾個進程組可以合並為一個會話。會話中所有進程都有同樣的會話ID,保存在task_struct的session中。SID可通過setsid系統調用設置。用於終端程序設計。
2、全局ID和局部ID
PID Namespace使得父命名空間可以看見所有子命名空間PID,但子命名空間無法看到父命名空間的PID,這意味着某些進程具有多個PID,凡可以看到該進程的命名空間,都會為其分配一個PID,由此需要區分全局ID和局部ID:
(1)全局ID:在內核本身和初始命名空間的唯一ID號,在系統啟動期間開始的init進程即屬於初始命名空間。對每個ID類型,都有一個給定的全局ID,保證在整個系統中是唯一的。
(2)局部ID:屬於某個特定命名空間,不具備全局有效性。
3、數據結構
首先給出一個總圖,其中紅線是結構描述,黑線是指向。其中進程A,B,C是一個進程組的,A是組長進程,所以B和C的task_struct結構體中的pid_link成員的node字段就被鄰接到進程A對應的struct pid中的tasks[1]。
以下用ID指代提到的任何進程ID。
struct task_struct { ... pid_t pid; //全局PID pid_t tgid; //線程組ID struct task_struct *group_leader; //指向線程組組長task_struct實例 struct pid_link pids[PIDTYPE_MAX]; //PID和PID散列表的聯系,將所有共享同一ID的task_struct實例都按進程存儲在一個散列表中 ... };
其中PIDTYPE_MAX表示ID類型的數目,枚舉類型中定義的ID類型不包括線程組ID,因為線程組ID即為線程組組長的PID:
enum pid_type { PIDTYPE_PID, PIDTYPE_PGID, PIDTYPE_SID, PIDTYPE_MAX };
task_struct中的輔助結構pid_link可以將task_struct連接到表頭在struct pid中的散列表上:
struct pid_link { struct hlist_node node; //散列表元素 struct pid *pid; //指向進程所屬pid結構實例 };
為了在給定命名空間中查找對應於指定PID數值的pid結構實例,使用了一個散列表:static struct hlist_head *pid_hash; hlist_head是內核標准數據結構,用於建立雙鏈散列表。
假如已經分配了struct pid的一個新實例,並設置用於給定的ID類型。它會通過如下附加到task_struct(kernel/pid.c):
void attach_pid(struct task_struct *task, enum pid_type type, struct pid *pid) { struct pid_link *link; link = &task->pids[type]; link->pid = pid; hlist_add_head_rcu(&link->node, &pid->tasks[type]); }
這里建立雙向鏈表:task_struct可以通過task_struct->pids[type]->pid訪問pid實例;而從pid實例開始,可以遍歷task[type]散列表找到task_struct。hlist_add_head_rcu是遍歷散列表的標准函數。
PID的管理圍繞兩個數據結構:struct pid是內核對PID的內部表示,struct upid表示特定的命名空間中可見的信息:
struct pid { //內核對PID的內部表示 atomic_t count; //引用計數 unsigned int level; //這個pid所在的層級 /* 使用該pid的進程的列表 */ struct hlist_head tasks[PIDTYPE_MAX]; //每個數組項都是一個散列表表頭,對應一個ID類型。因為一個ID可能用於幾個進程,所有共享同一給定ID的task_struct實例,都通過該列表連接起來 struct rcu_head rcu; struct upid numbers[1]; //這個pid對應的命名空間,一個pid不僅要包含當前的pid,還要包含父命名空間,默認大小為1,所以就處於根命名空間中,可添加附加項擴充 };
struct upid { //包裝命名空間所抽象出來的一個結構體 /* Try to keep pid_chain in the same cacheline as nr for find_vpid */ int nr; //pid在該命名空間中的pid數值 struct pid_namespace *ns; //對應的命名空間 struct hlist_node pid_chain; //通過pidhash將一個pid對應的所有的命名空間連接起來(所有upid實例被保存在一個散列表中) };
struct pid_namespace { struct kref kref; struct pidmap pidmap[PIDMAP_ENTRIES]; //一個pid命名空間應該有其獨立的pidmap int last_pid; //上次分配的pid unsigned int nr_hashed; struct task_struct *child_reaper; //每個PID命名空間都需要一個作用相當於全局init進程的進程,init的一個目的是對孤兒進程調用wait4,此保存了指向該進程的task_struct的指針 struct kmem_cache *pid_cachep; unsigned int level; //所在的命名空間層次,初始為0,子空間level為1。level較高命名空間的PID對level較低的命名空間的PID是可見的,從給定level設置,可知進程會關聯多少個PID struct pid_namespace *parent; //指向父命名空間,構建命名空間的層次關系 ... };
4、函數操作
本質上內核需要完成兩個任務:
(1)給出局部數字ID和對應命名空間,查找此二元組描述的task_struct。
(2)給出task_struct、ID類型、命名空間,取得命名空間局部數組ID。
對於(1)分解為兩步:
a.由局部PID和ns,確定pid實例。內核采用標准散列方式,首先,根據PID和ns指針計算在pid_hash數組中索引,然后遍歷散列表直至找到所要的upid實例,而由於這些實例直接包含在struct pid中,所以通過使用container_of機制可推斷出pid實例(kernel/pid.c):
struct pid *find_pid_ns(int nr, struct pid_namespace *ns) { struct upid *pnr; hlist_for_each_entry_rcu(pnr, &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; }
b.pid_task取出pid->task[type]散列表中的第一個task_struct實例(kernel/pid.c):
struct task_struct *find_task_by_pid_ns(pid_t nr, struct pid_namespace *ns) { rcu_lockdep_assert(rcu_read_lock_held(), "find_task_by_pid_ns() needs rcu_read_lock()" " protection"); return pid_task(find_pid_ns(nr, ns), PIDTYPE_PID); }
對於(2)也分為兩步:
a.獲得與task_struct關聯的pid實例:
static inline struct pid *task_pid(struct task_struct *task) { return task->pids[PIDTYPE_PID].pid; }
還可通過task_tgid、task_pgrp和task_session分別用於取得不同類型的ID:
static inline struct pid *task_tgid(struct task_struct *task) { return task->group_leader->pids[PIDTYPE_PID].pid; }
static inline struct pid *task_pgrp(struct task_struct *task) { return task->group_leader->pids[PIDTYPE_PGID].pid; }
b.從struct pid的numbers數組中upid信息,即可獲得數字ID:
pid_t pid_nr_ns(struct pid *pid, struct pid_namespace *ns) { struct upid *upid; pid_t nr = 0; if (pid && ns->level <= pid->level) { //由於父ns可以看到子ns的PID,反過來不行,內核必須確保當前ns的level小於或等於產生局部ID的ns的level upid = &pid->numbers[ns->level]; if (upid->ns == ns) nr = upid->nr; } return nr; }
以下函數用於返回該ID所屬的ns所看到的局部PID:
pid_t pid_vnr(struct pid *pid) { return pid_nr_ns(pid, task_active_pid_ns(current)); }
5、生成唯一PID
在建立一個新進程時,進程可能在多個ns中可見,對每個這樣的ns,都需要生成一個局部ID:
struct pid *alloc_pid(struct pid_namespace *ns) { //pid分配要依賴與pid namespace,也就是說這個pid是屬於哪個pid namespace 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); //分配一個pid結構 if (!pid) goto out; tmp = ns; pid->level = ns->level; //初始化level for (i = ns->level; i >= 0; i--) { //遞歸到上面的層級進行pid的分配和初始化 nr = alloc_pidmap(tmp); //從當前pid namespace開始直到全局pid namespace,每一個層級都分配一個pid if (nr < 0) goto out_free; pid->numbers[i].nr = nr; //初始化upid結構 pid->numbers[i].ns = tmp; tmp = tmp->parent; //遞歸到父親pid namespace } if (unlikely(is_child_reaper(pid))) { //如果是init進程需要做一些設定,為其准備proc目錄 if (pid_ns_prepare_proc(ns)) goto out_free; } get_pid_ns(ns); atomic_set(&pid->count, 1); for (type = 0; type < PIDTYPE_MAX; ++type) //初始化pid中的hlist結構 INIT_HLIST_HEAD(&pid->tasks[type]); upid = pid->numbers + ns->level; //定位到當前namespace的upid結構 spin_lock_irq(&pidmap_lock); if (!(ns->nr_hashed & PIDNS_HASH_ADDING)) goto out_unlock; for ( ; upid >= pid->numbers; --upid) { hlist_add_head_rcu(&upid->pid_chain, &pid_hash[pid_hashfn(upid->nr, upid->ns)]); //建立pid_hash,讓pid和pid namespace關聯起來 upid->ns->nr_hashed++; } spin_unlock_irq(&pidmap_lock); out: return pid; out_unlock: spin_unlock_irq(&pidmap_lock); out_free: while (++i <= ns->level) free_pidmap(pid->numbers + i); kmem_cache_free(ns->pid_cachep, pid); pid = NULL; goto out; }
參考:
linux-3.10.1內核源碼
《深入Linux內核架構》
https://blog.csdn.net/zhangyifei216/article/details/49926459