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_leader
もtask_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 | SIGCHLD
がclone_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