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