Пожалуйста, объясните функцию exec () и ее семейство

Что такое exec() функция и ее семейство? Почему используется эта функция и как она работает?

Пожалуйста, объясните эти функции.


person user507401    schedule 17.11.2010    source источник
comment
Попробуйте еще раз прочитать Стивенса и пояснить, что вы не понимаете.   -  person vlabrecque    schedule 17.11.2010


Ответы (7)


Проще говоря, в 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
person paxdiablo    schedule 17.11.2010
comment
Спасибо за столь подробное объяснение :) - person Faizan; 25.10.2013
comment
Спасибо за ссылку на оболочку с программой поиска. Именно то, что мне нужно было понять. - person User; 16.03.2015
comment
Почему exec эта утилита используется для перенаправления ввода-вывода текущего процесса? Каким образом нулевой регистр, выполняющий exec без аргумента, был использован для этого соглашения? - person Ray; 08.04.2015
comment
@Ray, я всегда считал это естественным продолжением. Если вы думаете о exec как о средстве замены текущей программы (оболочки) в этом процессе другой, то не указание этой другой программы для ее замены может просто означать, что вы не хочу его заменить. - person paxdiablo; 08.04.2015
comment
Я понимаю, что вы имеете в виду, если под естественным продолжением подразумеваете что-то вроде органического роста. Похоже, что для поддержки функции замены программы было бы добавлено перенаправление, и я вижу, что это поведение остается в вырожденном случае, когда exec вызывается без программы. Но это немного странно в этом сценарии, поскольку первоначальная полезность перенаправления для новой программы - программы, которая фактически будет exec отключена - исчезает, и у вас есть полезный артефакт, перенаправляющий текущую программу, которая не exec запускается и не запускается. вверх любым способом - вместо этого. - person Ray; 28.04.2015
comment
Схема помогает. Спасибо - person Kevin; 31.08.2018

Функции в семействе exec () имеют разное поведение:

  • l: аргументы передаются в виде списка строк в main ()
  • v: аргументы передаются как массив строк в main ()
  • p: путь / s для поиска новой запущенной программы
  • e: окружение может быть указано вызывающим

Вы можете смешивать их, поэтому у вас есть:

  • int execl (const char * путь, const char * arg, ...);
  • int execlp (const char * файл, const char * arg, ...);
  • int execle (const char * путь, const char * arg, ..., char * const envp []);
  • int execv (const char * путь, char * const argv []);
  • int execvp (const char * файл, char * const argv []);
  • int execvpe (const char * файл, char * const argv [], char * const envp []);

Для всех из них начальным аргументом является имя файла, который должен быть выполнен.

Для получения дополнительной информации прочтите справочную страницу exec (3):

man 3 exec  # if you are running a UNIX system
person T04435    schedule 01.06.2016
comment
Интересно, что вы пропустили execve() из своего списка, который определяется POSIX , и вы добавили execvpe(), который не определяется POSIX (в основном по причинам исторического прецедента; он завершает набор функций). В противном случае полезное объяснение соглашения об именах для семейства - полезное дополнение к paxdiablo 'a answer, в котором подробно рассказывается о работе функций. - person Jonathan Leffler; 16.01.2018
comment
И в вашу защиту я вижу, что на странице руководства Linux для 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.

person Fred Foo    schedule 17.11.2010
comment
Спасибо, это было действительно полезно. В настоящее время я работаю над проектом, требующим от нас использования exec (), и ваше описание укрепило мое понимание. - person TwilightSparkleTheGeek; 25.06.2014

что такое функция exec и ее семейство.

Семейство функций exec - это все функции, используемые для выполнения файла, такие как execl, execlp, execle, execv и execvp. Все они являются внешними интерфейсами для execve и предоставляют различные методы его вызова.

почему используется эта функция

Функции Exec используются, когда вы хотите выполнить (запустить) файл (программу).

и как это работает.

Они работают, перезаписывая текущий образ процесса тем, который вы запустили. Они заменяют (завершая) текущий запущенный процесс (тот, который вызывал команду exec) новым запущенным процессом.

