Linuxの親プロセス

## Unixのプロセス

UnixではプロセスはプロセスID (PID) という数字で管理している。プロセスには、必ず親プロセスがあり、そのプロセスを作ったプロセスのPID (Parent PID、PPIDと呼ぶ) がプロセスごとに記録されている[^1]。

[^1]: 多くのUnix (*BSDを含む) ではstruct procの中にp_ppidというメンバー変数があり、そこにPPIDが保存されている。Linuxではstruct task_structの中に、親プロセスのstruct task_structへのポインタがある。

Unixでプロセスを作るには、[fork(2)やvfork(2)](https://ja.wikipedia.org/wiki/Fork)というシステムコールを使う。Linuxには[clone(2)](http://manpages.ubuntu.com/manpages/bionic/ja/man2/clone.2.html)というシステムコールもあり、fork(2)やvfork(2)はそれぞれ特殊な引数をつけたclone(2)の呼び出しと実質的に等価である。

親プロセスの役割は、プロセスを看取ることである。プロセスがexit(2)を呼び出すと、そのプロセスは終了し、親プロセスにシグナルSIGCHLDが送られる。親プロセスは、wait(2)など (waitpid(2)とかwait4(2)のようなバリエーションがある) のシステムコールで子供の終了コードを取得することができる。原則として、プロセスの終了コードを取得する方法はこれだけである。余談だが、親がwait(2)などで看取ってくれないと、終了したプロセスの終了コードなどをカーネルが保持し続ける必要があり、このような状態をゾンビと呼ぶ。

子プロセスを持つプロセスが終了する (親プロセスが子プロセスより先に終了する) と、子プロセスの親はPID 1のプロセスにさし変わる。PID 1のプロセスは[init](https://ja.wikipedia.org/wiki/Init)と呼ばれ、カーネル初期化後に最初に起動されるプロセスである (伝統的にはカーネルが自ら起動する唯一のプロセスでもあった)。initは、各種デーモンやコンソールログインを司るgetty(8)などを起動する。現在のLinuxでは[systemd](https://systemd.io)がinitとして起動されるのが主流である。PID 1のプロセスが終了する (あるいは起動できない) と、カーネルは異常終了する。

以上は、Unixにおける通常の動作であって、現在のLinuxでは特殊な例が存在する。

## プロセスを作ったプロセスが親にならない例

clone(2)のflagとしてCLONE_PARENTを指定すると、生成される子プロセスの親プロセスは、clone(2)を発行したプロセスではなくその親プロセスとなる (親プロセスの情報が、親から子へコピーされる)。

```parenttest.c
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sched.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <sys/wait.h>

#define STACK (4096)

int
childfunc(void *arg)
{
	prctl(PR_SET_NAME, "parenttest:C");
	printf(":child... sleeping for 10sec.\n");
	sleep(10);
	printf(":child... sleep done; exiting.\n");
	return 128;
}	

int
main(void)
{
	int pid;
	void *childstack;

	printf("before clone; pid is %d\n", getpid());
	prctl(PR_SET_NAME, "parenttest:P");
	childstack = mmap(NULL, STACK,
			  PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
	if (childstack == MAP_FAILED) {
		perror("mmap");
		exit(EXIT_FAILURE);
	}

	pid = clone(childfunc, childstack+STACK, CLONE_PARENT|SIGCHLD, NULL);

	if (pid > 0) {
		int wstatus, r;
		
		printf("parent... child is %d.\n"
		       "waitpid ECHILD might be expected below\n", pid);
		r = waitpid(pid, &wstatus, 0);
		if (r < 0) {
			perror("waitpid");
			sleep(10);
			printf("parent... sleep done; exiting.\n");
		} else {
			printf("waitpid returns %d.\n"
			       " exit code %d.\n", r, WEXITSTATUS(wstatus));
		}
		return 0;
	} else if (pid == 0) {
		printf("??? clone(2) returned 0\n");
		exit(EXIT_FAILURE);
	} else {
		perror("clone");
		exit(EXIT_FAILURE);
	}
}
```

実行すると、PIDを表示した後clone(CLONE_PARENT)を発行する。clone()を発行したプロセス (parenttest:P) も、clone()で生成されたプロセス (parenttest:C) も、10秒後に終了するはずだが、その間にpstreeでプロセスを観察すると、シェルの子供が2ついることがわかる。

```
% ./parenttest &
[1] 999975
% before clone; pid is 999975
parent... child is 999976.
waitpid ECHILD might be expected below
waitpid: No child processes
:child... sleeping for 10sec.

% pstree -p $$
zsh(11686)─┬─parenttest:C(999976)
           ├─parenttest:P(999975)
           └─pstree(999979)
% parent... sleep done; exiting.
:child... sleep done; exiting.

[1]  + done       ./parenttest
```

clone()からCLONE_PARENTを外す (スタックとエントリポイントを除けばfork()と同等) と、

```
zsh(11686)─┬─parenttest:P(??????)───parenttest:C(??????)
           └─pstree(??????)
```

のようになり、parenttest:Pはwaitpid(2)によりparenttest:Cの終了コードも取れるはず。

CLONE_PARENTのとき、zshにとっては、parenttest:Cは知らないうちにできた子であるが、ちゃんと看取ってくれるようだ。シェルによっては誤動作するかもしれない。

## 親が先に終了した時に、祖父の子供になる例

prctl(2)のCHILD_SUBREAPERという機能を使うと、孫がある状態で、子供が先に終了した場合、親は孫の親プロセスとなる。

```subreapertest.c
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <sys/wait.h>

#define STACK (4096)

int
grandchildfunc(void *arg)
{
	prctl(PR_SET_NAME, "subreapertest:G");
	printf("::grandchild... sleeping 10sec.\n");
	sleep(10);
	printf("::grandchild... sleep done; exiting.\n");
	return 129;
}

int
childfunc(void *arg)
{
	int pid;
	void *childstack;

	prctl(PR_SET_NAME, "subreapertest:C");
	printf(":child... cloning\n");
	childstack = mmap(NULL, STACK,
			  PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
	if (childstack == MAP_FAILED) {
		perror("child mmap");
		exit(EXIT_FAILURE);
	}

	pid = clone(grandchildfunc, childstack+STACK, SIGCHLD, NULL);

	if (pid == 0) {
		printf(":??? clone(2) returned 0\n");
		exit(EXIT_FAILURE);
	} else if (pid < 0) {
		perror(":clone");
		exit(EXIT_FAILURE);
	}

	printf(":child: grandchild is %d, exiting.\n", pid);
	return 128;
}	

int
main(void)
{
	int pid;
	void *childstack;

	printf("before clone; pid is %d\n", getpid());
	prctl(PR_SET_NAME, "subreapertest:P");
	childstack = mmap(NULL, STACK,
			  PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
	if (childstack == MAP_FAILED) {
		perror("mmap");
		exit(EXIT_FAILURE);
	}
	if (prctl(PR_SET_CHILD_SUBREAPER, 0) < 0) {
		perror("prctl(PR_SET_CHILD_SUBREAPER)");
		exit(EXIT_FAILURE);
	}
	pid = clone(childfunc, childstack+STACK, SIGCHLD, NULL);

	if (pid > 0) {
		int child, r, wstatus;
		printf("parent... child is %d.\n"
		       " I might be a subreaper\n", pid);
		for ( ; ; ) {
			r = wait(&wstatus);
			if (r > 0) {
				printf("child %d exited with status %d\n",
				       r, WEXITSTATUS(wstatus));
			}
			else if (r < 0 && errno == ECHILD)
				break;
		}
		printf("parent: all child exited\n");
	} else if (pid == 0) {
		printf("??? clone(2) returned 0\n");
		exit(EXIT_FAILURE);
	} else {
		perror("clone");
		exit(EXIT_FAILURE);
	}

	return 0;
}
```

実行すると、子 (subreapertest:C) と孫 (subreapertest:G) が生えるが、subreapertest:Cはすぐに終了する。subreapertest:Gは10秒寝る。

```実行例
 ./subreapertest &
[1] 1003968
% before clone; pid is 1003968
parent... child is 1003969.
 I might be a subreaper
:child... cloning
:child: exiting.
child 1003969 exited with status 128
::grandchild... sleeping 10sec.

% pstree -p $$
zsh(11686)─┬─pstree(1003974)
           └─subreapertest:P(1003968)───subreapertest:G(1003970)
% ::grandchild... sleep done; exiting.
child 1003970 exited with status 129
parent: all child exited

[1]  + done       ./subreapertest
```

subreapertest:Cが終了した後、subreapertest:Pがsubreapertest:Gの親となっていて、終了コードも取得できることがわかる。

prctl(PR_SET_CHILD_SUBREAPER)の引数1を0にすると、subreapertest:Cが終了するとsubreapertest:GのPPIDは1 (init) になる。subreapertest:Pとsubreapertest:Gに親子関係はないため、subreapertest:Gがいてもすべての子供が終了したとしてsubreapertest:Pは終了する。

## 何に使うの?

何だかよくわからない機能ではあるが、[runc](https://github.com/opencontainers/runc/blob/v1.0.0-rc94/libcontainer/nsenter/nsexec.c) が駆使しているのは確認した。