Какова exec()
функция и ее семейство? Почему используется эта функция и как она работает?
Пожалуйста, объясните эти функции.
Какова exec()
функция и ее семейство? Почему используется эта функция и как она работает?
Пожалуйста, объясните эти функции.
Ответы:
Проще говоря, в UNIX у вас есть концепция процессов и программ. Процесс - это среда, в которой выполняется программа.
Простая идея «модели выполнения» UNIX заключается в том, что вы можете выполнять две операции.
Первый - to fork()
, который создает новый процесс, содержащий копию (в основном) текущей программы, включая ее состояние. Между этими двумя процессами есть несколько различий, которые позволяют им определить, какой из них является родительским, а какой - дочерним.
Во-вторых exec()
, программа в текущем процессе заменяется новой программой.
Из этих двух простых операций можно построить всю модель выполнения UNIX.
Чтобы добавить больше деталей к вышесказанному:
Использование UNIX fork()
и exec()
демонстрирует его дух, поскольку он обеспечивает очень простой способ запуска новых процессов.
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()
которая использует то, что они называют копированием при записи. Это уловка, позволяющая отложить копирование пространства процесса 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 (), он создает дублирующую копию самого себя, и эти дубликаты становятся дочерними для процесса. 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 () не используется внутри дочернего процесса.
Но родительский и дочерний элементы различаются некоторыми атрибутами PCB (блока управления процессом). Эти:
А как же детская память? Создано ли для ребенка новое адресное пространство?
Ответов нет. После 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 и т. Д. Будет ли правильным включать программный код, связанный с этими командами, в одну программу или же потомок загружать эти программы в память, когда это необходимо?
Все зависит от вашего варианта использования. У вас есть веб-сервер, которому задан вход x, который возвращает клиентам 2 ^ x. Для каждого запроса веб-сервер создает нового дочернего элемента и просит его выполнить вычисления. Вы напишете отдельную программу для расчета этого и будете использовать exec ()? Или вы просто напишете вычислительный код внутри родительской программы?
Обычно создание процесса включает комбинацию вызовов fork (), exec (), wait () и exit ().
Эти exec(3,3p)
функции заменяют текущий процесс с другим. То есть текущий процесс останавливается , а вместо него запускается другой, забирая часть ресурсов исходной программы.