Ltraceを使用してシステムコールをトレースすると、fork()がsys_fork()ではなくsys_clone()を使用することがわかりました。しかし、それが定義されているLinuxソースを見つけることができませんでした。
私のプログラムは
#include<stdio.h>
main()
{
int pid,i=0,j=0;
pid=fork();
if(pid==0)
printf("\nI am child\n");
else
printf("\nI am parent\n");
}
そしてltraceの出力は
SYS_brk(NULL) = 0x019d0000
SYS_access("/etc/ld.so.nohwcap", 00) = -2
SYS_mmap(0, 8192, 3, 34, 0xffffffff) = 0x7fe3cf84f000
SYS_access("/etc/ld.so.preload", 04) = -2
SYS_open("/etc/ld.so.cache", 0, 01) = 3
SYS_fstat(3, 0x7fff47007890) = 0
SYS_mmap(0, 103967, 1, 2, 3) = 0x7fe3cf835000
SYS_close(3) = 0
SYS_access("/etc/ld.so.nohwcap", 00) = -2
SYS_open("/lib/x86_64-linux-gnu/libc.so.6", 0, 00) = 3
SYS_read(3, "\177ELF\002\001\001", 832) = 832
SYS_fstat(3, 0x7fff470078e0) = 0
SYS_mmap(0, 0x389858, 5, 2050, 3) = 0x7fe3cf2a8000
SYS_mprotect(0x7fe3cf428000, 2097152, 0) = 0
SYS_mmap(0x7fe3cf628000, 20480, 3, 2066, 3) = 0x7fe3cf628000
SYS_mmap(0x7fe3cf62d000, 18520, 3, 50, 0xffffffff) = 0x7fe3cf62d000
SYS_close(3) = 0
SYS_mmap(0, 4096, 3, 34, 0xffffffff) = 0x7fe3cf834000
SYS_mmap(0, 4096, 3, 34, 0xffffffff) = 0x7fe3cf833000
SYS_mmap(0, 4096, 3, 34, 0xffffffff) = 0x7fe3cf832000
SYS_Arch_prctl(4098, 0x7fe3cf833700, 0x7fe3cf832000, 34, 0xffffffff) = 0
SYS_mprotect(0x7fe3cf628000, 16384, 1) = 0
SYS_mprotect(0x7fe3cf851000, 4096, 1) = 0
SYS_munmap(0x7fe3cf835000, 103967) = 0
__libc_start_main(0x40054c, 1, 0x7fff47008298, 0x4005a0, 0x400590 <unfinished ...>
fork( <unfinished ...>
SYS_clone(0x1200011, 0, 0, 0x7fe3cf8339d0, 0) = 5967
<... fork resumed> ) = 5967
puts("\nI am parent" <unfinished ...>
SYS_fstat(1, 0x7fff47008060) = 0
SYS_mmap(0, 4096, 3, 34, 0xffffffff
) = 0x7fe3cf84e000
I am child
SYS_write(1, "\n", 1
) = 1
SYS_write(1, "I am parent\n", 12) = -512
--- SIGCHLD (Child exited) ---
SYS_write(1, "I am parent\n", 12I am parent
) = 12
<... puts resumed> ) = 13
SYS_exit_group(13 <no return ...>
+++ exited (status 13) +++
Glibcのfork()
およびvfork()
ラッパーは、clone()
システムコールを介して実装されます。 fork()
とclone()
の関係をよりよく理解するには、Linuxでのプロセスとスレッドの関係を考慮する必要があります。
従来、fork()
は、親プロセスが所有するすべてのリソースを複製し、コピーを子プロセスに割り当てます。この方法ではかなりのオーバーヘッドが発生しますが、子がexec()
をすぐに呼び出すと、すべてが無駄になります。 Linuxでは、fork()
はcopy-on-writeページを利用して、親プロセスと子プロセス間で共有できるデータのコピーを遅らせるか、完全に回避します。したがって、通常のfork()
で発生するオーバーヘッドは、親のページテーブルのコピーと、子に対する一意のプロセス記述子構造体_task_struct
_の割り当てのみです。
Linuxは、スレッドに対しても例外的なアプローチを採用しています。 Linuxでは、スレッドは単に他のプロセスとリソースを共有する通常のプロセスにすぎません。これは、プロセスやスレッドがまったく異なる種類の獣であるWindowsやSolarisなどの他のオペレーティングシステムと比較して、スレッドに対する根本的に異なるアプローチです。 Linuxでは、各スレッドに通常の_task_struct
_があり、これはたまたま、アドレススペースなどの特定のリソースを親プロセスと共有するようにセットアップされます。
clone()
システムコールのflags
パラメータには、親プロセスと子プロセスが共有するリソース(存在する場合)を示すフラグのセットが含まれています。プロセスとスレッドはどちらもclone()
を介して作成されます。唯一の違いは、clone()
に渡されるフラグのセットです。
通常のfork()
は次のように実装できます:
_clone(SIGCHLD, 0);
_
これにより、親とリソースを共有しないタスクが作成され、終了時にSIGCHLD
終了シグナルを親に送信するように設定されます。
対照的に、アドレススペース、ファイルシステムリソース、ファイル記述子、およびシグナルハンドラーを親と共有するタスク、つまりthreadは、次のように作成できます。
_clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);
_
vfork()
は、個別の_CLONE_VFORK
_フラグを介して実装されます。これにより、子プロセスがシグナルを介してスリープ解除するまで、親プロセスがスリープします。子は、exec()
を呼び出すか、終了するまで、親の名前空間で唯一の実行スレッドになります。子供はメモリに書き込むことができません。対応するclone()
呼び出しは次のようになります。
_clone(CLONE_VFORK | CLONE_VM | SIGCHLD, 0)
_
sys_clone()
の実装はアーキテクチャ固有ですが、作業の大部分は_kernel/fork.c
_で定義されているdo_fork()
で行われます。この関数は静的clone_process()
を呼び出します。これは、親のコピーとして新しいプロセスを作成しますが、まだ開始していません。 clone_process()
は、レジスターをコピーし、PIDを新しいタスクに割り当て、クローンflags
で指定されたプロセス環境の適切な部分を複製または共有します。 clone_process()
が戻ると、do_clone()
は新しく作成されたプロセスを起動し、実行するようにスケジュールします。
Linuxでユーザーランドシステムコール関数をカーネルシステムコールに変換するコンポーネントはlibcです。 GLibCでは、NPTLライブラリがこれをclone(2)
システムコールにリダイレクトします。