Різниця між fork (), vfork (), exec () та clone ()


198

Я шукав, щоб знайти різницю між цими чотирма в Google, і я очікував, що там буде величезна кількість інформації, але насправді не було чіткого порівняння між чотирма дзвінками.

Я почав намагатися скласти якийсь базовий оглядовий погляд на відмінності між цими системними викликами, і ось що я отримав. Чи є вся ця інформація правильною / я пропускаю щось важливе?

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

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

Vfork: Основна відмінність vfork від fork полягає в тому, що коли новий процес створюється з vfork (), батьківський процес тимчасово призупиняється, і дочірній процес може зайняти адресний простір батьків. Цей дивний стан речей продовжується до тих пір, поки дочірній процес або не завершиться, або зателефонує execve (), і тоді батьківський процес триває.

Це означає, що дочірній процес vfork () повинен бути обережним, щоб уникнути несподіваних змін змінних батьківського процесу. Зокрема, дочірній процес не повинен повертатися з функції, що містить виклик vfork (), і він не повинен викликати exit () (якщо йому потрібно вийти, він повинен використовувати _exit (); власне, це також стосується дитини нормальної вилки ()).

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

Clone :Клон, як вилка, створює новий процес. На відміну від fork, ці виклики дозволяють дочірньому процесу ділитися частинами контексту його виконання з процесом виклику, таким як простір пам'яті, таблиця дескрипторів файлів та таблиця обробників сигналів.

Коли дочірній процес створюється з клоном, він виконує додаток функції fn (arg). (Це відрізняється від fork, де виконання у дитини продовжується від точки початкового виклику fork.) Аргумент fn - це вказівник на функцію, яка викликається дочірнім процесом на початку її виконання. Аргумент arg передається функції fn.

Коли програма fn (arg) функція повертається, дочірній процес припиняється. Ціле число, повернене fn, є вихідним кодом для дочірнього процесу. Дочірній процес може також явно закінчуватися, викликаючи вихід (2) або після отримання фатального сигналу.

Інформація отримана форма:

Дякуємо, що знайшли час, щоб прочитати це! :)


2
Чому vfork не повинен викликати exit ()? Або не повертатись? Не виходить () просто використовувати _exit ()? Я також намагаюся зрозуміти :)
LazerSharks

2
@Gnuey: тому що потенційно (якщо він реалізований інакше fork(), ніж це в Linux, і, ймовірно, всі BSD), він займає адресний простір свого батька. Все, що вона робить, крім дзвінків execve()або _exit(), має великий потенціал, щоб зіпсувати батьків. Зокрема, exit()викликає atexit()обробники та інші "фіналізатори", наприклад: він промиває потоки stdio. Повернення від vfork()дитини потенційно (таке ж застереження, як і раніше) зіпсує батьківський стек.
ninjalj

Мені було цікаво, що відбувається з потоками батьківського процесу; Чи всі вони клоновані чи лише нитка, яка викликає forksyscall?
Мохаммед Джафар Машхаді

@LazerSharks vfork створює процес, що нагадує потоки, де обмінюється пам’ять без захисту від копіювання під час запису, тому виконання даних про стеки може втратити батьківський процес.
Ясен

Відповіді:


160
  • vfork()є застарілою оптимізацією. До хорошого управління пам’яттю fork()зробив повну копію пам’яті батьків, тому це було досить дорого. оскільки у багатьох випадках fork()слідкує за тим exec(), що відкидає поточну карту пам'яті та створює нову, це було марною витратою. У наш час fork()не копіює пам'ять; його просто встановлюють як "копіювати при записі", тому fork()+ exec()так само ефективно, як і vfork()+ exec().

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



22
vforkдозволяє уникнути необхідності тимчасового використання значно більшої кількості пам'яті лише для того, щоб можна було виконати exec, і це все-таки більш ефективно, ніж fork, навіть якщо не майже на високий ступінь. Таким чином, можна уникнути необхідності перезавантажувати пам’ять лише для того, щоб велика програма, що переслідує, породила процес дитини. Отже, не просто підвищення продуктивності, але це може зробити це практично можливим.
Дедуплікатор

5
Насправді я був свідком з перших вуст, як fork () далеко не дешевий, коли ваш RSS великий. Я припускаю, що це тому, що ядро ​​все ще має скопіювати всі таблиці сторінок.
Мартіна Феррарі

4
Він повинен скопіювати всі таблиці сторінок, встановити всю записувану пам’ять, яку можна записати, в обох процесах , змити TLB, а потім він повинен повернути всі зміни до батьківського (і знову змити TLB) exec.
zwol

3
vfork все ще корисний у cygwin (ядро, що емулює dll, що працює над Windows у Microsoft). cygwin не може реалізувати ефективну вилку, оскільки її в базовій ОС немає.
ctrl-alt-delor

