linux核心PID管理

來源:互聯網
上載者:User

        PID即進程描述符在linux kernel中的分配和管理比較複雜。 本文分析了其相關資料結構以及函數。 (代碼基於v3.0.3)

和PID相關的資料結構有

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

其中 count是指向該資料結構的引用次數。

level是該pid在pid_namespace中處於第幾層。當level=0時表示是global namespace,即最高層。pid_namespace這個資料結構將在後面進行解釋。

tasks[PIDTYPE_MAX]數組中每個元素都代表了不同的含義。PIDTYPE_MAX表示pid所表示的類型的最大數。 該值定義在enum pid_type中

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

PIDTYPE_PID代表進程描述符(PID) 。 PIDTYPE_PGID代表一組進程描述符。 一組進程(process)可以組成一個群組,並且有一個組描述符。 這樣的好處是如果有一個訊號是針對這個組描述符,該群組內的所有進程都可以接受到。 PIDTYPE_SID是對組描述符再做一個群組,形成一個session。這是更高一個層次的抽象。

tasks[i]指向的是一個雜湊表。譬如說tasks[PIDTYPE_PID]指向的是PID的雜湊表。

rcu域我也沒有搞明白到底是做什麼的:(

numbers[1]域指向的是upid結構體。 numbers數組的本意是想表示不同的pid_namespace。 一個PID可以屬於不同的namespace, numbers[0]表示global namespace,numbers[i]表示第i層namespace,i越大所在層級越低。目前該數組只有一個元素, 即global namespace。所以namepace的概念雖然引入了pid,但是並未真正使用,在未來的版本可能會用到。

接下來我們再看看upid這個資料結構

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

pid結構體中的numbers域指向了upid結構體。該結構體中

nr是pid的值, 即 task_struct中 pid_t pid域的值。

ns指向該pid所處的namespace。

linux核心將所有進程的upid都存放在一個雜湊表中(pid_hash),以方便尋找和統一管理。因此,pid結構體中的numbers[0]指向的upid instance存放在pid_hash裡。通過pid_chain即雜湊表的節點就能夠找到該upid所在pid_hash中的位置。

接下來再看看pid_namespace結構體

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;};

kref表示指向pid_namespace的個數。

pidmap結構體表示分配pid的位元影像。當需要分配一個新的pid時只需尋找位元影像,找到bit為0的位置共置1,然後更新統計資料域(nr_free)。

struct pidmap {atomic_t nr_free;void *page;};

nr_free表示還能分配的pid的數量。

page指向的是存放pid的物理頁。

所以pidmap[PIDMAP_ENTRIES]域表示該pid_namespace下pid已指派情況。

last_pid用於pidmap的分配。指向最後一個分配的pid的位置。(不是特別確定)

child_reaper指向的是一個進程。 該進程的作用是當子進程結束時為其收屍(回收空間)。由於目前只支援global namespace,這裡child_reaper就指向init_task。

pid_cachep域指向分配pid的slab的地址。

level表示該namespace處於哪一層, 現在這裡顯然是0。

parent指向該namespace的父親namespace。 現在一定是NULL。

介紹完pid_namespace相關的資料結構,我們來看看設計它們的本意是什麼。 Linux中增加namespace這個概念的目的是為了虛擬化和方便管理。 比如在不同的namespace中可以有pid相同的進程。 pid_namespace的結構是層次化的。而且在child namespace中的進程一定會有parent namespace的映射。這句話可能不太好理解。可以結合下面這張圖

以為例子,此時pid_hash全域雜湊表中此時會存放15個(9+3+3)upid的instance。

前面介紹了這麼多關於pid, upid, pid_namespace的概念, 接下來我們再來看看它們和task_struct之間的關係

右下角的橢圓形虛線框是全域pid_hash,所有已指派的upid都會儲存在該hash表中。

左下角的橢圓形虛線框表示的是pid_namespace的關係。 當然目前只有一層。

Linux核心通過task_struct來管理進程。在task_struct中,和pid相關的域有

struct task_struct{...pid_t pid;pid_t tgid;struct task_struct *group_leader;struct pid_link pids[PIDTYPE_MAX];struct nsproxy *nsproxy;...};

pid指該進程的進程描述符。 後面會介紹在fork函數中如何對其進行賦值的。

tgid指該進程的線程描述符。在linux核心中對線程並沒有做特殊的處理,還是由task_struct來管理。所以從核心的角度看, 使用者態的線程本質上還是一個進程。對於同一個進程(使用者態角度)中不同的線程其tgid是相同的,但是pid各不相同。 主線程即group_leader(主線程會建立其他所有的子線程)。如果是單線程進程(使用者態角度),它的pid等於tgid。

對於使用者態程式來說,調用getpid()函數其實返回的是tgid。想想是為什嗎?:)

group_leader除了在多線程的模式下指向主線程,還有一個用處, 當一些進程組成一個群組時(PIDTYPE_PGID), 該域指向該群組的leader。

nsproxy指標指向namespace相關的域。

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 net           *net_ns;};

通過nsproxy域可以知道該task_struct屬於哪個pid_namespace, 當然現在一定是global namespace。(已經講了很多次了:))

其他一些域也是namespace相關,這裡就不展開解釋了。

pids[PIDTYPE_MAX]指向了和該task_struct相關的pid結構體。

pid_link的定義如下

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

在linux核心中如果想獲得該task_struct所對應的pid可以調用task_pid()函數, 這個函數的實現非常簡單

static inline struct pid *task_pid(struct task_struct *task){ return task->pids[PIDTYPE_PID].pid;}

自此我已將pid相關的資料結構介紹完了, 下面我們再看看和pid相關的使用。

(1)fork函數中如何分配一個新的pid?

fork(), vfork()還有clone()函數最終都是通過調用do_fork()來進行工作。 分配新的pid是在copy_process()函數實現的。 do_fork()函數會調用copy_process(), 它們之間的關係我會在以後的文章中進行介紹。

static struct task_struct *copy_process(unsigned long clone_flags,                                        unsigned long stack_start,                                        struct pt_regs *regs,                                        unsigned long stack_size,                                        int __user *child_tidptr,                                        struct pid *pid,                                        int trace){...if (pid != &init_struct_pid) {    retval = -ENOMEM;    pid = alloc_pid(p->nsproxy->pid_ns);    if (!pid)        goto bad_fork_cleanup_io;}p->pid = pid_nr(pid);p->tgid = p->pid;if (clone_flags & CLONE_THREAD)    p->tgid = current->tgid;...}

我只將和pid分配的代碼列出來了。

alloc_pid函數將分配一個新的pid struct。 簡單的說該函數的功能是在pidmap上找到一個未用的pid bit,如若找不著,著說明已經沒有可用的pid了,該namespace所在pid配給全部用完。 然後將其儲存到pid_hash的雜湊表裡,然後再將pid結構體返回。

pid_nr函數的實現也很簡單

static inline pid_t pid_nr(struct pid *pid) {         pid_t nr = 0;         if (pid)                 nr = pid->numbers[0].nr;         return nr; }

返回該pid所在global namespace的值。

後面幾行代碼用於區分進程和線程中tgid的值。

和pid相關的資料結構,函數定義可以在 include/linux/pid.h include/linux/pid_namespace.h 以及 kernel/pid.c kernel/pid_namespace.c中找到。

註:

(1)本文中如果發現任何錯誤請幫我指出。 非常感謝!

(2)歡迎和大家進行交流。

(3)本文系原創,如需轉載請標明出處。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.