do_fork()の動作を調べてみた。

#写真は前に撮った大崎駅近くからの夕焼け

kernelnewbiesメーリングリストで非SMPマシンでレースコンディションを作りたいと言うスレッドがあって、その中でtask_structの扱いが話題になっていたので調べてみた。

ソースはlxrにある2.6.29を参照しつつ
入り口はsys_clone()だけどsys_clone()は見てもdo_fork()を呼ぶのがメインの仕事なので飛ばして、do_fork()から。
この中で多分ここが主役。

1398        p = copy_process(clone_flags, stack_start, regs, stack_size,
1399                         child_tidptr, NULL, trace);

copy_process()に移って、982行目が主要っぽい。
currentとはお馴染みのcurrentマクロ。

 982        p = dup_task_struct(current);
 983        if (!p)
 984                goto fork_out;

dup_task_struct()に移って、229行目のarch_dup_task_struct()がtask_structをコピーしている部分。

 211static struct task_struct *dup_task_struct(struct task_struct *orig)
 212{
 213        struct task_struct *tsk;
 214        struct thread_info *ti;
 215        int err;
 216
 217        prepare_to_copy(orig);
 218
 219        tsk = alloc_task_struct();
 220        if (!tsk)
 221                return NULL;
 222
 223        ti = alloc_thread_info(tsk);
 224        if (!ti) {
 225                free_task_struct(tsk);
 226                return NULL;
 227        }
 228
 229        err = arch_dup_task_struct(tsk, orig);
 230        if (err)
 231                goto out;
 232

lxrでの検索によると、arch_dup_task_structの実装は2個あって、一つはarch/x86/kernel/process.cでもう一つはkernel/fork.cにある。

arch/x86/kernel/process.cでの実装は・・・

  22int arch_dup_task_struct(struct task_struct *dst, struct task_struct *src)
  23{
  24        *dst = *src;
  25        if (src->thread.xstate) {
  26                dst->thread.xstate = kmem_cache_alloc(task_xstate_cachep,
  27                                                      GFP_KERNEL);
  28                if (!dst->thread.xstate)
  29                        return -ENOMEM;
  30                WARN_ON((unsigned long)dst->thread.xstate & 15);
  31                memcpy(dst->thread.xstate, src->thread.xstate, xstate_size);
  32        }
  33        return 0;
  34}

kernel/fork.cでの実装は・・・

 204int __attribute__((weak)) arch_dup_task_struct(struct task_struct *dst,
 205                                               struct task_struct *src)
 206{
 207        *dst = *src;
 208        return 0;
 209}

いづれの実装も、元のプロセスのtask_structのポインタをコピーしていることには違いが無い。
この時点でtask_structの基本的なコピー処理ができて、その後do_fork()内でclone_flagsに応じた処理が行われる。

1106        /* copy all the process information */
1107        if ((retval = copy_semundo(clone_flags, p)))
1108                goto bad_fork_cleanup_audit;
1109        if ((retval = copy_files(clone_flags, p)))
1110                goto bad_fork_cleanup_semundo;
1111        if ((retval = copy_fs(clone_flags, p)))
1112                goto bad_fork_cleanup_files;
1113        if ((retval = copy_sighand(clone_flags, p)))
1114                goto bad_fork_cleanup_fs;
1115        if ((retval = copy_signal(clone_flags, p)))
1116                goto bad_fork_cleanup_sighand;
1117        if ((retval = copy_mm(clone_flags, p)))
1118                goto bad_fork_cleanup_signal;
1119        if ((retval = copy_namespaces(clone_flags, p)))
1120                goto bad_fork_cleanup_mm;
1121        if ((retval = copy_io(clone_flags, p)))

これらの処理は基本的にCLONE_XXXフラグが立っていたら参照カウントを増やして、立ってなければコピーするといった処理。
例えば、copy_mm()の場合こんな感じ

 655        if (clone_flags & CLONE_VM) {
 656                atomic_inc(&oldmm->mm_users);
 657                mm = oldmm;
 658                goto good_mm;
 659        }
 660
 661        retval = -ENOMEM;
 662        mm = dup_mm(tsk);
 663        if (!mm)
 664                goto fail_nomem;

do_fork()ではcopy_thread()と言う関数でスレッドのコピーもしていた。この関数はアーキテクチャごとに実装がある。
x86_64だとarch/x86/kernel/process_64.c。ちなみにpは新しいtask_struct。

1123        retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs);

ところで、そもそもdo_forkを調べていたのはCLONE_VMフラグが立ってたらアドレススペースは共有されるよ!っていうのを調べたかっただけなのでいい加減疲れたし、ここで調査終了(´ー`)y─┛~~