Чому нам потрібно розщедритися для створення нових процесів?


95

У Unix, коли ми хочемо створити новий процес, ми розщеплюємо поточний процес, створюючи новий дочірній процес, який точно такий же, як і батьківський процес; то робимо системний виклик exec, щоб замінити всі дані з батьківського процесу на дані нового процесу.

Чому ми створюємо копію батьківського процесу в першу чергу, а не створюємо новий процес безпосередньо?


Відповіді:


61

Коротка відповідь полягає forkв тому, що в Unix тому, що було легко вписатись у існуючу на той час систему і тому, що попередницька система в Берклі використовувала концепцію вилок.

З еволюції системи розподілу часу Unix ( виділений відповідний текст ):

Контроль процесів у його сучасному вигляді був розроблений та впроваджений протягом декількох днів. Дивно, як легко він вписується в існуючу систему; в той же час легко помітити, як деякі дещо незвичні риси дизайну присутні саме тому, що вони представляли невеликі, легко кодовані зміни до того, що існувало . Хороший приклад - розділення функцій fork та exec. Найпоширеніша модель створення нових процесів передбачає визначення програми для виконання процесу; в Unix, роздвоєний процес продовжує запускати ту саму програму, що і його батько, поки він не виконає явний exec. Розділення функцій, безумовно, не властиве Unix, і насправді він був присутній у системі обміну часом Берклі, яка була добре відома Томпсону. Тим не менш, здається, що він існує в Unix в основному через простоту, з якою можна реалізувати fork, не змінюючи багато іншого . Система вже обробляла кілька (тобто два) процеси; існувала таблиця процесів, і процеси були замінені між основною пам'яттю та диском. Потрібна лише початкова реалізація вилки

1) Розширення таблиці процесів

2) Додано виклик вилки, який скопіював поточний процес у область підкачки диска, використовуючи вже наявні примітиви swap IO, та вніс деякі корективи в таблицю процесів.

Насправді, для виклику вилки PDP-7 потрібно було точно 27 рядків коду складання. Звичайно, були потрібні інші зміни в операційній системі та програмах користувачів, і деякі з них були досить цікавими та несподіваними. Але комбінований fork-exec був би значно складнішим , якби тільки тому, що exec як такого не існувало; її функція вже виконувалась, використовуючи явний IO, оболонкою.

З того часу цей папір Unix розвивався. forkпісля чого exec- це вже не єдиний спосіб запуску програми.

  • vfork був створений, щоб бути більш ефективною форкою для випадку, коли новий процес має намір зробити exec відразу після вилки. Виконуючи vfork, батьківський та дочірній процеси обмінюються однаковим простором даних, і батьківський процес призупиняється, поки дочірній процес або не виконає програму, або завершить роботу.

  • posix_spawn створює новий процес і виконує файл у єдиному системному виклику. Потрібно купу параметрів, які дозволяють вибірково ділитися відкритими файлами абонента та копіювати його розпорядження сигналу та інші атрибути до нового процесу.


5
Приємна відповідь, але я додам, що vfork більше не слід використовувати. Зараз різниця в продуктивності незначна, і його використання може бути небезпечним. Дивіться це питання ТАК stackoverflow.com/questions/4856255/… , цей сайт ewontfix.com/7 та "Розширене програмування Unix" на сторінці 299 про vfork
Рафаель Аренс

4
Махінації (налаштування структури даних), необхідні для posix_spawn()виконання тих же завдань по відновленню пост-форка, які можна легко виконати за допомогою fork()вбудованого коду, є переконливим аргументом для fork()використання набагато простіше.
Джонатан Леффлер

34

[Я повторю частину своєї відповіді звідси .]

Чому б просто не мати команду, яка створює новий процес з нуля? Хіба не абсурдно і неефективно скопіювати той, який буде замінений лише одразу?

Насправді це може бути не настільки ефективним з кількох причин:

  1. «Копія» проводиться fork()трохи абстракції, так як ядро використовує копіювання при записі системи ; все, що дійсно має бути створене - це карта віртуальної пам'яті. Якщо копія негайно викликає виклик exec(), більшість даних, які були б скопійовані, якби вони були змінені діяльністю процесу, ніколи насправді не потрібно копіювати / створювати, оскільки процес не робить нічого, що вимагає його використання.

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

Щоб пояснити №1 трохи далі, пам'ять, яка "копіюється", але згодом до неї ніколи не дійсно копіюється, принаймні в більшості випадків. Виняток у цьому контексті може бути, якщо ви розблокували процес, а потім вийшов з батьківського процесу до того, як дитина замінила себе exec(). Я говорю міць , тому що велика частина батьків може бути в кеші , якщо є достатньо вільного місця, і я не впевнений , якою мірою це буде експлуатуватися (що буде залежати від реалізації ОС).

