sys_getppid 是很有趣的一個實作常式。
當 process fork 新的 process 後,便成為該 process 的 parent process,當然,新的 process 便成為 child process。在 UNIX 的 multithreaded 程式設計的理論中,process 之間的關係是相當重要的,比如說,只有 related process (e.g. parent process v.s. child process) 可以透過 pipe 的機制來交換資料。
process 呼叫 fork() 函數(更正確的說法是 fork system call 的 wrapper function)後,fork() 便傳回 child process 的 PID;child process 則可以透過 getppid() system call 來取得 parent process 的 PID。
64 | sys_getppid | linux/kernel/timer.c |
類別:Kernel Timer & Process 原型宣告:long sys_getppid(void); 用途說明:取得 parent process 的 PID。 |
||
Kernel (2.6.11 or above) 實作:/* * Accessing ->group_leader->real_parent is not SMP-safe, it could * change from under us. However, rather than getting any lock * we can use an optimistic algorithm: get the parent * pid, and go back and check that the parent is still * the same. If it has changed (which is extremely unlikely * indeed), we just try again.. * * NOTE! This depends on the fact that even if we _do_ * get an old value of "parent", we can happily dereference * the pointer (it was and remains a dereferencable kernel pointer * no matter what): we just can't necessarily trust the result * until we know that the parent pointer is valid. * * NOTE2: ->group_leader never changes from under us. */ asmlinkage long sys_getppid(void) { int pid; struct task_struct *me = current; struct task_struct *parent; parent = me->group_leader->real_parent; for (;;) { pid = parent->tgid; #if defined(CONFIG_SMP) || defined(CONFIG_PREEMPT) { struct task_struct *old = parent; /* * Make sure we read the pid before re-reading the * parent pointer: */ smp_rmb(); parent = me->group_leader->real_parent; if (old != parent) continue; } #endif break; } return pid; } |
Jollen 的說明
Linux kernel 內部的 getppid 實作常式 (routine) - sys_getppid 的程式碼並不難懂, 在這裡我們先忽略 SMP 與 preemptive scheduling 的議題,因此有一段落的程式碼我們故意視而不見,讓我們來討論剩下的程式碼:
asmlinkage long sys_getppid(void) { int pid; struct task_struct *me = current; struct task_struct *parent; parent = me->group_leader->real_parent; for (;;) { pid = parent->tgid; #if defined(CONFIG_SMP) || defined(CONFIG_PREEMPT) { ... } #endif break; } return pid; } |
struct task_struct { ... /* real parent process (when being debugged) */ struct task_struct *real_parent; struct task_struct *parent; /* parent process */ ... /* threadgroup leader */ struct task_struct *group_leader; ... }
real_parent 與 parent
在 struct task_struct 裡,我們發現一件有趣的事:怎麼會有 real_parent 與 parent 二個 field 呢?
我們姑且不去說明 parent 的用義,real_parent 指的是建立(create)此 process(task)的 process,當建立該 process 的 process(real_parent)已經不存在時(eg. terminate),real_parent 便指向 init process。
好囉,我們知道一件重要的事情了,要找到「我的爸爸」 kernel code 寫法是:
struct task_struct *me = current; /* 我自己 */
struct task_struct *parent; /* 我爸 */
parent = me->real_parent; /* 我真正的爸(生下我的)*/
但這裡的寫法並不正確。讓我們繼續往下討論。
Group Leader
每一個 process 都是某些 process group 的成員,所有的 process group 都有一個帶班的 process(group leader)。比如說,當 GNU/Linux 系統開機時,init process 就是所有 process 的 group leader。
因此 process 的 parent process 應該是「group leader 的 real parent」,所以我們要先索引到 process descriptor 的 group_leader 欄位,然後再索引到 group leader 的 real_parent。
TIP 借用一下作業系統的說法:struct task_struct 就是 process 的 process descriptor。 |
所以,正確的 kernel code 寫法是:
struct task_struct *me = current; /* 我自己 */
struct task_struct *parent; /* 我爸 */
parent = me->group_leader->real_parent; /* 領班的真正爸,才是我的爸 = = " */
這樣就能理解 Linux 2.6 的 sys_getppid 實作了,雖然有一點不太直覺,不過看起來是頗有學問的設計。知道怎麼找到 parent process 後,再由 process descriptor 裡把 PID 拿出來就行了:
parent->tgid;
作業系統還真是有趣。
--jollen
作者/陳俊宏
http://www.jollen.org
Jollen's Blog 使用 Github issues 與讀者交流討論。請點擊上方的文章專屬 issue,或 open a new issue
您可透過電子郵件 jollen@jollen.org,或是 Linkedin 與我連絡。更歡迎使用微信,請搜尋 WeChat ID:jollentw