Linux System Calls' Forum, #3:(第64號系統服務) sys_getppid

jollen 發表於 October 14, 2006 10:49 PM

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 的 field - 'group_leader' 是一個 pointer,指向 thread (process) group 的 leader;struct task_struct 的 field - 'real_parent' 是一個 pointer,指向 task 的 "real parent":
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