vfork
Rev.1を表示中。最新版はこちら。
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の実装はメモリー空間は確保しない。親プロセスのそれを利用する。そしてコピーオンライトにより、必要に応じ順次確保していく。
コピーオンライトは、MMUのページング機能によるハードの助けをかりて実装される手法である。従ってMMUの実装していない当時のシステムでは、このコピーオンライトによる遅延割り当てができなかった。(たぶん)そのような環境下でのforkの負荷の軽減すべく、子プロセスは親のメモリー空間で動作させようとするものだ。従って子プロセスが、exec/exitで自身メモリー空間を空け渡すまで、親プロセスはウエイトさせられる。と言うことのようだ。
実装
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関数内である。ここで子プロセスのメモリ開放をチェックし、まだのようだと実行権を他に譲ることになる。





