Как fork () возвращается для дочернего процесса - PullRequest
12 голосов
/ 01 апреля 2010

Я знаю, что fork () по-разному возвращается для дочернего и родительского процессов, но я не могу найти информацию о том, как это происходит. Как дочерний процесс получает возвращаемое значение 0 от fork? И какая разница в отношении стека вызовов? Насколько я понимаю, для родителя это выглядит примерно так:

родительский процесс - вызывает fork -> system_call - вызывает fork -> fork выполняется - возвращается к -> system_call - возвращается к -> родительскому процессу.

Что происходит в дочернем процессе?

Ответы [ 6 ]

23 голосов
/ 01 апреля 2010

% man fork

ВОЗВРАЩАЕМЫЕ ЗНАЧЕНИЯ

Upon successful completion, fork() returns a value of 0 to the child
process and returns the process ID of the child process to the parent
process.  Otherwise, a value of -1 is returned to the parent process, no
child process is created, and the global variable [errno][1] is set to indi-
cate the error.

Что происходит, так это то, что внутри системного вызова fork весь процесс дублируется. Затем вызов fork в каждом возвращается. Теперь это разные контексты, поэтому они могут возвращать разные коды возврата.

Если вы действительно хотите знать, как это работает на низком уровне, вы всегда можете проверить источник ! Код немного сбивает с толку, если вы не привыкли читать код ядра, но встроенные комментарии дают довольно хороший совет о том, что происходит.

Самая интересная часть источника с явным ответом на ваш вопрос находится в самом конце самого определения fork () -

if (error == 0) {
    td->td_retval[0] = p2->p_pid;
    td->td_retval[1] = 0;
}

"td", по-видимому, содержит список возвращаемых значений для разных потоков. Я не уверен, как именно работает этот механизм (почему нет двух отдельных «потоковых» структур). Если ошибка (возвращаемая из fork1, «реальной» функции разветвления) равна 0 (без ошибок), тогда возьмите «первый» (родительский) поток и установите его возвращаемое значение равным P2 (PID нового процесса). Если это «второй» поток (в p2), тогда установите возвращаемое значение равным 0.

7 голосов
/ 23 мая 2014

И родитель, и потомок возвращают разные значения из-за манипулирования регистрами ЦП в контексте дочернего элемента.

Каждый процесс в ядре Linux представлен task_struct. task_struct заключена (указатель) в структуру thread_info, которая находится в конце стека режима ядра. Весь контекст CPU (регистры) хранится в этой структуре thread_info.

struct thread_info {
    struct task_struct  *task;      /* main task structure */
    struct cpu_context_save cpu_context;    /* cpu context */
}

Все системные вызовы fork / clone () вызывают эквивалентную ядру функцию do_fork ().

long do_fork(unsigned long clone_flags,
          unsigned long stack_start,
          struct pt_regs *regs,
          unsigned long stack_size,
          int __user *parent_tidptr,
          int __user *child_tidptr)

Вот последовательность выполнения

do_fork () -> copy_process-> copy_thread () (copy_thread - это вызов функции для конкретной арки)

copy_thread () копирует значения регистра из родительского элемента и изменяет возвращаемое значение на 0 (В случае руки)

struct pt_regs *childregs = task_pt_regs(p); 
*childregs = *regs; /* Copy  register value from parent process*/
childregs->ARM_r0 = 0; /*Change the return value*/
thread->cpu_context.sp = (unsigned long)childregs;/*Write back the value to thread info*/
thread->cpu_context.pc = (unsigned long)ret_from_fork;

Когда дочерний элемент назначается по расписанию, он выполняет процедуру сборки ret_from_fork (), которая возвращает ноль. Для родителя он получает возвращаемое значение от do_fork (), которая является pid процесса

nr = task_pid_vnr(p);
return nr;
7 голосов
/ 01 апреля 2010

Системный вызов fork() возвращается дважды (если не происходит сбой).

  • Одно из возвращаемых значений находится в дочернем процессе, и там возвращаемое значение равно 0.

  • Другой возврат находится в родительском процессе, и там возвращаемое значение ненулевое (либо отрицательное, если произошел сбой форка, либо ненулевое значение, указывающее PID дочернего элемента).

Основные различия между родителем и ребенком:

  • Это отдельные процессы
  • Значение PID другое
  • Значение PPID (родительский PID) отличается

Другие более неясные различия перечислены в стандарте POSIX .

В некотором смысле, Как действительно не ваша проблема. Операционная система требуется для достижения результата. Однако o / s клонирует родительский процесс, создавая второй дочерний процесс, который является почти точной копией родительского процесса, устанавливая атрибуты, которые должны отличаться от правильных новых значений, и обычно помечая страницы данных как CoW (копировать на запись) или эквивалентный, так что когда один процесс изменяет значение, он получает отдельную копию страницы, чтобы не мешать другому. Это не похоже на устаревший (по крайней мере, для меня - нестандартный для POSIX) системный вызов vfork(), который вам будет разумно избегать, даже если он доступен в вашей системе. Каждый процесс продолжается после fork(), как если бы функция возвращалась - так (как я уже сказал сверху), системный вызов fork() возвращается дважды, по одному в каждом из двух процессов, которые находятся рядом с идентичными клонами друг друга.

3 голосов
/ 01 апреля 2010

Ответ Стивена Шланскера довольно хорош, но просто добавим некоторые подробности:

Каждый выполняющийся процесс имеет связанный контекст (отсюда и «переключение контекста») - этот контекст включает, помимо прочего, код процессасегмент (содержащий машинные инструкции), его куча памяти, его стек и его содержимое регистра.Когда происходит переключение контекста, контекст из старого процесса сохраняется, и загружается контекст из нового процесса.

Местоположение для возвращаемого значения определяется ABI, чтобы обеспечить возможность взаимодействия кода.Если я пишу код ASM для моего процессора x86-64 и обращаюсь к среде выполнения C, я знаю, что возвращаемое значение будет отображаться в регистре RAX.

Соединяя эти две вещи вместе,логический вывод состоит в том, что вызов int pid = fork() приводит к двум контекстам, в которых следующая инструкция, которая должна быть выполнена в каждой из них, является той, которая перемещает значение RAX (возвращаемого значения из вызова fork) в локальную переменную pid.Разумеется, только один процесс может одновременно выполняться на одном процессоре, поэтому планировщик определяет порядок, в котором происходит «возврат».

1 голос
/ 12 марта 2013

Я постараюсь ответить с точки зрения структуры памяти процесса. Ребята, пожалуйста, поправьте меня, если что-то не так или неточно.

fork () - это единственный системный вызов для создания процесса (кроме самого начального процесса 0), поэтому вопрос в том, что на самом деле происходит с созданием процесса в ядре. Существуют две структуры данных ядра, связанные с процессом, массив struct proc (он же таблица процессов) и struct user (он же область).

Чтобы создать новый процесс, эти две структуры данных должны быть правильно созданы или параметризованы. Прямой путь заключается в выравнивании с областью процедур создателя (или родителя). Большая часть данных дублируется между parent и child (например, сегментом кода), за исключением значений в регистре возврата (например, EAX в 80x86), для которого parent имеет pid дочернего элемента, а child равен 0. С тех пор у вас есть два процесса ( существующий и новый), выполняемый планировщиком, и при планировании каждый из них возвращает свои значения соответственно.

0 голосов
/ 01 апреля 2010

Процесс выглядит идентичным с обеих сторон, за исключением различного возвращаемого значения (поэтому возвращаемое значение есть, так что два процесса могут различить разницу вообще!). Что касается дочернего процесса, он будет только что возвращен из system_call так же, как и родительский процесс.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...