Различия между форком и exec - PullRequest
       86

Различия между форком и exec

188 голосов
/ 31 октября 2009

Каковы различия между fork и exec?

Ответы [ 9 ]

345 голосов
/ 31 октября 2009

Использование fork и exec иллюстрирует дух UNIX в том смысле, что он обеспечивает очень простой способ запуска новых процессов.

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

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

Вызов exec - это способ в основном заменить весь текущий процесс новой программой. Он загружает программу в текущее пространство процесса и запускает ее из точки входа.

Итак, fork и exec часто используются последовательно, чтобы новая программа работала как дочерний элемент текущего процесса. Оболочки обычно делают это всякий раз, когда вы пытаетесь запустить такую ​​программу, как find - оболочка разветвляется, затем дочерний элемент загружает в память программу find, настраивая все аргументы командной строки, стандартный ввод-вывод и т. Д.

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

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

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

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

Обратите внимание, что существует целое семейство exec вызовов (execl, execle, execve и т. Д.), Но exec в контексте здесь означает любой из них.

Следующая диаграмма иллюстрирует типичную операцию fork/exec, в которой оболочка bash используется для отображения каталога с помощью команды ls:

+--------+
| pid=7  |
| ppid=4 |
| bash   |
+--------+
    |
    | calls fork
    V
+--------+             +--------+
| pid=7  |    forks    | pid=22 |
| ppid=4 | ----------> | ppid=7 |
| bash   |             | bash   |
+--------+             +--------+
    |                      |
    | waits for pid 22     | calls exec to run ls
    |                      V
    |                  +--------+
    |                  | pid=22 |
    |                  | ppid=7 |
    |                  | ls     |
    V                  +--------+
+--------+                 |
| pid=7  |                 | exits
| ppid=4 | <---------------+
| bash   |
+--------+
    |
    | continues
    V
53 голосов
/ 31 октября 2009

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

 int pid = fork();

 if (pid == 0)
 {
     printf("I'm the child");
 }
 else
 {
     printf("I'm the parent, my child is %i", pid);
     // here we can kill the child, but that's not very parently of us
 }

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

exec немного легче понять, вы просто указываете exec выполнить процесс, используя целевой исполняемый файл, и у вас нет двух процессов, выполняющих один и тот же код или наследующих одно и то же состояние. Как говорит @Steve Hawkins, exec может использоваться после fork для выполнения в текущем процессе целевого исполняемого файла.

31 голосов
/ 07 апреля 2010

Я думаю, что некоторые концепции из "Расширенного программирования Unix" Марка Рочкинда были полезны для понимания различных ролей fork() / exec(), особенно для тех, кто привык к модели Windows CreateProcess():

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

.

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

.

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

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

  • fork() создает копию текущего процесса
  • exec() заменяет программу в текущем процессе другой программой

(по сути, это упрощенная версия "для чайников" гораздо более подробный ответ Паксиабло )

28 голосов
/ 22 мая 2013

Fork создает копию вызывающего процесса. как правило, следует структуре enter image description here

int cpid = fork( );

if (cpid = = 0) 
{

  //child code

  exit(0);

}

//parent code

wait(cpid);

// end

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

EXEC заменяет текущий процесс новым кодом процесса, данными, стеком. как правило, следует структуре enter image description here

int cpid = fork( );

if (cpid = = 0) 
{

  //child code

  exec(foo);

  exit(0);

}

//parent code

wait(cpid);

// end

(после вызова exec ядро ​​unix очищает текст, данные, стек дочернего процесса и заполняет текст / данные, относящиеся к процессу foo) таким образом, дочерний процесс с другим кодом (код foo {не совпадает с родительским})

7 голосов
/ 31 октября 2009

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

Процесс идет примерно так:

child = fork();  //Fork returns a PID for the parent process, or 0 for the child, or -1 for Fail

if (child < 0) {
    std::cout << "Failed to fork GUI process...Exiting" << std::endl;
    exit (-1);
} else if (child == 0) {       // This is the Child Process
    // Call one of the "exec" functions to create the child process
    execvp (argv[0], const_cast<char**>(argv));
} else {                       // This is the Parent Process
    //Continue executing parent process
}
4 голосов
/ 31 октября 2009

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

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

2 голосов
/ 01 января 2019

Основное различие между fork() и exec() заключается в том, что

Системный вызов fork() создает клон запущенной в данный момент программы. Исходная программа продолжает выполнение со следующей строки кода после вызова функции fork (). Клон также начинает выполнение со следующей строки кода. Посмотрите на следующий код, который я получил от http://timmurphy.org/2014/04/26/using-fork-in-cc-a-minimum-working-example/

#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv)
{
    printf("--beginning of program\n");
    int counter = 0;
    pid_t pid = fork();
    if (pid == 0)
    {
        // child process
        int i = 0;
        for (; i < 5; ++i)
        {
            printf("child process: counter=%d\n", ++counter);
        }
    }
    else if (pid > 0)
    {
        // parent process
        int j = 0;
        for (; j < 5; ++j)
        {
            printf("parent process: counter=%d\n", ++counter);
        }
    }
    else
    {
        // fork failed
        printf("fork() failed!\n");
        return 1;
    }
    printf("--end of program--\n");
    return 0;
}

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

--beginning of program
parent process: counter=1
parent process: counter=2
parent process: counter=3
child process: counter=1
parent process: counter=4
child process: counter=2
parent process: counter=5
child process: counter=3
--end of program--
child process: counter=4
child process: counter=5
--end of program--

Семейство системных вызовов exec() заменяет текущий исполняемый код процесса другим фрагментом кода. Процесс сохраняет свой PID, но становится новой программой. Например, рассмотрим следующий код:

#include <stdio.h> 
#include <unistd.h> 
main() {
 char program[80],*args[3];
 int i; 
printf("Ready to exec()...\n"); 
strcpy(program,"date"); 
args[0]="date"; 
args[1]="-u"; 
args[2]=NULL; 
i=execvp(program,args); 
printf("i=%d ... did it work?\n",i); 
} 

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

Ready to exec()... 
Tue Jul 15 20:17:53 UTC 2008 

Программа выводит строку adyЧтобы выполнить exec (). , , ‖ И после вызова функции execvp () заменяет ее код программой date. Обратите внимание, что линия -. , , сделал это работает? не отображается, потому что в этот момент код был заменен. Вместо этого мы видим результат выполнения ―date -u.‖

1 голос
/ 27 ноября 2015

enter image description herefork():

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

  1. fork() возвращает идентификатор процесса (pid) дочернего процесса в родительском

  2. fork() возвращает 0 у ребенка.

exec():

Он инициирует новый процесс внутри процесса. Он загружает новую программу в текущий процесс, заменяя существующую.

fork() + exec():

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

int main( void ) 
{
    int pid = fork();
    if ( pid == 0 ) 
    {
        execvp( "find", argv );
    }

    //Put the parent to sleep for 2 sec,let the child finished executing 
    wait( 2 );

    return 0;
}
0 голосов
/ 03 октября 2017

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

Для многих команд оболочка forks и дочерний процесс execs команда, связанная с name обрабатывает оставшиеся слова в командной строке как параметры команды.

Оболочка допускает три типа команд. Во-первых, команда может быть исполняемый файл , который содержит объектный код, полученный путем компиляции исходного кода (например, программа на C). Во-вторых, команда может быть исполняемым файлом, который содержит последовательность командных строк оболочки. Наконец, команда может быть внутренней командой оболочки (вместо исполняемого файла ex -> cd , ls и т. Д.)

...