xv6におけるforkの挙動について

はじめに

xv6でのfork関数について、親プロセスと子プロセスとのパラメタ設定の比較を意識してまとめてみた。

なお、context switch(struct contextが関係してる)やsyscall, iret(struct trapframeが関係してる)の詳細は省くので、switching編を参考にしてください(あんまりまとまってないけど)

Reference

fork

user側でforkが実行されたとき、sys_forkが実行される。sys_forkは即fork()を実行する。fork()

https://github.com/mit-pdos/xv6-public/blob/xv6-rev11/proc.c#L181

の実装である。

上記のコードに沿って、parent processとchild processの設定を比較したものが次のようになる:

process\item struct proc struct trapframe fork後の%eip fork後の%esp 戻り値 kernel stack
parent curproc curproc->tf curproc->tf->eip(変化なし) curproc->tf->esp(変化なし) return pid; curproc->kstack(変化なし)
child np *np->tf=*curproc->tf forkret => trapret => parentの%eip np->tf->espで変化しない(pagingで対応) np->tf->eax = 0 allocproc => p->kstack = kalloc()

子プロセスと親プロセスとでfork後の%esp(virtual memory address)は同じであるが、copyuvmで異なるpagingにしているため、指し示すphysical memory addressの場所が異なる。

親プロセスと子プロセスは以下のような時間経過をたどる1

f:id:knknkn11626:20190806192224j:plain
親プロセスと子プロセスの変遷(Ⓐはfork後のプログラムの実行を表す)

やや詳細に書くと以下のような感じ:

  1. [parent process] int $T_SYSCALLを実行し、sys_forkが呼ばれる。
  2. [parent process] sys_fork実行することで、np(子プロセスのdescriptor)を構成する。このとき、forkret関数trapret関数がstack上に格納される。trapframe自体はそのまま複製されるので、trapframeのeipメンバの値は親プロセスのものと同じである。
  3. [parent process] np->state = RUNNABLE;と設定する。
  4. [parent process] sys_forkがreturnし、trapretのiretの実行で、user landに戻る。このプロセスは特に何もいじってないので、そのまま進行する。
  5. [parent process] sched() -> swtchが呼ばれる(context switch)。例えば、local timerなどで定期的に発生したinterruptでyield() -> sched()が呼ばれるなど。
  6. [kernel process(swapper)] swtichkvmから再開する。
  7. [kernel process(swapper)] p->state == RUNNABLEのprocessが選出される。このとき、3で設定したprocessが選ばれるとしよう(このタイミングでなくてもいずれ選ばれるので)
  8. [kernel process(swapper)] swtch関数が実行される(context switch)
  9. [child process] context switchによって、stack上のcontext->eipのaddressにジャンプする。これによりforkret関数が実行される
  10. [child process] forkretが終わったらtrapretが実行される
  11. [child process] trapretのiretの実行でuser landに戻る。レジスタはtrapframeに保存されているので復元される。このとき、trapframeのeipメンバは親プロセスのものと同じだったから、後は親プロセスと同じ変遷をたどることになる。

Note) xv6では子供は親プロセスの後に開始するが、実はlinux kernelでは、子プロセスが親よりも先に実行される。また、xv6はcontext switchで必ずswapper processを経由するが、linux kernelの場合はprocessから他のprocessに直接遷移する。これらの詳細は次の機会としたい。

Note) sys_execはforkと親戚のような関数だが、forkよりも単純なsyscallだ:

ELFファイルの構造については、リンカ・ローダ実践開発テクニックのch.2が詳しいのでそちらを読んでください。


  1. xv6ではmulti-processor対応してるが、今回はかんたんのため物理コアを1つと仮定する。