forkの返り値
forkは、copy_processで親プロセスディスクリプタを雛形にして、clone_flagsのオプションに応じた子プロセスのプロセスディスクリプタが作成し、その子プロセスIDを返り値としている。従って返り値が0でない親プロセス処理と、0である子プロセス処理として記述していくことができる。
ユーザプロセスからforkを呼び出すと、glibがユーザスタックに詰まれた引数をレジスターに設定しなおす。カーネルへ処理がわたると、カーネルスタックとしてプロセス毎のthread_structにスタックが切り替わるからである。int 80hでカーネルプロセスに切り替わり、systm_callがコールされる。この時点からカーネルスタックとなる。int 80を呼び出す前に、ユーザプロセスから渡された引数はレジスターに設定しているわけで、これ等のレジスタを含めて他のレジスターをこのカーネルスタックに積みなおすことで、カーネル側でもC呼び出しによるスタック参照で値の受け渡しができるようになっている。
systm_callではAXレジスタに設定されたファンクションコードをインデックスとして、該当する処理をコールする。その処理からリターンすると、返り値はAXレジスターに入っている。Cで記述されているわけで、カーネルモードといえども同じことだ。それをこのシステムコールを呼び出す前にスタックに積んだAXレジスターの箇所に設定し、改めてスタックに積まれたすべてのレジスターをpopして各レジスタを復帰しiretで、glibが呼び出したint 80hにリターンすることでAXレジスターをシステムコールの返り値として、ユーザプロセスへと渡っていくようになっている。
カーネルスタックは、それぞれのプロセスディスクリプタのthread_structに設定している。従って子プロセスのプロセスディスクリプタのthread_struct内のスタック領域のAXがpushされる箇所に、子プロセスの返り値である0を設定すればいいわけだ。
子プロセスもforkの後から走り出す。そのためには、子プロセスのカーネルスタックは親のカーネルスタックと同じにする必要がある。ただtask_struct->thread.ipにはタスクスイッチ直後の実行アドレスを設定するようになっており、通常はスイッチされた直後から走り出すようになっているのだが、fork直後の子プロセスはret_from_forkから走りだすようになっている。
systm_callは該当システムコールからリターンして後、カーネルスタックからpopして、レジスター群を復帰している。この値はint 80Hをコールする前のユーザプロセスでのレジスタ値である。従ってiretでユーザプロセス(glib内)にリターンすることで、AXをforkの返り値として処理することが可能である。
ret_from_forkはsystm_callが呼び出された後のレジスタ群の復帰の処理を行っていて、ここで重要なの子プロセスのカーネルスタックは親プロセスカーネルスタックと同じにしているため、すぜに親プロセスがiretで戻っていても、再度のiretで、呼び出しもとのint 80hのところ、すなわちユーザプロセスの呼び出し元forkにリターンすることができる。
ユーザプロセスからforkを呼び出すと、glibがユーザスタックに詰まれた引数をレジスターに設定しなおす。カーネルへ処理がわたると、カーネルスタックとしてプロセス毎のthread_structにスタックが切り替わるからである。int 80hでカーネルプロセスに切り替わり、systm_callがコールされる。この時点からカーネルスタックとなる。int 80を呼び出す前に、ユーザプロセスから渡された引数はレジスターに設定しているわけで、これ等のレジスタを含めて他のレジスターをこのカーネルスタックに積みなおすことで、カーネル側でもC呼び出しによるスタック参照で値の受け渡しができるようになっている。
systm_callではAXレジスタに設定されたファンクションコードをインデックスとして、該当する処理をコールする。その処理からリターンすると、返り値はAXレジスターに入っている。Cで記述されているわけで、カーネルモードといえども同じことだ。それをこのシステムコールを呼び出す前にスタックに積んだAXレジスターの箇所に設定し、改めてスタックに積まれたすべてのレジスターをpopして各レジスタを復帰しiretで、glibが呼び出したint 80hにリターンすることでAXレジスターをシステムコールの返り値として、ユーザプロセスへと渡っていくようになっている。
カーネルスタックは、それぞれのプロセスディスクリプタのthread_structに設定している。従って子プロセスのプロセスディスクリプタのthread_struct内のスタック領域のAXがpushされる箇所に、子プロセスの返り値である0を設定すればいいわけだ。
子プロセスもforkの後から走り出す。そのためには、子プロセスのカーネルスタックは親のカーネルスタックと同じにする必要がある。ただtask_struct->thread.ipにはタスクスイッチ直後の実行アドレスを設定するようになっており、通常はスイッチされた直後から走り出すようになっているのだが、fork直後の子プロセスはret_from_forkから走りだすようになっている。
systm_callは該当システムコールからリターンして後、カーネルスタックからpopして、レジスター群を復帰している。この値はint 80Hをコールする前のユーザプロセスでのレジスタ値である。従ってiretでユーザプロセス(glib内)にリターンすることで、AXをforkの返り値として処理することが可能である。
ret_from_forkはsystm_callが呼び出された後のレジスタ群の復帰の処理を行っていて、ここで重要なの子プロセスのカーネルスタックは親プロセスカーネルスタックと同じにしているため、すぜに親プロセスがiretで戻っていても、再度のiretで、呼び出しもとのint 80hのところ、すなわちユーザプロセスの呼び出し元forkにリターンすることができる。
int copy_thread(int nr, unsigned long clone_flags, unsigned long sp, unsigned long unused, struct task_struct * p, struct pt_regs * regs) { struct pt_regs * childregs; struct task_struct *tsk; int err; 子プロセスのthread_struct内のレジスター群をpopしている位置を求める。 childregs = task_pt_regs(p); 親プロセスのスタック内のレジスター群regsを子プロセスのスタックに設定する。 *childregs = *regs; なお返り値に子プロセスとして0を設定する。 childregs->ax = 0; childregs->sp = sp; p->thread.sp = (unsigned long) childregs; p->thread.sp0 = (unsigned long) (childregs+1); スイッチングで子プロセスにスイッチングされるとret_from_forkから走り出す。 p->thread.ip = (unsigned long) ret_from_fork;