コピーオンライトについて

uClinux 調査環境について - まだ見えない先の
詳解LINUXカーネル p.414
MMU は書き込みが禁止されているページへの変更時にアクセス許可フォルトに起因する例外を発生する.
故意にページテーブルエントリを書き込み禁止にし,変更時までページの複製を遅延させる.参照されるだけならば複製の必要はないという考えに基づく.
fork時,子プロセスが親プロセスのアドレス空間にアクセスする際などに用いられる.
forkの場合,kernel/fork.c do_fork() => ... => dup_mmap() => mm/memory.c copy_page_range() => ... => copy_one_pte() にて pte_wrprotect() で (is_cow_mapping(vm_flags)が真ならば)ページテーブルエントリに書き込み禁止をセットしている.

fork と vfork p.123

noMMUではCOWができずfork時にメモリ空間を共有する(& 変更まで共有してしまう).そこでvforkにより,子プロセスが実行を終了,もしくはexecするまで,親プロセスの実行を止める方法を用いる.
noMMU時は,

asmlinkage int sys_fork(struct pt_regs *regs)
{
#ifdef CONFIG_MMU
        return do_fork(SIGCHLD, regs->ARM_sp, regs, 0, NULL, NULL);
#else
        /* can not support in nommu mode */
        return(-EINVAL);
#endif
}

と,fork時にエラーが返る.
sys_fork()とsys_vfork()の違いは,下記の通り.

sys_fork(): return do_fork(SIGCHLD, regs->ARM_sp, regs, 0, NULL, NULL);
sys_vfork(): return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs->ARM_sp, regs, 0, NULL, NULL);

CLONE_VFORK p.124

#define CLONE_VFORK     0x00004000      /* set if the parent wants the child to wake it up on mm_release */

1. 親プロセスは子プロセス構造体のvfork_doneを使って,子プロセスのexit,execを待つ
1.1 kernel/fork.c: do_fork(), 下記は親プロセスの動作

...
struct completion vfork;

if (clone_flags & CLONE_VFORK) {
        p->vfork_done = &vfork;
        init_completion(&vfork);
}

...

if (clone_flags & CLONE_VFORK) {
        freezer_do_not_count();
        wait_for_completion(&vfork);
        freezer_count();
        if (unlikely (current->ptrace & PT_TRACE_VFORK_DONE)) {
                current->ptrace_message = nr;
                ptrace_notify ((PTRACE_EVENT_VFORK_DONE << 8) | SIGTRAP);
        }
}
...

CLONE_VFORK時,親プロセスはプロセス構造体のvfork_doneメンバを利用して,wait_for_completion()に入る.
ここで,vforkはカーネル空間(カーネルスタック)上の変数であるため(ページテーブル操作をしなくても)メモリ空間は共有されており,子プロセスによるvforkへの変更は親プロセスから見ることが可能.

1.2 kernel/sched.c wait_for_completion(struct completion *x)

...
do {
        __set_current_state(TASK_UNINTERRUPTIBLE);
        spin_unlock_irq(&x->wait.lock);
        schedule();
        spin_lock_irq(&x->wait.lock);
} while (!x->done);
...

親プロセスは,x->done が 非0 になるまで TASK_UNINTERRUPTIBLE で回る.また,下記で呼ばれる complete() によって x->done++ される.

1.3 p->vfork_done は下記でのみ変更される.また,下記は子プロセスの動作.
kernel/fork.c: mm_release()

...
/* notify parent sleeping on vfork() */
if (vfork_done) {
        tsk->vfork_done = NULL;
        complete(vfork_done);
}
...

mm_release()時,子プロセスのcomplete()によって,親プロセスはループから抜ける.
mm_release()は,kernel/exit.c do_exit()=>exit_mm(),fs/exec.c do_execve() => search_binary_handler() => fs/binfmt_{elf,flat,...}.c load_*() => fs/exec.c flush_old_exec() => exec_mmap(),(kernel/exit.c daemonize()=>exit_mm()) から呼ばれる.
つまり,子プロセスが exit,exec を行う際に mm_release() は呼ばれ,親プロセスはループから抜ける.

fs/exec.c int coredump_wait()

...
/*                                                                                                           
 * Make sure nobody is waiting for us to release the VM,                                                     
 * otherwise we can deadlock when we wait on each other                                                      
 */
vfork_done = tsk->vfork_done;
if (vfork_done) {
        tsk->vfork_done = NULL;
        complete(vfork_done);
}
...

coredump_wait()は,kernel/signal.c get_signal_to_deliver()にて配送するシグナルのアクションが coredump の際,do_coredump() 経由で呼ばれる.
確認: coredump 時は,do_exit()経由の終了処理は行われない?