Верно ли, что fork() вызывает clone() внутри?

Я читаю здесь, что системный вызов clone() используется для создания потока в Linux. Теперь синтаксис clone() таков, что для его передачи требуется начальный адрес программы/функции.

Но здесь, на странице this, написано, что fork() вызывает clone() внутренне. Поэтому мой вопрос заключается в том, как дочерний процесс, созданный fork(), запускает часть кода, которая после вызова fork(), то есть как она не требует функции как отправной точки?

Если ссылки, которые я предоставил, имеют неверную информацию, пожалуйста, направляйте меня на несколько лучших ссылок/ресурсов.

Спасибо

Ответ 1

Для таких вопросов всегда читайте исходный код.

Из glibc nptl/sysdeps/unix/sysv/linux/fork.c (GitHub) (nptl= собственные потоки Posix для Linux) мы можем найти реализацию fork(), которая определенно не в syscall, мы можем видеть, что магия происходит внутри макроса ARCH_FORK, который определяется как встроенный вызов clone() в nptl/sysdeps/unix/sysv/linux/x86_64/fork.c (GitHub). Но подождите, никакая функция или указатель стека не передается этой версии clone()! Итак, что здесь происходит?

Посмотрим на реализацию clone() в glibc. Он находится в sysdeps/unix/sysv/linux/x86_64/clone.S (GitHub). Вы можете видеть, что он делает, это сохранение указателя функции в дочернем стеке, вызов клона клона, а затем новый процесс будет считывать функцию из стека и затем вызывать ее.

Итак, он работает следующим образом:

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();
    ...
}

Резюме

Фактический syscall clone() не принимает аргумент функции, он просто продолжается с точки возврата, как и fork(). Таким образом, функции библиотеки clone() и fork() являются обертками вокруг syscall clone().

Документация

Моя копия руководства несколько более подробно о том, что clone() является одновременно библиотечной функцией и системным вызовом. Тем не менее, я считаю, что это несколько вводит в заблуждение, что clone() находится в разделе 2, а не как в разделе 2, так и в разделе 3. На странице руководства:

#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(), так и базовый системный вызов, на котором он основан. В главном тексте функция обертки; различия для системного вызова описанный в конце этой страницы.

Наконец,

Необработанный clone() системный вызов более близко соответствует fork(2) в этом выполнение в ребенке продолжается с момента вызова. Как таковой, аргументы fn и arg функции обертки clone() опущены. Кроме того, порядок аргументов изменяется.

Ответ 2

@Dietrich проделал отличную работу, объясняя это, посмотрев на реализацию. Это потрясающе! Во всяком случае, есть еще один способ узнать, что: глядя на звонки, "нюхает".

Мы можем подготовить очень простую программу, которая использует fork(2), а затем проверит нашу гипотезу (т.е. что там не существует syscall fork).

#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 нет. Просто исходный s с его флагами, дочерним стеком и т.д.