vfork
Rev.3を表示中。最新版はこちら。
vforkと言うのがある。たぶん今となっては使うことはないのでは・・・(実務は良くわかりませんが。)これは子プロセスが終了/execするまで実質、親プロセスは動作しない。以下はその違いを検証するのサンプルで、vforkの場合、子プロセスが終了するまで、親プロセスは動作していないことが見て取れる。なお、forkの場合は親プロセスがまず動作して、スケジューラにより、子プロセスも起動している。なお、vforkでは、スレッドのようにstatic変数aを共有している。ただしスレッドのように他のリソースは共有しない。これは共有することが目的でなく、vforkの目的上、共有せざるおえないからである。
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <err.h> int a = 0; int main() { pid_t pid; int i; pid = vfork(); // pid = fork(); if (pid == 0) { a = 1; for (i = 0; i < 5; i++) { printf("child %d\n", i + 1); sleep(1); } exit (EXIT_SUCCESS); } else { printf("*data %d\n", a); for (i = 0; i < 5; i++) { printf("parent %d\n", i + 1); sleep(1); } exit (EXIT_SUCCESS); } }vfork()で子プロセスを作成
[kitamura@localhost ]$ ./a.out child 1 child 2 child 3 child 4 child 5 *data 1 parent 1 parent 2 parent 3 parent 4 parent 5fork()で子プロセスを作成
[kitamura@localhost test]$ ./a.out *data 0 parent 1 child 1 parent 2 child 2 parent 3 child 3 parent 4 child 4 parent 5 child 5vforkはUNIX時代の過去の産物らしい。そのforkの実装では、子プロセスのメモリも確保し、その空間で動作するようになっていた。そうなると、forkの動作が重くなる。forkのlinuxの実装は、メモリー空間は確保しない。親プロセスのそれを利用する。そしてコピーオンライトにより、書き込み処理毎に順次確保していく。
コピーオンライトは、MMUのページング機能によるハードの助けをかりて実装される手法である。従ってMMUの実装していない当時のシステムでは、このコピーオンライトによる遅延割り当てができなかった。(たぶん)そのような環境下でのforkの負荷の軽減すべく、子プロセスは親のメモリー空間で動作させようとするものだ。従って子プロセスが、exec/exitで自身メモリー空間を空け渡すまで、親プロセスは動作する事ができないのだ。通常forkはexecで別のプロセスに置き換えられる、その時新しくメモリを確保することになり、親プロセスも動作するようになる。
実装
vforkは、CLONE_VFORK | CLONE_VM | SIGCHLDで、do_fork()をコールする。CLONE_VMが親プロセスメモリ空間で動作するゆえんである。int sys_vfork(struct pt_regs *regs) { return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs->sp, regs, 0, NULL, NULL); }CLONE_VFORKでdo_forkがコールされると、子プロセスのp->vfork_done = &vforkに、struct completionが設定される。そしてwake_up_new_taskで子プロセスを起床させ、wait_for_vfork_doneで子プロセスがメモリ空間を開放するまで、親プロセスはウエイトすることになる。
long do_fork(unsigned long clone_flags, unsigned long stack_start, struct pt_regs *regs, unsigned long stack_size, int __user *parent_tidptr, int __user *child_tidptr) { struct task_struct *p; int trace = 0; long nr; : : if (!IS_ERR(p)) { struct completion vfork; trace_sched_process_fork(current, p); nr = task_pid_vnr(p); if (clone_flags & CLONE_PARENT_SETTID) put_user(nr, parent_tidptr); if (clone_flags & CLONE_VFORK) { p->vfork_done = &vfork; init_completion(&vfork); get_task_struct(p); } UTRACE_HOOK(current, CLONE, report_clone(clone_flags, p)); wake_up_new_task(p); if (clone_flags & CLONE_VFORK) UTRACE_HOOK(current, CLONE, finish_vfork(current)); if (unlikely(trace)) ptrace_event(trace, nr); if (clone_flags & CLONE_VFORK) { if (!wait_for_vfork_done(p, &vfork)) ptrace_event(PTRACE_EVENT_VFORK_DONE, nr); } } else { nr = PTR_ERR(p); } return nr; }補足
wait_for_vfork_doneで子プロセスのメモリ空間を開放を待つ間も、親プロセスに実行権が移譲されるも、親プロセスのウエイトしているパスは、wait_for_vfork_done関数内である。ここで子プロセスのメモリ開放をチェックし、まだのようだと実行権を他に譲ることになる。