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

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

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

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

Спасибо

2 ответа

Решение

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

Из Глибц nptl/sysdeps/unix/sysv/linux/fork.c ( GitHub) (nptl = родные потоки Posix для Linux) мы можем найти реализацию fork()что определенно не является системным вызовом, мы можем видеть, что магия происходит внутри 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, а затем новый процесс считывает, извлекает функцию из стека и затем вызывает ее.

Так что это работает так:

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, а не в разделе 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() функция обертки опущена. Кроме того, порядок аргументов изменяется.

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

Мы можем подготовить очень простую программу, которая использует fork(2) а затем проверить нашу гипотезу (то есть, что нет 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 звонки. Просто сырье clone Системный вызов с его флагами, дочерним стеком и т. д. правильно установлен.

Другие вопросы по тегам