web-dev-qa-db-ja.com

fork()が内部的にclone()を呼び出すのは本当ですか?

私は here を読みます。clone()システムコールは、Linuxでスレッドを作成するために使用されます。 clone()の-​​ syntax は、開始ルーチン/関数アドレスを渡す必要があるようなものです。

しかし、ここ this ページでは、fork()clone()を内部的に呼び出すと書かれています。だから私の質問は、fork()によって作成された子プロセスがfork()呼び出しの後にあるコードの一部の実行をどのように開始するか、つまり、開始点として関数を必要としないのですか?

私が提供したリンクに誤った情報がある場合は、いくつかのより良いリンク/リソースに私を導いてください。

ありがとう

42

このような質問については、常にソースコードを読んでください。

Glibcのnptl/sysdeps/unix/sysv/linux/fork.cGitHub )(nptl = LinuxのネイティブPosixスレッド)から、fork()の実装を見つけることができます。これは間違いなくではありません syscallの場合、Arch_FORKGitHub )のclone()へのインライン呼び出しとして定義されているnptl/sysdeps/unix/sysv/linux/x86_64/fork.cマクロ内でマジックが発生することがわかります。ただし、このバージョンのclone()には関数またはスタックポインターが渡されません。それで、ここで何が起こっているのですか?

それでは、glibcでのclone()の実装を見てみましょう。 sysdeps/unix/sysv/linux/x86_64/clone.SGitHub )にあります。子のスタックに関数ポインターを保存し、クローン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引数は省略されます。さらに、引数の順序が変わります。

71
Dietrich Epp

@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だけです。

11
Ciro Costa