Чому снаряди називають вилкою ()?


32

Коли процес запускається з оболонки, то чому оболонка розщеплюється перед виконанням процесу?

Наприклад, коли користувач вводить дані grep blabla foo, чому оболонка не може просто викликати exec()grep без дочірньої оболонки?

Крім того, коли оболонка розвивається в емуляторі терміналу GUI, чи запускає інший емулятор терміналу? (наприклад, pts/13стартовий pts/14)

Відповіді:


34

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

Як приклад, ви хочете запустити grepза допомогою exec. bashце процес (який має окрему пам'ять, адресний простір). Тепер, коли ви телефонуєте exec(grep), exec замінить пам'ять поточного процесу, адресний простір, набір інструкцій тощо на grep'sдані. Це означає, що bashпроцес більше не буде. Як результат, після завершення grepкоманди ви не зможете повернутися до терміналу . Ось чому методи сім'ї exec ніколи не повертаються. Ви не можете виконати жоден код після exec; це недосяжно.


Майже нормально --- Я замінив Термінал bash. ;-)
Рмано

2
До речі, ви можете сказати bash виконувати grep, не розправляючи спочатку, використовуючи команду exec grep blabla foo. Звичайно, у цьому конкретному випадку це буде не дуже корисно (оскільки ваше термінальне вікно просто закриється, як тільки закінчиться греп), але воно може бути зручно (наприклад, якщо ви запускаєте іншу оболонку, можливо, через ssh / sudo / screen, і не збираюся повертатися до початкового, або якщо процес оболонки, над яким ви працюєте, - це під-оболонка, яка ніколи не має на меті виконувати більше однієї команди).
Ільмарі Каронен

7
Набір інструкцій має дуже конкретне значення. І це не той сенс, яким ви його використовуєте.
Андрій Савіних

@IlmariKaronen Було б корисно в скриптах для обгортки, де ви хочете підготувати аргументи та середовище для команди. І ви згадали випадок, коли bash ніколи не призначений для запуску більше однієї команди, це насправді, bash -c 'grep foo bar'і виклик exec є форма оптимізації bash робить для вас автоматично
Сергій Колодяжний,

3

Відповідно pts, перевірте самі: в оболонці, бігайте

echo $$ 

щоб знати ваш ідентифікатор процесу (PID), я маю, наприклад

echo $$
29296

Потім запустіть, наприклад sleep 60 потім в іншому терміналі

(0)samsung-romano:~% ps -edao pid,ppid,tty,command | grep 29296 | grep -v grep
29296  2343 pts/11   zsh
29499 29296 pts/11   sleep 60

Так ні, загалом у вас є ті ж самі тти, пов'язані з процесом. (Зверніть увагу, що це ваш, sleepоскільки він має вашу оболонку як батьків).


2

TL; DR : Оскільки це оптимальний метод для створення нових процесів і збереження контролю в інтерактивній оболонці

fork () необхідний для процесів і труб

Щоб відповісти на конкретну частину цього питання, якби grep blabla fooвикликати його exec()безпосередньо через батьківську систему, батьківство захопило б існувати, а його PID з усіма ресурсами буде перейнятоgrep blabla foo .

Однак поговоримо загалом про exec()і fork(). Ключова причина такої поведінки полягає в тому, що fork()/exec()це стандартний метод створення нового процесу на Unix / Linux, і це не є специфічним башком; цей метод існує з самого початку і під цим впливом впливає вже існуюча операційна система того часу. Дещо перефразовуючи відповідь goldilocks на пов’язане питання, fork()створити новий процес простіше, оскільки ядро ​​менше роботи, ніж виділення ресурсів, і багато властивостей (таких, як дескриптори файлів, оточення тощо) - все може успадковується від батьківського процесу (у цьому випадку відbash ).

По-друге, що стосується інтерактивних оболонок, ви не можете запустити зовнішню команду без розгортання. Для запуску виконуваного файлу, який живе на диску (наприклад, /bin/df -h), потрібно викликати одну з exec()сімейних функцій, наприклад execve(), яка замінить батьківський новий процес, перейняти його PID та наявні дескриптори файлів тощо. Що стосується інтерактивної оболонки, ви хочете, щоб елемент керування повернувся до користувача та дозволив батьківській інтерактивній оболонці продовжувати працювати. Таким чином, найкращий спосіб - створити підпроцес через fork(), і нехай цей процес буде перейнятий через execve(). Отже, інтерактивна оболонка PID 1156 породила б дитину за fork()допомогою PID 1157, потім зателефонувала execve("/bin/df",["df","-h"],&environment), що змушує /bin/df -hзапускати PID 1157. Тепер оболонці залишається лише чекати, поки процес вийде і поверне йому управління.

У випадку, коли вам доведеться створити трубу між двома або більше командами, скажімо df | grep, вам потрібен спосіб створити два дескриптори файлів (це читати і записувати кінець труби, що надходять з pipe()syscall), то якимось чином нехай два нові процеси успадкують їх. Це робиться для розгортання нового процесу, а потім копіювання кінця запису труби за допомогою dup2()виклику на його stdoutака fd 1 (так, якщо кінець запису є fd 4, ми робимо dup2(4,1)). Коли трапляється exec()нерест, dfдочірній процес нічого не подумає stdoutі запише до нього, не усвідомлюючи (якщо він активно не перевіряє), що його результат насправді йде на дудку. Те ж саме відбувається і з grep, за винятком ми fork(), взяти читання кінця труби з дескриптором 3 і dup(3,0)перед нерестом grepзexec(). Весь цей час батьківський процес все ще існує, очікуючи відновити контроль, коли конвеєр буде завершено.

У випадку вбудованих команд, як правило, оболонки немає fork(), за винятком sourceкоманди. Абонентів потрібно fork().

Словом, це необхідний і корисний механізм.

Недоліки розгортання та оптимізації

Тепер це відрізняється від неінтерактивних оболонок , таких як bash -c '<simple command>'. Незважаючи на fork()/exec()те, що є оптимальним методом, коли вам доведеться обробляти багато команд, це марна трата ресурсів, коли у вас є лише одна команда. Щоб цитувати Стефана Шазеласа з цієї публікації :

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

Тому багато оболонок (не просто bash) використовують exec()для того, bash -c ''щоб дозволити перейняти цю єдину просту команду. І саме з зазначених вище причин краще мінімізувати конвеєри в сценарії оболонок. Часто можна побачити початківців, що роблять щось подібне:

cat /etc/passwd | cut -d ':' -f 6 | grep '/home'

Звичайно, це буде fork()3 процеси. Це простий приклад, але врахуйте великий файл у діапазоні гігабайт. Це було б набагато ефективніше з одним процесом:

awk -F':' '$6~"/home"{print $6}' /etc/passwd

Втрата ресурсів насправді може бути формою атаки відмови в службі, і, зокрема, вилкові бомби створюються за допомогою оболонок, які називають себе в конвеєрі, який розщеплює кілька копій самих себе. Сьогодні це зменшується через обмеження максимальної кількості процесів у групах на systemd , які Ubuntu також використовує з версії 15.04.

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

Дивись також


1

Для кожної команди (наприклад: grep), яку ви випускаєте в записі bash, ви насправді маєте намір розпочати новий процес, а потім повернутися до запиту bash після виконання.

Якщо процес оболонки (bash) викликає exec () для запуску grep, процес оболонки буде замінено grep. Grep спрацює нормально, але після виконання керування не може повернутися до оболонки, оскільки процес bash вже замінений.

З цієї причини bash викликає fork (), який не замінює поточний процес.

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