Какова цель fork ()?


87

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


151
Чтобы все эти обедающие философы не умирали с голоду.
kenj0418

Ответы:


107

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

Вот несколько примеров использования fork:

  1. Ваша оболочка использует forkдля запуска программ, которые вы вызываете из командной строки.
  2. Веб-серверы, такие как apache, используют forkдля создания нескольких серверных процессов, каждый из которых обрабатывает запросы в собственном адресном пространстве. Если один умирает или утекает память, это не влияет на другие, поэтому он действует как механизм отказоустойчивости.
  3. Google Chrome использует forkдля обработки каждой страницы в рамках отдельного процесса. Это не позволит клиентскому коду на одной странице вывести из строя весь браузер.
  4. forkиспользуется для порождения процессов в некоторых параллельных программах (например, написанных с использованием MPI ). Обратите внимание, что это отличается от использования потоков , которые не имеют собственного адресного пространства и существуют внутри процесса.
  5. Языки сценариев forkкосвенно используют для запуска дочерних процессов. Например, каждый раз, когда вы используете команду, как subprocess.Popenв Python, вы forkстановитесь дочерним процессом и читаете его вывод. Это позволяет программам работать вместе.

Типичное использование forkв оболочке может выглядеть примерно так:

int child_process_id = fork();
if (child_process_id) {
    // Fork returns a valid pid in the parent process.  Parent executes this.

    // wait for the child process to complete
    waitpid(child_process_id, ...);  // omitted extra args for brevity

    // child process finished!
} else {
    // Fork returns 0 in the child process.  Child executes this.

    // new argv array for the child process
    const char *argv[] = {"arg1", "arg2", "arg3", NULL};

    // now start executing some other program
    exec("/path/to/a/program", argv);
}

Оболочка порождает дочерний процесс с использованием execи ожидает его завершения, а затем продолжает свое выполнение. Обратите внимание, что вам не обязательно использовать fork таким образом. Вы всегда можете запустить множество дочерних процессов, как это может делать параллельная программа, и каждый из них может запускать программу одновременно. По сути, каждый раз, когда вы создаете новые процессы в системе Unix, вы используете fork(). Для эквивалента Windows, посмотрите CreateProcess.

Если вам нужно больше примеров и более подробное объяснение, в Википедии есть достойное резюме. А вот несколько слайдов о том, как процессы, потоки и параллелизм работают в современных операционных системах.


Пуля 5: «часто»? Только «часто»? Какие из них не используют его, или при каких обстоятельствах fork () не используется - то есть в системах, поддерживающих fork ().
Джонатан Леффлер,

19
Как ни странно, он называется CreateProcess () - эти сумасшедшие парни из Windows :-)
paxdiablo

2
до сих пор никогда не осознавал, что «оболочка использует fork для запуска программ, которые вы вызываете из командной строки»!
Lazer

1
Ссылка на слайды не работает
Piertoni

1
Все ответы говорят , что fork()это путь , чтобы создать новый процесс в UNIX, но быть педантичным, есть по крайней мере один другой: posix_spawn().
Дэвислор

15

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

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

$ telnetd &

Что касается причины, по которой вы создаете новые процессы, это то, как ваша операционная система может делать много вещей одновременно. Вот почему вы можете запустить программу и, пока она запущена, переключиться в другое окно и сделать что-нибудь еще.


@varDumper Хороший улов!
Дэниел С. Собрал

9

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

Если возвращаемое значение равно 0, вы знаете, что вы дочерний процесс, а если возвращаемое значение является числом (которое оказывается идентификатором дочернего процесса), вы знаете, что являетесь родителем. (и если это отрицательное число, форк не удался и дочерний процесс не был создан)

http://www.yolinux.com/TUTORIALS/ForkExecProcesses.html


1
Если возвращаемое значение не равно -1, и в этом случае fork () завершился ошибкой.
Джонатан Леффлер,

8

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

pid=fork()
if pid==0
//this is the child process
else if pid!=0
//this is the parent process

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


6

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


почему вы создаете ребенка, который идентичен родителю, в чем польза?

1
Это похоже на построение армии против одного солдата. Вы выполняете форк, чтобы ваша программа могла обрабатывать больше запросов одновременно, а не один за другим.
cloudhead

fork () возвращает 0 для дочернего элемента и pid дочернего элемента для родительского. Затем дочерний элемент может использовать такой вызов, как exec (), чтобы заменить свое состояние новой программой. Так запускаются программы.
Тодд Гэмблин,

