Как работает fork ()?

Im действительно новый для разветвления, что делает pid в этом коде? Может кто-нибудь объяснить, что выходит в строке X и строке Y ?

#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#define SIZE 5
int nums[SIZE] = {0,1,2,3,4};
int main()
{
    int i;
    pid_t pid;
    pid = fork();
    if (pid == 0) {
        for (i = 0; i < SIZE; i++) {
            nums[i] *= -i;
            printf("CHILD: %d ",nums[i]); /* LINE X */
        }
    }
    else if (pid > 0) {
        wait(NULL);
        for (i = 0; i < SIZE; i++)
            printf("PARENT: %d ",nums[i]); /* LINE Y */
    }
    return 0;
}

5 ответов


fork() дублирует процесс, поэтому после вызова fork на самом деле есть 2 экземпляра вашей программы.

как вы знаете, какой процесс является исходным (родительским), а какой-новым (дочерним)?

в Родительском процессе PID дочернего процесса (который будет положительным целым числом) возвращается из fork(). Вот почему if (pid > 0) { /* PARENT */ } код работает. В процессе ребенка, fork() просто возвращает 0.

таким образом, из-за if (pid > 0) проверка, родительский процесс и дочерний процесс будут производить разные выходные данные, которые вы можете увидеть здесь (как предусмотрено @jxh в комментариях).


самый простой пример для fork ()

printf("I'm printed once!\n");
fork();
// Now there are two processes running one is parent and another child.
// and each process will print out the next line.
printf("You see this line twice!\n");

возвращаемое значение fork(). Возвращаемое значение -1= failed; 0= in child process; positive = in parent process (а возвращаемое значение-идентификатор дочернего процесса)

pid_t id = fork();
if (id == -1) exit(1); // fork failed 
if (id > 0)
{ 
// I'm the original parent and 
// I just created a child process with id 'id'
// Use waitpid to wait for the child to finish
} else { // returned zero
// I must be the newly made child process
}

чем отличается дочерний процесс от родительского?

  • родитель уведомляется через сигнал, когда дочерний процесс завершается, но не наоборот.
  • ребенок не наследует отложенные сигналы или таймер сигнализация. Полный список см. В разделе вызов Fork()
  • здесь идентификатор процесса может быть возвращен getpid (). Идентификатор родительского процесса может быть возвращен getppid ().

теперь давайте визуализировать ваш программный код

pid_t pid;
pid = fork();

теперь ОС делает две идентичные копии адресных пространств, одну для родителя, а другую для ребенка.

enter image description here

как родительский, так и дочерний процесс начинают свое выполнение прямо после системного вызова Fork(). Поскольку оба процесса имеют одинаковые, но отдельные адресные пространства, эти переменные, инициализированные перед вызовом fork (), имеют одинаковые значения в обоих адресных пространствах. Каждый процесс имеет свое собственное адресное пространство, поэтому любые изменения будут независимы от других. Если родитель изменяет значение своей переменной, изменение повлияет только на переменную в адресном пространстве родительского процесса. Другие адресные пространства, созданные вызовами Fork () sysem, не будут затронуты даже хотя у них одинаковые имена переменных .

enter image description here

здесь Родительский pid ненулевой, он вызывает функцию ParentProcess (). С другой стороны, у ребенка есть нулевой pid, и он вызывает ChildProcess() как показано ниже: enter image description here

в вашем коде родительский процесс вызова wait() он останавливается в этом месте, пока ребенок не выходит. Таким образом, вывод ребенка появляется первым.

if (pid == 0) {                    
    // The child runs this part because fork returns 0 to the child
    for (i = 0; i < SIZE; i++) {
        nums[i] *= -i;
        printf("CHILD: %d ",nums[i]); /* LINE X */
    }
}

вывод из дочернего элемента процесс

что выходит в строке X

 CHILD: 0 CHILD: -1 CHILD: -4 CHILD: -9 CHILD: -16

затем после выхода ребенка родитель продолжает после вызова wait () и печатает его вывод далее.

else if (pid > 0) {
        wait(NULL);
        for (i = 0; i < SIZE; i++)
            printf("PARENT: %d ",nums[i]); /* LINE Y */
    }

вывод из родительского процесса:

что выходит в строке Y

PARENT: 0 PARENT: 1 PARENT: 2 PARENT: 3 PARENT: 4

наконец оба выхода, объединенные дочерним и родительским процессом, будут показаны на терминале как следуйте:

 CHILD: 0 CHILD: -1 CHILD: -4 CHILD: -9 CHILD: -16 PARENT: 0 PARENT: 1 PARENT: 2 PARENT: 3 PARENT: 4

дополнительная информация см. ссылку


на fork() функция особенная, потому что она фактически возвращается дважды: один раз в родительский процесс и один раз в дочерний процесс. В Родительском процессе, fork() возвращает pid ребенка. В дочернем процессе он возвращает 0. В случае ошибки дочерний процесс не создается и родительскому процессу возвращается значение -1.

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

