vforkシステムコール
gccのvfork関数は、CLONE_VFORKでforkシステムコールからdo_fork()され、copy_process()で作成された子プロセスp->vfork_done->done=0とし、呼び出し元の親プロセスは、schedule_timeout()でp->vfork_done->done!=0までウエイトします。
p->vfork_done->doneはdo_exit()からexit_mm()/mm_release()/complete()/complete_vfork_done()でx->done++となり、子プロセスが終了時p->vfork_done.waitにリンクしている親プロセスを起床させます。
待機している親プロセスをシグナルで起床可能で、起床させると子プロセスはp->vfork_done=NULLとし、通常の子プロセスとして親子で非同期的に動作する事になります。
p->vfork_done->doneはdo_exit()からexit_mm()/mm_release()/complete()/complete_vfork_done()でx->done++となり、子プロセスが終了時p->vfork_done.waitにリンクしている親プロセスを起床させます。
待機している親プロセスをシグナルで起床可能で、起床させると子プロセスはp->vfork_done=NULLとし、通常の子プロセスとして親子で非同期的に動作する事になります。
struct completion { unsigned int done; wait_queue_head_t wait; } static inline void init_completion(struct completion *x) { x->done = 0; init_waitqueue_head(&x->wait); } 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 (clone_flags & CLONE_NEWUSER) { if (clone_flags & CLONE_THREAD) return -EINVAL; if (!capable(CAP_SYS_ADMIN) || !capable(CAP_SETUID) || !capable(CAP_SETGID)) return -EPERM; } if (likely(user_mode(regs)) && !(clone_flags & CLONE_UNTRACED)) { if (clone_flags & CLONE_VFORK) trace = PTRACE_EVENT_VFORK; else if ((clone_flags & CSIGNAL) != SIGCHLD) trace = PTRACE_EVENT_CLONE; else trace = PTRACE_EVENT_FORK; if (likely(!ptrace_event_enabled(current, trace))) trace = 0; } p = copy_process(clone_flags, stack_start, regs, stack_size, child_tidptr, NULL, trace); 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); } wake_up_new_task(p); 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_completion_killable()で子プロセスが終了するまで待機します。返り値が0でないならシグナルによる復帰で、子プロセスは終了しておらず、 child->vfork_done=NULLとし、親子プロセスはvforkでない通常の非同期プロセスとなります。
static int wait_for_vfork_done(struct task_struct *child, struct completion *vfork) { int killed; freezer_do_not_count(); killed = wait_for_completion_killable(vfork); freezer_count(); if (killed) { task_lock(child); child->vfork_done = NULL; task_unlock(child); } put_task_struct(child); return killed; } int __sched wait_for_completion_killable(struct completion *x) { long t = wait_for_common(x, MAX_SCHEDULE_TIMEOUT, TASK_KILLABLE); if (t == -ERESTARTSYS) return t; return 0; }wait_queue_t wait.private=currentのカレントプロセスの親プロセスを待機プロセスとする、p->vfork_done->x->waitにリストし、子プロセスがdo_exit()で終了時、p->vfork_done->x->waitのリストされているプロセスを起床させます。MAX_SCHEDULE_TIMEOUTはタイムに掛かる待機は考慮されず、子プロセスが終了するまで永久的に待機します。
static long __sched wait_for_common(struct completion *x, long timeout, int state) { might_sleep(); spin_lock_irq(&x->wait.lock); timeout = do_wait_for_common(x, timeout, state); spin_unlock_irq(&x->wait.lock); return timeout; } #define __WAITQUEUE_INITIALIZER(name, tsk) { \ .private = tsk, \ .func = default_wake_function, \ .task_list = { NULL, NULL } } #define DECLARE_WAITQUEUE(name, tsk) \ wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk) static inline long __sched do_wait_for_common(struct completion *x, long timeout, int state) { if (!x->done) { DECLARE_WAITQUEUE(wait, current); __add_wait_queue_tail_exclusive(&x->wait, &wait); do { if (signal_pending_state(state, current)) { timeout = -ERESTARTSYS; break; } __set_current_state(state); spin_unlock_irq(&x->wait.lock); timeout = schedule_timeout(timeout); spin_lock_irq(&x->wait.lock); } while (!x->done && timeout); __remove_wait_queue(&x->wait, &wait); if (!x->done) return timeout; } x->done--; return timeout ?: 1; }プロセス終了時do_exit()からコールされで、tsk->vfork_done!=NULLのvforkプロセスなら、complete()でtsk->vfork_done->done++とし、tsk->vfork_done->waitにリンクされている待機中の親プロセスを起床させます。
static void exit_mm(struct task_struct * tsk) { struct mm_struct *mm = tsk->mm; struct core_state *core_state; mm_release(tsk, mm); if (!mm) return; down_read(&mm->mmap_sem); core_state = mm->core_state; if (core_state) { struct core_thread self; up_read(&mm->mmap_sem); self.task = tsk; self.next = xchg(&core_state->dumper.next, &self); if (atomic_dec_and_test(&core_state->nr_threads)) complete(&core_state->startup); for (;;) { set_task_state(tsk, TASK_UNINTERRUPTIBLE); if (!self.task) /* see coredump_finish() */ break; schedule(); } __set_task_state(tsk, TASK_RUNNING); down_read(&mm->mmap_sem); } atomic_inc(&mm->mm_count); BUG_ON(mm != tsk->active_mm); /* more a memory barrier than a real lock */ task_lock(tsk); tsk->mm = NULL; up_read(&mm->mmap_sem); enter_lazy_tlb(mm, current); task_unlock(tsk); mm_update_next_owner(mm); mmput(mm); } void mm_release(struct task_struct *tsk, struct mm_struct *mm) { /* Get rid of any futexes when releasing the mm */ #ifdef CONFIG_FUTEX if (unlikely(tsk->robust_list)) { exit_robust_list(tsk); tsk->robust_list = NULL; } #ifdef CONFIG_COMPAT if (unlikely(tsk->compat_robust_list)) { compat_exit_robust_list(tsk); tsk->compat_robust_list = NULL; } #endif if (unlikely(!list_empty(&tsk->pi_state_list))) exit_pi_state_list(tsk); #endif deactivate_mm(tsk, mm); if (tsk->vfork_done) complete_vfork_done(tsk); if (tsk->clear_child_tid) { if (!(tsk->flags & PF_SIGNALED) && atomic_read(&mm->mm_users) > 1) { put_user(0, tsk->clear_child_tid); sys_futex(tsk->clear_child_tid, FUTEX_WAKE, 1, NULL, NULL, 0); } tsk->clear_child_tid = NULL; } } static void complete_vfork_done(struct task_struct *tsk) { struct completion *vfork; task_lock(tsk); vfork = tsk->vfork_done; if (likely(vfork)) { tsk->vfork_done = NULL; complete(vfork); } task_unlock(tsk); } void complete(struct completion *x) { unsigned long flags; spin_lock_irqsave(&x->wait.lock, flags); x->done++; __wake_up_common(&x->wait, TASK_NORMAL, 1, 0, NULL); spin_unlock_irqrestore(&x->wait.lock, flags); }
備考
本実装はgccのvfork()関数の実装で、vforkシステムコールは、CLONE_VFORK | CLONE_MMでdo_fork()され、exec()でなくsystem()によるforkしてのexecとする必要があり、そうであるならvfork()することなく親プロセスからsystem()すればいいわけで、掛かる実装は、他のプロセスから親プロセスにシグナルを送信することで、子プロセスの途中までを実行させ、以降の子プロセスの処理は親と非同期で動作させる運用故かと。int sys_vfork(struct pt_regs *regs) { return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs->sp, regs, 0, NULL, NULL); }