Как fork() возвращает дочерний процесс - программирование
Подтвердить что ты не робот

Как fork() возвращает дочерний процесс

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

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

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

4b9b3361

Ответ 1

% 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.

Ответ 2

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

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

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

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

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

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

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

Ответ 3

Оба родителя и дочернего объекта возвращают разные значения из-за манипуляции с регистром процессора в детском контексте.

Каждый процесс в ядре linux представлен task_struct. task_struct заключен в оболочку (указатель) в структуре thread_info, которая находится в конце стека режима ядра. В этой структуре 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;

Ответ 4

Ответ Steven Schlansker неплохой, но просто добавим несколько деталей:

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

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

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

Ответ 5

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

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

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

Ответ 6

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