fork()の仕組みとプロセスの識別子(PID, PGID, TGID, TID, SID)

`fork()`システムコールとプロセスの識別子について調べた。@[詳解Linuxカーネル読書会](https://kernelreading.doorkeeper.jp/events/44499)
ソースコードのバージョンはlinuxカーネルはv4.7-rc4、glibcはb6084a958をみている。

`fork()`のおおまかな仕組みは、currentの`task_struct`を元に子プロセスの`task_struct`を作って、スケジューラに登録する、というものだ。

## プロセスに付与されるID

IDとは言っても、だいたい`pid_t`(=`int`)をインクリメントしてるだけなので、重複する可能性が無いというわけではない。カーネル内での呼び名と、ユーザ空間での呼び名が違う場合があるので結構ややこしい。
表はカーネルから見たIDでまとめた。

|ID|読み方|実装上の意味|
|---|---|---|
|PID|プロセスID|プロセスごとの識別子|
|TGID|スレッドグループID|スレッドごと、シングルスレッドの場合はPIDと同一、ユーザ空間にgetpid()で帰ってくるのはなぜかこっち。|
|PGID|プロセスグループID|グループリーダが持っているPGIDを継承|
|SID|セッションID|グループリーダが持っているSIDを継承|
|TID|スレッドID|gettidで取れるカーネルで管理されているもの（実はPID) or ユーザ空間で管理されているもの、ややこしい|

PIDとTGIDは、ここの図がわかりやすいので、引用する。
http://stackoverflow.com/questions/9305992/linux-threads-and-process

~~~
                      USER VIEW
 <-- PID 43 --> <----------------- PID 42 ----------------->
                     +---------+
                     | process |
                    _| pid=42  |_
                  _/ | tgid=42 | \_ (new thread) _
       _ (fork) _/   +---------+                  \
      /                                        +---------+
+---------+                                    | process |
| process |                                    | pid=44  |
| pid=43  |                                    | tgid=42 |
| tgid=43 |                                    +---------+
+---------+
 <-- PID 43 --> <--------- PID 42 --------> <--- PID 44 --->
                     KERNEL VIEW
~~~

`_do_fork()`の実装は次のようになっている。`CLONE_THREAD`の場合は、TGIDにコピー元のtgidをコピーし、それ以外の場合はpidと同じものがセットされる。fork()は後者である。
`group_leader`も`task_struct`が入るだけで伝わり方はほぼ同じだ。

~~~c
  /* ok, now we should be set up.. */
  p->pid = pid_nr(pid);
  if (clone_flags & CLONE_THREAD) {
    p->exit_signal = -1;
    p->group_leader = current->group_leader;
    p->tgid = current->tgid;
  } else {
    if (clone_flags & CLONE_PARENT)
      p->exit_signal = current->group_leader->exit_signal;
    else
      p->exit_signal = (clone_flags & CSIGNAL);
    p->group_leader = p;
    p->tgid = p->pid;
  }
~~~

PGID,SIDはこのあたり。

~~~c
  if (likely(p->pid)) {
    ptrace_init_task(p, (clone_flags & CLONE_PTRACE) || trace);

    init_task_pid(p, PIDTYPE_PID, pid);
    if (thread_group_leader(p)) {
      init_task_pid(p, PIDTYPE_PGID, task_pgrp(current));
      init_task_pid(p, PIDTYPE_SID, task_session(current));
~~~

ユーザ空間で決めたTIDはこのあたり。

~~~c
  p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? child_tidptr : NULL;
  /*
   * Clear TID on mm_release()?
   */
  p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? child_tidptr : NULL;
~~~

## 実はfork()システムコールそのものは使われていない

`fork()`はglibcでラップされていて、`clone()`システムコールで実装されている。

~~~
juntaki@ubuntu% cat forktest
ls; ls;
juntaki@ubuntu% strace -f bash forktest 2>&1 | grep "fork("
juntaki@ubuntu% strace -f bash forktest 2>&1 | grep "clone("
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f154401a9d0) = 26811
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f154401a9d0) = 26812
~~~

### glibc実装とカーネルのfork()

glibcのx86の実装では、`CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD`が`clone_flag`に入る。フラグの意味は`man clone`にある。


~~~c
#define ARCH_FORK() \
  INLINE_SYSCALL (clone, 4,                                                   \
                  CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD, 0,     \
                  NULL, &THREAD_SELF->tid)
~~~

Linuxカーネルでは、フラグはSIGCHLDだけ。

~~~c
#ifdef __ARCH_WANT_SYS_FORK
SYSCALL_DEFINE0(fork)
{
#ifdef CONFIG_MMU
  return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
#else
  /* can not support in nommu mode */
  return -EINVAL;
#endif
}
#endif
~~~