Звичайно, це не дозволяє зробити копію більш ефективною, ніж використання порожнього шиферу, - окрім "порожнього шиферу" - це не буквально нічого, і він повинен передбачати розподіл. У системі може бути загальний шаблон пустого / нового процесу, який він копіює так само, 1, але це насправді нічого не збереже, порівняно з виделкою копіювання при записі. Тож №1 просто демонструє, що використання "нового" порожнього процесу не було б більш ефективним.

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

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


3
Незважаючи на те, що обидва пункти вірні, жоден з них не підтримує, чому метод форкінгу був обраний замість повторного введення нового процесу з даного виконуваного файлу.
SkyDan

3
Я думаю, що це відповідає на питання. Fork використовується, оскільки у випадках, коли створення нового процесу є найбільш ефективним способом, вартість використання fork замість цього є тривіальною (ймовірно, менше 1% від вартості створення процесу). З іншого боку, існує багато місць, де вилка значно ефективніша або набагато простіша API (наприклад, обробка ручок файлів). Unix прийняла рішення підтримати лише один API, що спростить специфікацію.
Корт Аммон

1
@SkyDan Ви маєте рацію, це скоріше відповідь на те, чому ні, а не чому , на що Марк Плотнік відповідає прямо - що я би трактував, маючи на увазі не просто те, що це був найпростіший вибір, а й те, що він, мабуть, був найбільш ефективним вибір (згідно з цитатою Денніса Річі: "Для виклику вилки PDP-7 потрібно було точно 27 ліній складання ... exec як такого не існувало; його функція вже виконувалася"). Отже, це "чому ні" насправді розмірковує про дві стратегії, в яких одна поверхово виявляється простішою та ефективнішою, коли, можливо, це не так (свідчить про сумнівну долю ...
goldilocks

1
Золотинки правильно. Бувають ситуації, коли розгортання та зміна дешевше, ніж створення нового з нуля. Зрозуміло, самий крайній приклад - це будь-коли, коли ти хочеш самої поведінки вилки. fork()чи може це дуже швидко (як згадував GL, на замовлення 27 ліній складання). Якщо дивитися в інший бік, якщо ви хочете "створити процес з нуля", fork()коштує лише крихітний дорожче, ніж починати з порожнього створеного процесу (27 рядків складання + вартість закриття ручок файлів). Таким чином, forkобробляє як виделку, так і добре створює, тоді як createлише ручка може створювати добре.
Корт Аммон

2
Ваша відповідь стосувалася апаратних удосконалень: віртуальної пам’яті, копіювання-запис. До цього forkнасправді копіювалася вся процесова пам'ять, і це було дуже дорого.
Вармар

6

Я думаю, що причина, по якій Unix мав лише forkфункцію створювати нові процеси, є результатом філософії Unix

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

Що робити з новим процесом, то залежить від програміста. Він може використовувати одну з exec*функцій і запускати іншу програму, або він не міг використовувати exec і використовувати два екземпляри однієї програми, що може бути корисно.

Так ви отримуєте більший ступінь свободи, оскільки можете користуватися

  1. виделка без exec *
  2. виделка з exec * або
  3. просто виконувати * без виделки

і крім того , ви повинні тільки запам'ятовувати forkті і exec*виклики функцій, які в 1970 - х роках ви повинні були зробити.


3
Я розумію, як працюють виделки та як ними користуватися. Але чому я хотів би створити новий процес, коли я можу зробити те саме, але з меншими зусиллями? Наприклад, мій вчитель дав мені завдання, де я повинен створити процес для кожного числа, переданого до argv, щоб перевірити, чи є число простим. Але хіба це не просто об'їзд того, що в кінцевому рахунку робити те саме? Я міг би просто використати масив і використати функцію для кожного числа ... То чому ми створюємо дочірні процеси, а не виконуємо всю обробку в основному процесі?
користувач1534664

2
Я б ризикну сказати, що ви розумієте, як працюють виделки та як ними користуватися, тому що колись у вас був учитель, який дав вам завдання, де вам довелося створити купу процесів (при цьому кількість визначається під час виконання), контролювати їх, координувати їх і спілкуватися між ними. Звичайно, ніхто не зробив би такого тривіального в реальному житті. Але якщо у вас є велика проблема, яка легко розкладається на шматки, які можна обробляти паралельно (наприклад, виявлення країв у зображенні), розгортання дозволяє одночасно використовувати декілька ядер CPU.
Скотт

5

Існує дві філософії створення процесу: роздвоєння з успадкуванням і створення з аргументами. Unix використовує вилку, очевидно. (OSE, наприклад, і VMS використовують метод створення.) Unix має МНОГО успадкованих характеристик і більше періодично додається. Завдяки успадкуванню ці нові характеристики можна додати БЕЗ ЗМІНИ ІСНУЮЧИХ ПРОГРАМ! Використовуючи модель створення аргументів, додавання нових характеристик означатиме додавання нових аргументів до виклику create. Модель Unix простіша.

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

Модель fork / exec також надає можливість певній дитині успадкувати радикально дивне середовище, встановлене між вилкою та exec. Такі речі, як спадкові дескриптори файлів, особливо. (Розширення stdio fd's.) Модель create не пропонує можливість успадкувати те, що не передбачали творці виклику create.

Деякі системи також можуть підтримувати динамічну компіляцію нативного коду, де процес фактично пише власну програму з власним кодом. Іншими словами, вона хоче нову програму, яку вона пише сама на ходу, БЕЗ необхідності пройти цикл вихідного коду / компілятора / лінкера та зайняти дисковий простір. (Я вважаю, що існує мовна система Verilog, яка це робить.) Модель fork підтримує це, модель створення зазвичай не буде.


Дескриптори файлів не є "розширенням stdio"; покажчики файлів stdio - це обгортка навколо дескрипторів файлів. Дескриптори файлів вийшли першими, і вони є основними ручками вводу / виводу Unix. Але в іншому випадку це хороший момент.
Скотт

2

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

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

як показано, коли процес є батьком fork () повертає ідентифікатор процесу син, який PID він повертає0

наприклад, ви можете скористатися ним, якщо у вас є процес (веб-сервер), який отримує запити, і на кожен запит він створює son processдля обробки цього запиту, тут батько та його сини мають різні завдання.

Так, жодна запущена копія процесу не є точним, як fork ().


5
Хоча це правда, це не відповідає на питання. Чому розробка потрібна для створення процесу, якщо я хочу запустити інший виконуваний файл?
SkyDan

1
Я погоджуюся зі SkyDan - це не дає відповіді на питання. posix_spawn - дещо фантастичніша версія того, що можна було уявити 30 років тому (до існування Posix) як функції fork_execve ; той, який створює новий процес, ініціалізуючи своє зображення з виконуваного файлу, навіть не натякаючи на копіювання зображення батьківського процесу (за винятком списку аргументів, середовища та атрибутів процесу (наприклад, робочий каталог)), і повертає PID нового процесу для абонента (батьківського процесу) .
Скотт

1
Є й інші способи передачі «батьківської» інформації дитині. Техніка повернення величини як раз і є найефективнішим способом це зробити, fork якщо ви вважаєте, що хочете forkв першу чергу
Корт Аммон

0

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


Все, що ви тут робите, - це повторити третій абзац відповіді Джима Кеті з невеликою деталізацією.
Скотт

-2

Я думаю, що всі тут знають, що як fork працює, але питання полягає в тому, чому нам потрібно створити точний дублікат батьків, використовуючи fork? Відповідь ==> Візьмемо приклад сервера (без виделки), а клієнт-1 звертається до сервера, якщо одночасно приходить другий клієнт-2 і хоче отримати доступ до сервера, але сервер не дає дозволу новоприбулому client-2, оскільки сервер зайнятий, щоб обслуговувати client-1, тому client-2 повинен зачекати. Після того, як усі послуги клієнта-1 закінчуються, client-2 тепер може отримати доступ до сервера. Тепер врахуйте, якщо одночасно client-3 прибуває, тож client-3 повинен зачекати, поки всі послуги клієнта-2 закінчаться. Взяти сценарій, коли тисячі клієнтів повинні одночасно отримати доступ до сервера ... тоді всі клієнти повинні зачекайте (сервер зайнятий !!).

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


Ось чому серверні процеси не повинні бути однопотоковими, обробляти запити клієнтів послідовно, коли з ними можна одночасно обробляти - наприклад, в окремих процесах. Але багатопотокова модель сервера може бути легко реалізована за допомогою процесу слухача, який приймає запити клієнтів і створює абсолютно новий процес, в якому запускати програму обслуговування клієнтів. Єдина перевага, яку пропонує forkвиклик, який копіює батьківський процес, - це те, що вам не потрібно мати дві окремі програми - але наявність окремих програм (наприклад, inetd) може зробити систему більш модульною.
Скотт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.