LoginSignup
24
16

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-07-03

fork()システムコールとプロセスの識別子について調べた。@詳解Linuxカーネル読書会
ソースコードのバージョンは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_leadertask_structが入るだけで伝わり方はほぼ同じだ。

  /* 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はこのあたり。

  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はこのあたり。

  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 | SIGCHLDclone_flagに入る。フラグの意味はman cloneにある。

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

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

#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
24
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
16