Процессы очень близки к идентичным, но есть много тонких отличий. Очевидные различия - это текущий PID и родительский PID. Существуют проблемы, связанные с удерживаемыми блокировками и удерживаемыми семафорами. На странице руководства fork () для POSIX перечислены 25 различий между родителем и потомком.
Джонатан Леффлер,

2
@kar: Если у вас есть два процесса, они могут продолжаться отдельно оттуда, и один из них может полностью заменить себя (exex ()) другой программой.
Vatine

4

Вам, вероятно, не нужно использовать fork в повседневном программировании, если вы пишете приложения.

Даже если вы действительно хотите, чтобы ваша программа запускала другую программу для выполнения какой-либо задачи, существуют другие более простые интерфейсы, которые используют fork за кулисами, такие как «система» в C и perl.

Например, если вы хотите, чтобы ваше приложение запускало другую программу, такую ​​как bc, для выполнения за вас некоторых вычислений, вы можете использовать "system" для ее запуска. Система выполняет «вилку», чтобы создать новый процесс, затем «exec», чтобы превратить этот процесс в bc. После завершения bc система возвращает управление вашей программе.

Вы также можете запускать другие программы асинхронно, но я не могу вспомнить, как.

Если вы пишете серверы, оболочки, вирусы или операционные системы, вы, скорее всего, захотите использовать fork.


Спасибо за system(). Я читал об этом, fork()потому что хочу, чтобы мой код C запускал скрипт python.
Bean Taxi,

4

Форк создает новые процессы. Без форка у вас была бы система unix, которая могла бы запускать только init.


4

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

Если fork () возвращает отрицательное значение, создание дочернего процесса не удалось. fork () возвращает ноль вновь созданному дочернему процессу. fork () возвращает родителю положительное значение, идентификатор дочернего процесса. Возвращаемый идентификатор процесса имеет тип pid_t, определенный в sys / types.h. Обычно идентификатор процесса является целым числом. Более того, процесс может использовать функцию getpid () для получения идентификатора процесса, назначенного этому процессу. Следовательно, после системного вызова fork () простой тест может определить, какой процесс является дочерним. Обратите внимание, что Unix сделает точную копию адресного пространства родителя и передаст его потомку. Следовательно, родительский и дочерний процессы имеют отдельные адресные пространства.

Давайте разберемся с этим на примере, чтобы прояснить вышеупомянутые моменты. В этом примере не различаются родительский и дочерний процессы.

#include  <stdio.h>
#include  <string.h>
#include  <sys/types.h>

#define   MAX_COUNT  200
#define   BUF_SIZE   100

void  main(void)
{
     pid_t  pid;
     int    i;
     char   buf[BUF_SIZE];

     fork();
     pid = getpid();
     for (i = 1; i <= MAX_COUNT; i++) {
          sprintf(buf, "This line is from pid %d, value = %d\n", pid, i);
          write(1, buf, strlen(buf));
     } 
}

Предположим, что указанная выше программа выполняется до момента вызова fork ().

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

pid = .....;

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

В чем причина использования write, а не printf? Это потому, что printf () «буферизируется», то есть printf () группирует вывод процесса вместе. При буферизации вывода для родительского процесса дочерний процесс также может использовать printf для вывода некоторой информации, которая также будет буферизована. В результате, поскольку выходные данные не будут отправлены на экран немедленно, вы можете не получить ожидаемый результат в правильном порядке. Хуже того, выходные данные двух процессов могут странным образом смешиваться. Чтобы решить эту проблему, вы можете рассмотреть возможность использования «небуферизованной» записи.

Если вы запустите эту программу, вы можете увидеть на экране следующее:

................
This line is from pid 3456, value 13
This line is from pid 3456, value 14
     ................
This line is from pid 3456, value 20
This line is from pid 4617, value 100
This line is from pid 4617, value 101
     ................
This line is from pid 3456, value 21
This line is from pid 3456, value 22
     ................

Идентификатор процесса 3456 может быть тем, который назначен родительскому или дочернему. Из-за того, что эти процессы выполняются одновременно, их выходные строки перемешиваются довольно непредсказуемым образом. Причем порядок этих строк определяется планировщиком ЦП. Следовательно, если вы снова запустите эту программу, вы можете получить совершенно другой результат.


3
Вместо копирования текста вы могли бы прокомментировать ссылку: csl.mtu.edu/cs4411.ck/www/NOTES/process/fork/create.html
chaitanya lakkundi

3

Многопроцессорность играет центральную роль в вычислениях. Например, ваш IE или Firefox может создать процесс загрузки файла для вас, пока вы все еще просматриваете Интернет. Или, когда вы распечатываете документ в текстовом редакторе, вы все равно можете просматривать разные страницы и по-прежнему редактировать их.


