Які відмінності між fork
і exec
?
fork
це в основному клонування: O
Які відмінності між fork
і exec
?
fork
це в основному клонування: O
Відповіді:
Використання 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
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
виконаєте в поточному процесі цільовий виконуваний файл.
pid < 0
і fork()
виклик не вдався
Я думаю, що деякі концепції з «Розширеного програмування Unix» Марка Рочкінда були корисними для розуміння різних ролей fork()
/ exec()
, особливо для тих, хто звик до CreateProcess()
моделі Windows :
Програма представляє собою набір інструкцій і даних , які зберігаються в звичайному файлі на диску. (з 1.1.2 Програми, процеси та нитки)
.
Для запуску програми ядро спочатку пропонується створити новий процес , який є середовищем, в якому програма виконує. (також з 1.1.2 Програми, процеси та нитки)
.
Неможливо зрозуміти системні виклики exec або fork без повного розуміння відмінності між процесом і програмою. Якщо ці умови для вас нові, можливо, ви захочете повернутися назад і переглянути Розділ 1.1.2. Якщо ви готові продовжити зараз, ми узагальнимо розмежування в одному реченні: Процес - це середовище виконання, яке складається з інструкцій, даних користувача та сегментів системних даних, а також безлічі інших ресурсів, придбаних під час виконання , тоді як програма - це файл, що містить інструкції та дані, які використовуються для ініціалізації сегментів інструкцій та даних користувачів. (від 5.3
exec
системних дзвінків)
Після того, як ви зрозумієте різницю між програмою та процесом, поведінку fork()
та exec()
функції можна узагальнити як:
fork()
створює дублікат поточного процесуexec()
замінює програму в поточному процесі на іншу програму(це, по суті, спрощена версія "для манекенів" набагато більш детальної відповіді paxdiablo )
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 (не такий, як батьківський))
Вони використовуються разом для створення нового дочірнього процесу. По-перше, виклик 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
}
fork () створює копію поточного процесу, причому виконання у новому дочірньому розпочинається відразу після виклику fork (). Після fork () вони однакові, за винятком значення повернення функції fork (). (RTFM для більш детальної інформації.) Потім два процеси можуть розходитись ще далі, при цьому один не може перешкоджати іншому, за винятком можливо, через будь-які спільні файли.
exec () замінює поточний процес на новий. Це не має нічого спільного з fork (), за винятком того, що exec () часто слідує fork (), коли потрібне запускати інший дочірній процес, а не замінювати поточний.
Основна відмінність 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.‖
Це створює копію запущеного процесу. Запущений процес називається батьківським процесом , а новостворений процес називається дочірнім процесом . Спосіб розмежування двох полягає в перегляді повернутого значення:
fork()
повертає ідентифікатор процесу (pid) дочірнього процесу у батьків
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;
}
Прекрасний приклад для розуміння fork()
і exec()
концепцією є оболонкою , програма командного інтерпретатора , що користувачі , як правило , виконуються після входу в system.The оболонку інтерпретує перше слово командного рядка в якості команди імені
Для багатьох команд, оболонка розгортається і дочірній процес виконує команду, пов'язану з іменем, трактуючи решта слів у командному рядку як параметри для команди.
Оболонки дозволяє використовувати три типи команд. По-перше, командою може бути виконуваний файл, що містить об'єктний код, що утворюється при компіляції вихідного коду (наприклад, програма C). По-друге, командою може бути виконуваний файл, який містить послідовність командних рядків оболонки. Нарешті, команда може бути командою внутрішньої оболонки. (Замість виконуваного файлу ex-> cd , ls тощо)