Что такое 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()
, который использует то, что они называют копированием при записи. Это уловка, позволяющая отложить копирование пространства процесса в 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 без аргумента, был использован для этого соглашения?
- person Ray; 08.04.2015
exec
как о средстве замены текущей программы (оболочки) в этом процессе другой, то не указание этой другой программы для ее замены может просто означать, что вы не хочу его заменить.
- person paxdiablo; 08.04.2015
exec
вызывается без программы. Но это немного странно в этом сценарии, поскольку первоначальная полезность перенаправления для новой программы - программы, которая фактически будет exec
отключена - исчезает, и у вас есть полезный артефакт, перенаправляющий текущую программу, которая не exec
запускается и не запускается. вверх любым способом - вместо этого.
- person Ray; 28.04.2015
Функции в семействе exec () имеют разное поведение:
Вы можете смешивать их, поэтому у вас есть:
Для всех из них начальным аргументом является имя файла, который должен быть выполнен.
Для получения дополнительной информации прочтите справочную страницу exec (3):
man 3 exec # if you are running a UNIX system
execve()
из своего списка, который определяется POSIX , и вы добавили execvpe()
, который не определяется POSIX (в основном по причинам исторического прецедента; он завершает набор функций). В противном случае полезное объяснение соглашения об именах для семейства - полезное дополнение к paxdiablo 'a answer, в котором подробно рассказывается о работе функций.
- person Jonathan Leffler; 16.01.2018
execvpe()
(и др.) Нет списка execve()
; у него есть собственная отдельная страница руководства (по крайней мере, в Ubuntu 16.04 LTS) - разница в том, что другие функции семейства exec()
перечислены в разделе 3 (функции), тогда как execve()
перечислены в разделе 2 (системные вызовы). По сути, все остальные функции в семействе реализуются посредством вызова execve()
.
- person Jonathan Leffler; 16.01.2018
Семейство функций 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)
заменяют текущий процесс другим. То есть текущий процесс останавливается, а вместо него запускается другой, забирая некоторые ресурсы, которые были у исходной программы.