3

Fork () используется для создания новых процессов, как написано каждым телом.

Вот мой код, который создает процессы в виде двоичного дерева ....... Он попросит просканировать количество уровней, до которых вы хотите создать процессы в двоичном дереве.

#include<unistd.h> 
#include<fcntl.h> 
#include<stdlib.h>   
int main() 
{
int t1,t2,p,i,n,ab;
p=getpid();                
printf("enter the number of levels\n");fflush(stdout);
scanf("%d",&n);                
printf("root %d\n",p);fflush(stdout);
for(i=1;i<n;i++)    
{        
    t1=fork();

    if(t1!=0)
        t2=fork();        
    if(t1!=0 && t2!=0)        
        break;            
    printf("child pid %d   parent pid %d\n",getpid(),getppid());fflush(stdout);
}   
    waitpid(t1,&ab,0);
    waitpid(t2,&ab,0);
return 0;
}

ВЫХОД

  enter the number of levels
  3
  root 20665
  child pid 20670   parent pid 20665
  child pid 20669   parent pid 20665
  child pid 20672   parent pid 20670
  child pid 20671   parent pid 20670
  child pid 20674   parent pid 20669
  child pid 20673   parent pid 20669

2

Сначала нужно понять, что такое системный вызов fork (). Позволь мне объяснить

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

  2. Системный вызов Fork копирует память на основе копирования при записи, что означает, что дочерний элемент создает страницу виртуальной памяти, когда требуется копирование.

Теперь цель fork ():

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

1

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

fork()по-прежнему используется некоторыми серверными приложениями, в основном теми, которые работают с правами root на машине * NIX, которые отбрасывают разрешения перед обработкой пользовательских запросов. Есть еще несколько вариантов использования, но сейчас в основном люди перешли на многопоточность.


2
Я не понимаю мнения, что «большинство людей» перешло на многопоточность. Процессы здесь, чтобы остаться, и потоки тоже. Ни от кого не «ушел» никто. В параллельном программировании самые большие и наиболее параллельные коды являются многопроцессорными программами с распределенной памятью (например, MapReduce и MPI). Тем не менее, большинство людей выберут OpenMP или некоторую парадигму с общей памятью для многоядерной машины, и в наши дни графические процессоры используют потоки, но есть еще много чего. Бьюсь об заклад, что больше программистов на этом сайте сталкиваются с параллелизмом процессов на стороне сервера, чем с чем-либо многопоточным.
Тодд Гэмблин,

1

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

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

В операционной системе Windows, напротив, программисты должны использовать функцию CreateProcess, которая НАМНОГО сложнее и требует заполнения разнообразной структуры для определения параметров нового процесса.

Подводя итог, можно сказать, что причиной разветвления (по сравнению с exec'ing) является простота создания новых процессов.


0

Системный вызов Fork () используется для создания дочернего процесса. Это точный дубликат родительского процесса. Форк копирует раздел стека, раздел кучи, раздел данных, переменную среды, аргументы командной строки из родительского элемента.

см .: http://man7.org/linux/man-pages/man2/fork.2.html


0

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

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

  • У дочернего процесса есть идентификатор родительского процесса, который совпадает с PID
    процесса, который его создал.

  • Счетчики использования ресурсов и времени ЦП обнуляются в дочернем процессе.

  • Набор ожидающих сигналов в дочернем элементе пуст.

  • Ребенок не наследует таймеры от своего родителя

Пример :

    #include <unistd.h>
    #include <sys/types.h>
    #include <errno.h>
    #include <stdio.h>
    #include <sys/wait.h>
    #include <stdlib.h>

    int var_glb; /* A global variable*/

int main(void)
{
    pid_t childPID;
    int var_lcl = 0;

    childPID = fork();

    if(childPID >= 0) // fork was successful
    {
        if(childPID == 0) // child process
        {
            var_lcl++;
            var_glb++;
            printf("\n Child Process :: var_lcl = [%d], var_glb[%d]\n", var_lcl, var_glb);
        }
        else //Parent process
        {
            var_lcl = 10;
            var_glb = 20;
            printf("\n Parent process :: var_lcl = [%d], var_glb[%d]\n", var_lcl, var_glb);
        }
    }
    else // fork failed
    {
        printf("\n Fork failed, quitting!!!!!!\n");
        return 1;
    }

    return 0;
}

Теперь, когда приведенный выше код скомпилирован и запущен:

$ ./fork

Parent process :: var_lcl = [10], var_glb[20]

Child Process :: var_lcl = [1], var_glb[1]
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.