私は here を読みます。clone()
システムコールは、Linuxでスレッドを作成するために使用されます。 clone()
の- syntax は、開始ルーチン/関数アドレスを渡す必要があるようなものです。
しかし、ここ this ページでは、fork()
がclone()
を内部的に呼び出すと書かれています。だから私の質問は、fork()
によって作成された子プロセスがfork()
呼び出しの後にあるコードの一部の実行をどのように開始するか、つまり、開始点として関数を必要としないのですか?
私が提供したリンクに誤った情報がある場合は、いくつかのより良いリンク/リソースに私を導いてください。
ありがとう
このような質問については、常にソースコードを読んでください。
Glibcのnptl/sysdeps/unix/sysv/linux/fork.c
( GitHub )(nptl
= LinuxのネイティブPosixスレッド)から、fork()
の実装を見つけることができます。これは間違いなくではありません syscallの場合、Arch_FORK
( GitHub )のclone()
へのインライン呼び出しとして定義されているnptl/sysdeps/unix/sysv/linux/x86_64/fork.c
マクロ内でマジックが発生することがわかります。ただし、このバージョンのclone()
には関数またはスタックポインターが渡されません。それで、ここで何が起こっているのですか?
それでは、glibcでのclone()
の実装を見てみましょう。 sysdeps/unix/sysv/linux/x86_64/clone.S
( GitHub )にあります。子のスタックに関数ポインターを保存し、クローンsyscallを呼び出して、新しいプロセスがスタックから関数をポップして読み取り、それを呼び出すことを確認できます。
したがって、次のように機能します。
clone(void (*fn)(void *), void *stack_pointer)
{
Push fn onto stack_pointer
syscall_clone()
if (child) {
pop fn off of stack
fn();
exit();
}
}
そしてfork()
は...
fork()
{
...
syscall_clone();
...
}
実際のclone()
syscallは関数の引数をとらず、fork()
と同様に、戻り点から継続します。したがって、clone()
およびfork()
ライブラリ関数は、どちらもclone()
システムコールのラッパーです。
私のマニュアルのコピーは、clone()
がライブラリ関数でありシステムコールでもあるという事実について、いくらか先行しています。ただし、clone()
がセクション2とセクション3の両方ではなく、セクション2にあることは多少誤解を招きます。manページから:
#include <sched.h>
int clone(int (*fn)(void *), void *child_stack,
int flags, void *arg, ...
/* pid_t *ptid, struct user_desc *tls, pid_t *ctid */ );
/* Prototype for the raw system call */
long clone(unsigned long flags, void *child_stack,
void *ptid, void *ctid,
struct pt_regs *regs);
そして、
このページでは、glibc
clone()
ラッパー関数と、その関数の基礎となる基本的なシステムコールの両方について説明します。本文はラッパー関数について説明しています。 rawシステムコールの違いについては、このページの終わりに記載されています。
最後に、
未加工の
clone()
システムコールは、子の実行がコールのポイントから継続するという点でfork(2)
により密接に対応しています。そのため、clone()
ラッパー関数のfnおよびarg引数は省略されます。さらに、引数の順序が変わります。
@Dietrichは、実装を検討することで説明する素晴らしい仕事をしました。すごい!とにかく、それを発見する別の方法があります:呼び出しstrace "sniffs"を見ることです。
fork(2)
を使用する非常に単純なプログラムを準備してから、仮説を確認できます(つまり、fork
syscallが実際に発生していない)。
#define WRITE(__fd, __msg) write(__fd, __msg, strlen(__msg))
int main(int argc, char *argv[])
{
pid_t pid;
switch (pid = fork()) {
case -1:
perror("fork:");
exit(EXIT_FAILURE);
break;
case 0:
WRITE(STDOUT_FILENO, "Hi, i'm the child");
exit(EXIT_SUCCESS);
default:
WRITE(STDERR_FILENO, "Heey, parent here!");
exit(EXIT_SUCCESS);
}
return EXIT_SUCCESS;
}
次に、そのコードをコンパイルします(clang -Wall -g fork.c -o fork.out
)、次にstrace
で実行します。
strace -Cfo ./fork.strace.log ./fork.out
これは、プロセスによって呼び出されたシステムコールをインターセプトします(-f
また、子の呼び出しを傍受し、それらの呼び出しを./fork.trace.log
; -c
オプションを指定すると、最後に要約が表示されます)。私のマシン(Ubuntu 14.04、x86_64 Linux 3.16)での結果は(要約)です。
6915 Arch_prctl(Arch_SET_FS, 0x7fa001a93740) = 0
6915 mprotect(0x7fa00188c000, 16384, PROT_READ) = 0
6915 mprotect(0x600000, 4096, PROT_READ) = 0
6915 mprotect(0x7fa001ab9000, 4096, PROT_READ) = 0
6915 munmap(0x7fa001a96000, 133089) = 0
6915 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fa001a93a10) = 6916
6915 write(2, "Heey, parent here!", 18) = 18
6916 write(1, "Hi, i'm the child", 17 <unfinished ...>
6915 exit_group(0) = ?
6916 <... write resumed> ) = 17
6916 exit_group(0) = ?
6915 +++ exited with 0 +++
6916 +++ exited with 0 +++
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
24.58 0.000029 4 7 mmap
17.80 0.000021 5 4 mprotect
14.41 0.000017 9 2 write
11.02 0.000013 13 1 munmap
11.02 0.000013 4 3 3 access
10.17 0.000012 6 2 open
2.54 0.000003 2 2 fstat
2.54 0.000003 3 1 brk
1.69 0.000002 2 1 read
1.69 0.000002 1 2 close
0.85 0.000001 1 1 clone
0.85 0.000001 1 1 execve
0.85 0.000001 1 1 Arch_prctl
------ ----------- ----------- --------- --------- ----------------
100.00 0.000118 28 3 total
予想どおり、fork
呼び出しはありません。フラグ、子スタックなどが適切に設定されたraw clone
syscallだけです。