Верно ли, что 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 с его флагами, дочерним стеком и т.д.