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とし、通常の子プロセスとして親子で非同期的に動作する事になります。
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);
}


最終更新 2017/01/19 14:14:57 - north
(2014/08/09 19:01:07 作成)


検索

アクセス数
3689383
最近のコメント
コアダンプファイル - sakaia
list_head構造体 - yocto_no_yomikata
勧告ロックと強制ロック - wataash
LKMからのファイル出力 - 重松 宏昌
kprobe - ななし
ksetの実装 - スーパーコピー
カーネルスレッドとは - ノース
カーネルスレッドとは - nbyst
asmlinkageってなに? - ノース
asmlinkageってなに? - よろしく
Adsense
広告情報が設定されていません。