81
  • execve() замінює поточний виконуваний образ іншим, завантаженим із виконуваного файлу.
  • fork() створює процес дитини.
  • vfork()це історично оптимізована версія fork(), призначена для використання, коли execve()викликається безпосередньо після fork(). Виявилося, що це добре працює в системах, що не належать до MMU (де fork()не можна ефективно працювати), а також при виконанні fork()процесів з величезним слідом пам'яті для запуску якоїсь невеликої програми (думаю, Java Runtime.exec()). POSIX стандартизував posix_spawn()заміщення цих останніх двох сучасних застосувань vfork().
  • posix_spawn()робить еквівалент a fork()/execve(), а також дозволяє деякий fd жонглювання між ними. Він повинен замінити fork()/execve(), в основному, для платформ, що не належать до MMU.
  • pthread_create() створює нову нитку.
  • clone()- специфічний для Linux виклик, який можна використовувати для впровадження всього, з fork()чого pthread_create(). Це дає багато контролю. Натхненний на rfork().
  • rfork()- специфічний виклик плану-9. Це повинен бути загальний виклик, який дозволяє отримати декілька ступенів спільного використання між повними процесами та потоками.

2
Дякую за те, що ви додали більше інформації, ніж про що насправді просили, це допомогло мені заощадити свій час
Neeraj

5
План 9 - така дражня.
JJ

1
Для тих, хто не може згадати, що означає MMU: "Блок управління пам'яттю" - подальше читання у Вікіпедії
mgarey

43
  1. fork()- створює новий дочірній процес, який є повною копією батьківського процесу. Дочірні та батьківські процеси використовують різні віртуальні адресні простори, які спочатку заповнюються однаковими сторінками пам'яті. Потім, коли обидва процеси виконуються, віртуальний адресний простір починає відрізнятися все більше і більше, оскільки операційна система виконує ледачу копіювання сторінок пам'яті, які записуються будь-яким з цих двох процесів і призначає незалежні копії змінених сторінок пам'ять для кожного процесу. Ця методика називається Copy-On-Write (COW).
  2. vfork()- створює новий дочірній процес, який є "швидкою" копією батьківського процесу. На відміну від системного виклику fork(), дочірні та батьківські процеси ділять однаковий віртуальний адресний простір. ПРИМІТКА! Використовуючи один і той же віртуальний простір адрес, і батько, і дитина використовують один і той же стек, покажчик стека та вказівник, як у випадку з класичним fork()! Для запобігання небажаних втручань між батьком і дочіркою, які використовують один і той же стек, виконання батьківського процесу буде заморожене, поки дитина не зателефонує exec()(створить новий віртуальний адресний простір та перехід до іншого стека), або _exit()(припинення виконання процесу ). vfork()- це оптимізація моделі fork()"fork-and-exec". Це можна виконати в 4-5 разів швидше, ніж на fork(), ніж на відміну відfork()(навіть з урахуванням COW), впровадження vfork()системного виклику не включає створення нового адресного простору (розподіл та налаштування нових каталогів сторінок).
  3. clone()- створює новий процес дитини. Різні параметри цього системного виклику визначають, які частини батьківського процесу необхідно скопіювати в дочірній процес та які частини будуть спільними між ними. Як результат, цей системний виклик може використовуватися для створення всіх видів об'єктів виконання, починаючи від потоків і закінчуючи повністю незалежними процесами. Насправді clone()системний виклик - це база, яка використовується для реалізації pthread_create()та всієї сімейства fork()системних викликів.
  4. exec()- скидає всю пам'ять процесу, завантажує та аналізує вказаний виконуваний двійковий файл, встановлює новий стек і передає управління в точку входу завантаженого виконуваного файлу. Цей системний виклик ніколи не повертає управління абоненту і служить для завантаження нової програми до вже існуючого процесу. Цей системний виклик із fork()системним викликом разом утворюють класичну модель управління процесами UNIX під назвою "fork-and-exec".

2
Зауважте, що вимоги BSD та POSIX до vforkтаких настільки слабкі, що було б законно вносити vforkсинонім fork(а POSIX.1-2008 повністю видаляє vforkз специфікації). Якщо ви перевіряєте свій код у системі, яка їх синонімізує (наприклад, більшість BSD після 4.4, окрім NetBSD, ядер Linux 2.2.0-pre6 тощо), він може працювати, навіть якщо ви порушите vforkконтракт, а потім вибухнете якщо запустити його в іншому місці. Деякі з тих , які імітують його fork(наприклад , OpenBSD) по- , як і раніше гарантувати батьків не відновлює виконання до дитини execз або _exitс. Це смішно не портативно.
ShadowRanger

