Відмінності між fork та exec


199

Які відмінності між forkі exec?


3
Хороший, детальний підсумок вилок, exec та інших функцій управління процесами знаходиться за адресою yolinux.com/TUTORIALS/ForkExecProcess.html
Джонатан Фінгленд

9
@Justin, тому що ми хочемо , щоб стати SO місцем для програмування питань.
paxdiablo

4
@ Polaris878: о, це зараз! : D
Януш Ленар

так forkце в основному клонування: O
Себастьян Ходжас

Відповіді:


364

Використання 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

52

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виконаєте в поточному процесі цільовий виконуваний файл.


6
також є стан, коли pid < 0і fork()виклик не вдався
Джонатан Фінгленд

3
Це зовсім не заважає :-) Один фрагмент коду, який виконується двома процесами, відбувається щоразу, коли використовується спільна бібліотека або DLL.
paxdiablo

31

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

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

.

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

.

Неможливо зрозуміти системні виклики exec або fork без повного розуміння відмінності між процесом і програмою. Якщо ці умови для вас нові, можливо, ви захочете повернутися назад і переглянути Розділ 1.1.2. Якщо ви готові продовжити зараз, ми узагальнимо розмежування в одному реченні: Процес - це середовище виконання, яке складається з інструкцій, даних користувача та сегментів системних даних, а також безлічі інших ресурсів, придбаних під час виконання , тоді як програма - це файл, що містить інструкції та дані, які використовуються для ініціалізації сегментів інструкцій та даних користувачів. (від 5.3 execсистемних дзвінків)

Після того, як ви зрозумієте різницю між програмою та процесом, поведінку fork()та exec()функції можна узагальнити як:

  • fork() створює дублікат поточного процесу
  • exec() замінює програму в поточному процесі на іншу програму

(це, по суті, спрощена версія "для манекенів" набагато більш детальної відповіді paxdiablo )


29

Fork створює копію процесу виклику. як правило, слід структуру введіть тут опис зображення

int cpid = fork( );

if (cpid = = 0) 
{

  //child code

  exit(0);

}

//parent code

wait(cpid);

// end

(для дочірнього тексту тексту (код), даних, стека такий же, як процес виклику) дочірній процес виконує код у блоці if.

EXEC замінює поточний процес новим кодом, даними, стеком процесу. як правило, слід структуру введіть тут опис зображення

int cpid = fork( );

if (cpid = = 0) 
{   
  //child code

  exec(foo);

  exit(0);    
}

//parent code

wait(cpid);

// end

(після виклику exec виклику unix очищає дочірній процес, текст, дані, стек і заповнює тексти / дані, пов'язані з процесом foo), таким чином, дочірній процес має інший код (код foo (не такий, як батьківський))


1
Це трохи не пов’язано з питанням, але чи не викликає цей код вище перелічений стан, якщо дочірній процес закінчує свій код першим? У такому випадку батьківський процес буде навічно чекати, коли дитина припинить себе, правда?
stdout

7

Вони використовуються разом для створення нового дочірнього процесу. По-перше, виклик 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
}

2
У 7-му рядку згадується, що функція exec () створює дочірній процес. Це дійсно так, тому що fork () вже створив дочірній процес, а виклик exec () просто замінює програму нового створеного процесу
cbinder

4

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

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


3

Основна відмінність 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. Після виклику вилки у нас паралельно працюють два процеси, обидва збільшують власну версію лічильника. Кожен процес триватиме до завершення та виходу. Оскільки процеси протікають паралельно, ми не можемо знати, що закінчиться першим. Запуск цієї програми надрукує щось подібне до того, що показано нижче, хоча результати можуть відрізнятися від одного циклу до іншого.

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

Програма виводить рядок ―Ready to exec (). . . ‖ І після виклику функції execvp () замінює свій код програмою дати. Зауважимо, що рядок -. . . чи це працювало‖ не відображається, оскільки в цей момент код було замінено. Натомість ми бачимо результат виконання ―date -u.‖


1

введіть тут опис зображенняfork():

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

  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

Прекрасний приклад для розуміння fork() і exec()концепцією є оболонкою , програма командного інтерпретатора , що користувачі , як правило , виконуються після входу в system.The оболонку інтерпретує перше слово командного рядка в якості команди імені

Для багатьох команд, оболонка розгортається і дочірній процес виконує команду, пов'язану з іменем, трактуючи решта слів у командному рядку як параметри для команди.

Оболонки дозволяє використовувати три типи команд. По-перше, командою може бути виконуваний файл, що містить об'єктний код, що утворюється при компіляції вихідного коду (наприклад, програма C). По-друге, командою може бути виконуваний файл, який містить послідовність командних рядків оболонки. Нарешті, команда може бути командою внутрішньої оболонки. (Замість виконуваного файлу ex-> cd , ls тощо)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.