более подробно рассмотрим пример в вопросе:

pid_t pid;
pid = fork();
// When we reach this line, two processes now exist,
// with each one continuing to run from this point
if (pid == 0) {                    
    // The child runs this part because fork returns 0 to the child
    for (i = 0; i < SIZE; i++) {
        nums[i] *= -i;
        printf("CHILD: %d ",nums[i]); /* LINE X */
    }
}
else if (pid > 0) {
    // The parent runs this part because fork returns the child's pid to the parent
    wait(NULL);     // this causes the parent to wait until the child exits
    for (i = 0; i < SIZE; i++)
        printf("PARENT: %d ",nums[i]); /* LINE Y */
}

это выведет следующее:

CHILD: 0 CHILD: -1 CHILD: -4 CHILD: -9 CHILD: -16 PARENT: 0 PARENT: 1 PARENT: 2 PARENT: 3 PARENT: 4

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


fork() - это вызов функции, которая создает процесс. Процесс, который вызывает fork() называется родительский процесс, а вновь созданный процесс называется дочерний процесс.

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

на родительский процесс, pid - это Child process ID(идентификатор процесса вновь созданного дочерний процесс).
В дочерний процесс, pid is 0.


ядро выполняет следующую последовательность операций для fork().

  1. он выделяет слот в таблице процессов для нового процесса.
  2. он присваивает уникальный ID номер дочерний процесс.
  3. это делает логическую копию контекста родительский процесс. Поскольку некоторые части процесса, такие как область текста, могут совместно использоваться процессы, ядро иногда может увеличить количество ссылок на область вместо копирования области в новый физический адрес в памяти,
  4. он увеличивает счетчики таблиц файлов и режимов для файлов, связанных с процесс.
  5. возвращает ID номер ребенка к родительский процесс и 0 значение дочерний процесс.


Теперь давайте посмотрим, что происходит в вашем коде, когда вы вызываете fork()
01: pid = fork();
02: if (pid == 0) {
03:     for (i = 0; i < SIZE; i++) {
04:         nums[i] *= -i;
05:         printf("CHILD: %d ",nums[i]); /* LINE X */
06:     }
07: }
08: else if (pid > 0) {
09:     wait(NULL);
10:     for (i = 0; i < SIZE; i++)
11:         printf("PARENT: %d ",nums[i]); /* LINE Y */
12: }

01 строку: fork() называется дочерний процесс это. fork() возвращает и возвращаемое значение сохраняется в pid.
[Примечание: поскольку в коде OP нет проверки ошибок, это будет обсуждаться позже]

02 строку: значение против значения 0. Обратите внимание, что эта проверка выполняется в обоих родительский процесс и вновь созданный дочерний процесс. Как упоминалось выше, значение pid будет 0 на дочерний процесс и child process ID на родительский процесс. Таким образом, это условие проверки оценивается в True на дочерний процесс и False на родительский процесс. Отсюда 03-07 строки выполнены в дочерний процесс.

строки 03-07: эти линии довольно прямо вперед. The num[] массив дочерний процесс изменить (nums[i] *= -i;), и распечатывается с помощью printf("CHILD: %d ",nums[i]);.

здесь следует отметить, что значения, которые печатаются, имеют num[] массив дочерний процесс. The num[] массив родительский процесс до сих пор же, как это было до.

здесь есть аккуратный трюк, называемый копирование при записи. Хотя это не задается в этом вопросе, все же это будет интересное чтение.

линия 08: эта строка теперь проверена в родительский процесс. Он не будет проверять в дочерний процесс как прошла успешно. Идентификатор процесса всегда является положительным числом, поэтому когда родительский процесс получил идентификатор процесса вновь созданного дочернего процесса, он всегда будет проходить тест else if (pid > 0), и войдите в блок.

[Примечание: это никогда не может быть 0, потому что 0 защищены. Читать здесь.]

строке 09: эта строка делает родительский процесс подождите, пока дочерний процесс завершил выполнение. Именно по этой причине вы увидите все printf() of дочерний процесс перед любым из printf() на родительский процесс.

строка 10-12: это тоже довольно вперед for цикл, который печатает значения num[] массив. Обратите внимание, что значения для родительский процесс. Как это было изменено дочерний процесс ранее, который владеет собственной копией массива num[].


, когда fork() не удается.

есть возможно, что fork() вызов может завершиться неудачей. В таком случае возвращаемое значение -1. Это также должно быть обработано для программы, чтобы быть правильным.

pid = fork();
if (pid == -1)
    perror("Fork failed");

некоторое содержание взято из книги дизайн операционной системы UNIX.


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

