Яка exec()
функція та її родина? Чому використовується ця функція і як вона працює?
Будь ласка, будь-хто пояснить ці функції.
Яка exec()
функція та її родина? Чому використовується ця функція і як вона працює?
Будь ласка, будь-хто пояснить ці функції.
Відповіді:
Спрощено, в UNIX ви маєте поняття процесів і програм. Процес - це середовище, в якому виконується програма.
Проста ідея «моделі виконання» UNIX полягає в тому, що ви можете зробити дві операції.
Перший - це fork()
, який створює абсолютно новий процес, що містить дублікат (переважно) поточної програми, включаючи її стан. Існує кілька відмінностей між двома процесами, які дозволяють їм з’ясувати, хто є батьком, а хто дитиною.
Другий - до exec()
, який замінює програму в поточному процесі новою програмою.
З цих двох простих операцій можна побудувати всю модель виконання UNIX.
Щоб додати дещо більше до вищезазначеного:
Використання fork()
та exec()
ілюструє дух UNIX, оскільки він забезпечує дуже простий спосіб для запуску нових процесів.
fork()
Виклик робить майже дублікат поточного процесу, ідентичний майже в усіх відношеннях (Не все скопійовано, наприклад, обмеження використання ресурсів в деяких реалізаціях, але ідея полягає в тому, щоб створити як можна ближче копію наскільки це можливо). Лише один процес викликає, fork()
але два процеси повертаються з цього дзвінка - звучить химерно, але насправді досить елегантно
Новий процес (який називається дочірнім) отримує інший ідентифікатор процесу (PID) і має PID старого процесу (батьківського) як батьківський PID (PPID).
Оскільки зараз два процеси виконують абсолютно однаковий код, вони повинні мати змогу визначити, який саме - код повернення fork()
надає цю інформацію - дочірній отримує 0, батько отримує PID дочірнього (якщо fork()
не вдається, ні дитина створюється, а батько отримує код помилки).
Таким чином, батько знає PID дитини і може спілкуватися з ним, вбивати його, чекати його тощо (дитина завжди може знайти свій батьківський процес із викликом getppid()
).
exec()
Виклик замінює весь вміст поточного процесу з новою програмою. Він завантажує програму в поточний простір процесу та запускає її з точки входу.
Отже, fork()
і exec()
часто використовуються послідовно для запуску нової програми як дочірнього поточного процесу. Зазвичай оболонки роблять це, коли ви намагаєтеся запустити програму, наприклад find
- оболонка розгалужується, потім дочірня find
програма завантажує в пам’ять, налаштовуючи всі аргументи командного рядка, стандартний ввід / вивід тощо.
Але їх не потрібно використовувати разом. Цілком прийнятно, щоб програма дзвонила fork()
без наступного, exec()
якщо, наприклад, програма містить і батьківський, і дочірній код (потрібно бути обережним, що ви робите, кожна реалізація може мати обмеження).
Це використовувалось досить багато (і дотепер) для демонів, які просто прослуховують порт TCP і розгалужують копію себе для обробки конкретного запиту, поки батько повертається до прослуховування. У цій ситуації програма містить як батьківський, так і дочірній код.
Подібним чином програми, які знають, що вони закінчили і просто хочуть запустити іншу програму, не потрібно fork()
, exec()
і то wait()/waitpid()
для дитини. Вони можуть просто завантажити дитину безпосередньо в поточний процес exec()
.
Деякі реалізації UNIX мають оптимізовану версію, fork()
яка використовує те, що вони називають copy-on-write. Це фокус, щоб затримати копіювання простору процесів, fork()
доки програма не спробує щось змінити в цьому просторі. Це корисно для тих програм, які використовують лише, fork()
а не exec()
тим, що їм не потрібно копіювати весь простір процесу. У Linux, fork()
лише копіюючи таблиці сторінок і нову структуру завдань, exec()
буде виконувати грубу роботу "розділення" пам'яті двох процесів.
Якщо exec
це називається наступне fork
(і це те , що відбувається , в основному), що призводить до запису в простір процесу і потім копіюється для дочірнього процесу, перед внесенням змін допускається.
Linux також має vfork()
, ще більш оптимізовану, яка поділяє майже все між двома процесами. Через це існують певні обмеження щодо того, що може робити дитина, і батько зупиняється, поки дитина не зателефонує exec()
або _exit()
.
Батька слід зупинити (і дочірній заборонено повертатися з поточної функції), оскільки два процеси навіть мають однаковий стек. Це трохи ефективніше для класичного випадку використання, за fork()
яким слід негайно exec()
.
Зверніть увагу , що існує ціле сімейство 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
exec
утиліта використовується для перенаправлення поточного вводу-виводу поточного процесу? Як "нульовий" випадок, що виконує exec без аргументу, звик до цієї конвенції?
exec
як засіб , щоб замінити поточну програму (оболонка) в цьому процесі з іншого, то НЕ вказавши , що інші програми , щоб замінити його може просто означати , що ви НЕ хочете , щоб замінити його.
exec
виклику без програми. Але це трохи дивно в цьому сценарії, оскільки первісна корисність перенаправлення на нову програму - програму, яка насправді може бути використана exec
- зникає, і у вас є корисний артефакт, що перенаправляє поточну програму - яка не використовується exec
або не запускається будь-яким способом - замість цього.
Функції в сім'ї exec () мають різну поведінку:
Ви можете їх змішувати, тому у вас є:
Для всіх них початковим аргументом є назва файлу, який має бути виконаний.
Для отримання додаткової інформації прочитайте сторінку керівництва exec (3) :
man 3 exec # if you are running a UNIX system
execve()
зі свого списку, який визначений POSIX, і додали execvpe()
, який не визначений POSIX (здебільшого з історичних причин; він завершує набір функцій). В іншому випадку корисне пояснення правила іменування для сім’ї - корисний додаток до paxdiablo - відповідь, яка пояснює більше про роботу функцій.
execvpe()
(та ін) не містить переліку execve()
; він має свою окрему сторінку довідок (принаймні на Ubuntu 16.04 LTS) - різниця полягає в тому, що інші exec()
функції сімейства перелічені в розділі 3 (функції), тоді execve()
як перелічені в розділі 2 (системні дзвінки). В основному всі інші функції в сім’ї реалізуються з точки зору заклику до execve()
.
exec
Сімейство функцій зробити процес виконання іншої програми, замінивши стару програму вона була запущена. Тобто, якщо ви телефонуєте
execl("/bin/ls", "ls", NULL);
тоді ls
програма виконується з ідентифікатором процесу, поточним робочим каталогом та користувачем / групою (права доступу) процесу, який викликав execl
. Згодом оригінальна програма вже не працює.
Для запуску нового процесу використовується fork
системний виклик. Щоб виконати програму без заміни оригіналу, потрібно fork
, тоді exec
.
що таке функція exec та її родина.
exec
Функція сім'я всі функції , які використовуються для виконання файлу, таких як execl
, execlp
, execle
, execv
і execvp
.Оніте все оболонки для execve
і забезпечують різні способи виклику його.
чому використовується ця функція
Функції Exec використовуються, коли потрібно виконати (запустити) файл (програму).
і як це працює.
Вони працюють, перезаписуючи поточний образ процесу на той, який ви запустили. Вони замінюють (закінчуючи) поточно запущений процес (той, що викликав команду exec) новим процесом, який запущений.
Детальніше: див. Це посилання .
exec
часто використовується разом із fork
, про що я бачив, про що ви також запитували, тому я обговорю це з урахуванням цього.
exec
перетворює поточний процес в іншу програму. Якщо ви коли-небудь спостерігали за Доктором Хто, то це як коли він відроджується - його старе тіло замінюється новим.
Спосіб, що це відбувається з вашою програмою, exec
полягає в тому, що багато ресурсів, які перевіряє ядро ОС, перевіряє, чи exec
виконується поточним користувачем (ідентифікатор користувача процесу) файл, якому ви передаєте як аргумент програми (перший аргумент) здійснення exec
виклику), і якщо це так, то він замінює відображення віртуальної пам'яті поточного процесу віртуальною пам’яттю новим процесом і копіює дані argv
та envp
дані, передані під час exec
виклику, в область цієї нової карти віртуальної пам'яті. Тут може трапитися ще кілька речей, але файли, відкриті для програми, яка викликала exec
, все одно будуть відкриті для нової програми, і вони матимуть той самий ідентифікатор процесу, але програма, яка викликала exec
, припиниться (якщо exec не вдалося).
Причина , по якій це робиться таким чином, що шляхом поділу роботи на нову програму в два етапи , як це ви можете зробити деякі речі між двома кроками. Найпоширеніше, що потрібно зробити, це переконатися, що нова програма відкриває певні файли як певні дескриптори файлів. (тут пам’ятайте, що дескриптори файлів - це не те саме FILE *
, але це int
значення, про які знає ядро). Роблячи це, ви можете:
int X = open("./output_file.txt", O_WRONLY);
pid_t fk = fork();
if (!fk) { /* in child */
dup2(X, 1); /* fd 1 is standard output,
so this makes standard out refer to the same file as X */
close(X);
/* I'm using execl here rather than exec because
it's easier to type the arguments. */
execl("/bin/echo", "/bin/echo", "hello world");
_exit(127); /* should not get here */
} else if (fk == -1) {
/* An error happened and you should do something about it. */
perror("fork"); /* print an error message */
}
close(X); /* The parent doesn't need this anymore */
Це досягає запуску:
/bin/echo "hello world" > ./output_file.txt
з командної оболонки.
Коли процес використовує fork (), він створює дублікат своєї копії, і цей дублікат стає дочірнім процесом. Форк () реалізований за допомогою системного виклику clone () в linux, який двічі повертається з ядра.
Давайте розберемося в цьому на прикладі:
pid = fork();
// Both child and parent will now start execution from here.
if(pid < 0) {
//child was not created successfully
return 1;
}
else if(pid == 0) {
// This is the child process
// Child process code goes here
}
else {
// Parent process code goes here
}
printf("This is code common to parent and child");
У цьому прикладі ми припустили, що exec () не використовується всередині дочірнього процесу.
Але батько та дочірній матеріал відрізняються за деякими атрибутами друкованої плати (блоку управління процесом). Це:
Але як щодо пам’яті дитини? Чи створений новий адресний простір для дитини?
Відповіді у п. Після fork () і батьки, і діти дочіряють спільний адресний простір пам'яті батьків. У Linux ці адресні простори розділені на кілька сторінок. Лише коли дитина пише на одній із батьківських сторінок пам'яті, для дитини створюється дублікат цієї сторінки. Це також відомо як копіювання на запис (копіюйте батьківські сторінки лише тоді, коли дитина пише на нього).
Давайте розберемося з копією на запис на прикладі.
int x = 2;
pid = fork();
if(pid == 0) {
x = 10;
// child is changing the value of x or writing to a page
// One of the parent stack page will contain this local variable. That page will be duplicated for child and it will store the value 10 in x in duplicated page.
}
else {
x = 4;
}
Але чому необхідна копія на запис?
Типове створення процесу відбувається за допомогою комбінації fork () - exec (). Давайте спочатку зрозуміємо, що робить exec ().
Група функцій Exec () замінює адресний простір дитини новою програмою. Після виклику exec () всередині дочірньої дитини буде створено окремий адресний простір для дитини, який повністю відрізняється від батьківського.
Якби не було копіювання механізму запису, пов’язаного з fork (), дублікати сторінок були б створені для дитини, і всі дані були б скопійовані на сторінки дитини. Виділення нової пам'яті та копіювання даних є дуже дорогим процесом (вимагає часу процесора та інших системних ресурсів). Ми також знаємо, що в більшості випадків дитина збирається викликати exec (), і це замінить пам'ять дитини новою програмою. Отже, перша копія, яку ми зробили, була б марною, якби там не було копії на запис.
pid = fork();
if(pid == 0) {
execlp("/bin/ls","ls",NULL);
printf("will this line be printed"); // Think about it
// A new memory space will be created for the child and that memory will contain the "/bin/ls" program(text section), it's stack, data section and heap section
else {
wait(NULL);
// parent is waiting for the child. Once child terminates, parent will get its exit status and can then continue
}
return 1; // Both child and parent will exit with status code 1.
Чому батьки чекають на процес дитини?
Чому необхідний системний виклик exec ()?
Не обов’язково використовувати exec () з fork (). Якщо код, який буде виконувати дочірній файл, знаходиться в програмі, пов'язаній з батьківським, exec () не потрібен.
Але згадайте випадки, коли дитині доводиться запускати кілька програм. Візьмемо приклад програми оболонки. Він підтримує декілька команд, таких як find, mv, cp, date і т. Д. Чи правильно буде включати програмний код, пов'язаний з цими командами, в одну програму або дочірній завантажувати ці програми в пам'ять, коли це потрібно?
Все залежить від вашого випадку використання. У вас є веб-сервер, який вводить х, який повертає клієнтам 2 ^ х. Для кожного запиту веб-сервер створює нову дочірню систему та просить її обчислити. Чи будете ви писати окрему програму для обчислення цього та використовувати exec ()? Або ви просто напишете код обчислення всередині батьківської програми?
Зазвичай створення процесу передбачає поєднання викликів fork (), exec (), wait () та exit ().
Ці exec(3,3p)
функції замінюють поточний процес з іншим. Тобто поточний процес зупиняється , а замість нього запускається інший, беручи на себе частину ресурсів, які мала оригінальна програма.