Для получения дополнительных сведений: перейдите по этой ссылке.

person Reese Moore    schedule 17.11.2010

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

из командной оболочки.

person nategoose    schedule 17.11.2010

Когда процесс использует fork (), он создает дублирующую копию самого себя, и эти дубликаты становятся потомком процесса. Fork () реализован с помощью системного вызова clone () в Linux, который дважды возвращается из ядра.

  • Родителю возвращается ненулевое значение (идентификатор процесса дочернего процесса).
  • Дочернему элементу возвращается нулевое значение.
  • В случае, если дочерний элемент не был успешно создан из-за каких-либо проблем, таких как нехватка памяти, в fork () возвращается -1.

Давайте разберемся с этим на примере:

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 (блока управления процессом). Эти:

  1. PID - у дочернего и родительского процессов разные идентификаторы процесса.
  2. Ожидающие сигналы - дочерний элемент не наследует ожидающие сигналы родителя. При создании он будет пустым для дочернего процесса.
  3. Блокировки памяти - дочерний элемент не наследует блокировки памяти своего родителя. Блокировки памяти - это блокировки, которые можно использовать для блокировки области памяти, а затем эту область памяти нельзя переместить на диск.
  4. Блокировки записей - дочерний элемент не наследует блокировки записей своего родителя. Блокировки записи связаны с файловым блоком или целым файлом.
  5. Использование ресурсов процесса и затраченное время ЦП для дочернего объекта устанавливаются равными нулю.
  6. Потомок также не наследует таймеры от родителя.

А как же детская память? Создано ли для ребенка новое адресное пространство?

Ответов нет. После 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.

Почему родитель ждет дочернего процесса?

  1. Родитель может назначить задачу своему дочернему элементу и дождаться, пока он ее выполнит. Затем он может выполнять другую работу.
  2. Как только дочерний элемент завершается, все ресурсы, связанные с дочерним элементом, освобождаются, за исключением блока управления процессом. Теперь ребенок находится в состоянии зомби. Используя wait (), родитель может запросить статус дочернего элемента, а затем попросить ядро ​​освободить печатную плату. Если родитель не использует ожидание, ребенок останется в состоянии зомби.

Почему необходим системный вызов exec ()?

Необязательно использовать exec () с fork (). Если код, который будет выполнять дочерний элемент, находится в программе, связанной с родительским, exec () не требуется.

Но подумайте о случаях, когда ребенку приходится запускать несколько программ. Возьмем пример программы-оболочки. Он поддерживает несколько команд, таких как find, mv, cp, date и т. Д. Будет ли правильным включать программный код, связанный с этими командами, в одну программу или же потомок загружать эти программы в память, когда это необходимо?

Все зависит от вашего варианта использования. У вас есть веб-сервер, которому задан вход x, который возвращает 2 ^ x клиентам. Для каждого запроса веб-сервер создает нового дочернего элемента и просит его вычислить. Вы напишете отдельную программу для расчета этого и будете использовать exec ()? Или вы просто напишете вычислительный код внутри родительской программы?

Обычно создание процесса включает комбинацию вызовов fork (), exec (), wait () и exit ().

person Shivam Mitra    schedule 14.08.2019

Функции exec(3,3p) заменяют текущий процесс другим. То есть текущий процесс останавливается, а вместо него запускается другой, забирая некоторые ресурсы, которые были у исходной программы.

person Ignacio Vazquez-Abrams    schedule 17.11.2010
comment
Не совсем. Он заменяет текущий образ процесса новым образом процесса. Это тот же процесс с тем же идентификатором pid, той же средой и той же таблицей файловых дескрипторов. Что изменилось, так это вся виртуальная память и состояние процессора. - person JeremyP; 17.11.2010
comment
@JeremyP Здесь важен тот же дескриптор файла, он объясняет, как работает перенаправление в оболочках! Я был озадачен тем, как может работать перенаправление, если exec все перезаписывает! Спасибо - person FUD; 06.01.2017