pid_t original = getpid();
pid_t pid = fork();
if (pid == -1)
{
    /* Failed to fork - one return */
    …handle error situation…
}
else if (pid == 0)
{
    /* Child process - distinct from original process */
    assert(original == getppid() || getppid() == 1);
    assert(original != getpid());
    …be childish here…
}
else
{
    /* Parent process - distinct from child process */
    assert(original != pid);
    …be parental here…
}

дочерний процесс является копией родителя. Он например, имеет один и тот же набор открытых файловых дескрипторов; каждый файловый дескриптор N, открытый в Родительском файле, открыт в дочернем файле, и они имеют одно и то же открытое файловое описание. Это означает, что если один из процессов изменяет позицию чтения или записи в файле, это также влияет на другой процесс. С другой стороны, если один из процессов закрывает файл, это не оказывает прямого влияния на файл в другом процессе.

это также означает, что если в стандарте были буферизованы данные Пакет ввода-вывода в Родительском процессе (например, некоторые данные были прочитаны из стандартного дескриптора входного файла (STDIN_FILENO) в буфер данных для stdin, то эти данные доступны как для родителя, так и для ребенка, и оба могут читать эти буферизованные данные, не влияя на другие, которые также будут видеть те же данные. С другой стороны, как только буферизованные данные считываются, если родитель читает другой буфер полный, который перемещает текущую позицию файла как для родителя, так и для ребенка, поэтому ребенок не будет затем посмотрите данные, которые только что прочитал родитель (но если ребенок также читает блок данных, родитель этого не увидит). Это может сбить с толку. Следовательно, обычно рекомендуется убедиться, что нет ожидающего стандартного ввода-вывода перед разветвлением - fflush(0) это один из способов сделать это.

в фрагменте кода assert(original == getppid() || getppid() == 1); допускает возможность того, что к моменту выполнения дочернего оператора родительский процесс может выйти, и в этом случае ребенок будет наследуется системным процессом, который обычно имеет PID 1 (я не знаю системы POSIX, где осиротевшие дети наследуются другим PID, но, вероятно, есть один).

другие общие ресурсы, такие как файлы, отображаемые на память, ни общей памяти, по-прежнему доступны в обоих. Последующее поведение сопоставленного с памятью файла зависит от параметров, используемых для создания сопоставления; MAP_PRIVATE означает, что два процесса имеют независимые копии данных и MAP_SHARED означает, что они используют одну и ту же копию данных, а изменения, внесенные одним процессом, будут видны в другом.

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

спецификация POSIX для fork() указывает различия в деталях:

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

  • дочерний процесс должен иметь уникальный идентификатор процесса.

  • идентификатор дочернего процесса также не должен совпадать с любым ID группы активных процессов.

  • дочерний процесс будет иметь другой идентификатор родительского процесса, который должен быть идентификатором вызывающего процесса.

  • дочерний процесс должен иметь собственную копию дескрипторов родительского файла. Каждый из файловых дескрипторов ребенка должен ссылаться на одно и то же открытое описание файла с соответствующим файловым дескриптором родителя.

  • дочерний процесс имеет собственная копия потоков open directory родителя. Каждый открытый поток каталогов в дочернем процессе может совместно использовать позиционирование потока каталогов с соответствующим потоком каталогов родительского.

  • дочерний процесс должен иметь собственную копию дескрипторов каталога сообщений родителя.

  • значения дочернего процесса tms_utime, tms_stime, tms_cutime и tms_cstime должно быть установлено на 0.

  • оставшееся время до тех пор, пока сигнал будильника не будет сброшен до нуля, и сигнал тревоги, если таковой имеется, не будет отменен; см. сигнал тревоги.

  • [XSI] ⌦ все значения semadj должны быть очищены. ⌫

  • блокировки файлов, установленные родительским процессом, не наследуются дочерним процессом.

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

  • [XSI] Interval интервальные таймеры должен быть сброшен в дочернем процессе. ⌫

  • любые семафоры, открытые в Родительском процессе, также должны быть открыты в дочернем процессе.

  • [ML] ⌦ дочерний процесс не должен наследовать блокировки памяти адресного пространства, установленные родительским процессом с помощью вызовов mlockall() или mlock(). ⌫

  • сопоставления памяти, созданные в Родительском, должны сохраняться в дочернем процессе. Map_private сопоставления наследуется от родителя также map_private сопоставления в дочернем, и любые изменения данных в этих сопоставлениях, сделанные родителем до вызова fork() должны быть видны ребенку. Любые изменения данных в сопоставлениях MAP_PRIVATE, сделанные родителем после fork() возврат должен быть виден только родителю. Изменения данных в сопоставлениях MAP_PRIVATE, сделанные дочерним элементом, должны быть видны только дочернему элементу.

  • [PS] ⌦ Для SCHED_FIFO и SCHED_RR политики планирования, дочерний процесс наследует политику и параметры приоритета родительского процесса во время