2
стосовно останнього речення вашого 3-го пункту: Я помітив, що в Linux, використовуючи strace, я помітив, що в той час як glibc обгортка fork () викликає клон syscall, обгортка для vfork () закликає vfork syscall
ilstam

7

Усі fork (), vfork () та clone () викликають do_fork () для виконання справжньої роботи, але з різними параметрами.

asmlinkage int sys_fork(struct pt_regs regs)
{
    return do_fork(SIGCHLD, regs.esp, &regs, 0);
}

asmlinkage int sys_clone(struct pt_regs regs)
{
    unsigned long clone_flags;
    unsigned long newsp;

    clone_flags = regs.ebx;
    newsp = regs.ecx;
    if (!newsp)
        newsp = regs.esp;
    return do_fork(clone_flags, newsp, &regs, 0);
}
asmlinkage int sys_vfork(struct pt_regs regs)
{
    return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, &regs, 0);
}
#define CLONE_VFORK 0x00004000  /* set if the parent wants the child to wake it up on mm_release */
#define CLONE_VM    0x00000100  /* set if VM shared between processes */

SIGCHLD means the child should send this signal to its father when exit.

Що стосується вилки, дитина і батько мають незалежну таблицю сторінок VM, але оскільки ефективність, fork насправді не копіює жодної сторінки, вона просто встановлює всі сторінки, що записуються, лише для читання для дочірнього процесу. Отже, коли дочірній процес хоче щось написати на цій сторінці, трапиться виняток на сторінці, і ядро ​​виділить нову сторінку, клоновану зі старої сторінки з дозволом на запис. Це називається "копіювати при записі".

Для vfork віртуальна пам'ять є саме дитиною та батьком --- саме тому батько і дитина не можуть прокидатися одночасно, оскільки вони будуть впливати один на одного. Тож батько спить наприкінці "do_fork ()" і прокинеться, коли виклик дитини () або execve () з цього часу матиме нову таблицю сторінки. Ось код (у do_fork ()), який спить батько.

if ((clone_flags & CLONE_VFORK) && (retval > 0))
down(&sem);
return retval;

Ось код (у mm_release (), який називається вихід () та execve ()), який будить батька.

up(tsk->p_opptr->vfork_sem);

Для sys_clone () вона більш гнучка, оскільки ви можете вводити до неї будь-які clone_flags. Тож pthread_create () викликає цей системний виклик з багатьма clone_flags:

int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGNAL | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID | CLONE_SYSVSEM);

Підсумок: fork (), vfork () та clone () створить дочірні процеси з різною версією спільного використання ресурсу з батьком. Ми також можемо сказати, що vfork () і clone () можуть створювати потоки (насправді це процеси, оскільки вони мають незалежну task_struct), оскільки вони ділять таблицю сторінок VM з батьківським процесом.


-4

у fork () будь-який дочірній або батьківський процес буде виконуватися на основі вибору процесора .. Але у vfork (), безумовно, дитина виконає першим. після припинення дитини батько виконає страту.


3
Неправильно. vfork()можна просто реалізувати як fork().
ninjalj

після AnyFork () не визначено, хто керує першим батьком / дитиною.
AjayKumarBasuthkar

5
@Raj: У вас є деякі концептуальні непорозуміння, якщо ви думаєте, що після їх розгортання виникає неявна концепція послідовного замовлення. Форкінг створює новий процес, а потім повертає контроль обом процесам (кожен повертається різним pid) - операційна система може запланувати новий процес, який може запускатися паралельно, якщо така річ має сенс (наприклад, кілька процесорів). Якщо з певних причин вам потрібні ці процеси для виконання в певному послідовному порядку, то вам потрібна додаткова синхронізація, яку форкінг не забезпечує; відверто кажучи, ви, мабуть, навіть не хотіли б виделки в першу чергу.
Андон М. Коулман

Насправді @AjayKumarBasuthkar та @ninjalj, ви обидва помиляєтесь. з vfork(), дитина біжить першою. Це на сторінках man; виконання батьків призупиняється, поки дитина не помре або не помер exec. І ніндзя шукають вихідний код ядра. Неможливо реалізувати vfork()так, fork()оскільки вони передають різні аргументи do_fork()в ядро. Однак можна реалізувати vforkза допомогою clonesyscall
Zac Wimer

@ZacWimer: см коментар ShadowRanger до іншого відповісти на stackoverflow.com/questions/4856255 / ... Старий Linux зробив synonimize їх, як , мабуть , крім NetBSD BSDs (який має тенденцію бути перенесена на безліч систем без MMU) робити. З вхідної сторінки Linux: в 4.4BSD було зроблено синонімом fork (2), але NetBSD знову ввів його; див. ⟨netbsd.org/ Documentation/ kernel/ vfork.html⟩ . В Linux він був еквівалентний fork (2) до 2.2.0-pre6 або близько того.
ніндзя
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.