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

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

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

Ответ 1

Упрощенно, в 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

Ответ 2

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

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

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

  • int execl (const char * path, const char * arg,...);
  • int execlp (const char * file, const char * arg,...);
  • int execle (const char * path, const char * arg,..., char * const envp []);
  • int execv (const char * path, char * const argv []);
  • int execvp (const char * file, char * const argv []);
  • int execvpe (const char * file, char * const argv [], char * const envp []);

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

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

man 3 exec  # if you are running a UNIX system

Ответ 3

Семейство функций exec заставляет ваш процесс выполнять другую программу, заменяя старую запущенную программу. I.e., если вы вызываете

execl("/bin/ls", "ls", NULL);

то программа ls выполняется с идентификатором процесса, текущим рабочим каталогом и пользователем/группой (правами доступа) процесса, который называется execl. Впоследствии исходная программа больше не работает.

Для запуска нового процесса используется системный вызов fork. Чтобы выполнить программу без замены оригинала, вам нужно fork, затем exec.

Ответ 4

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 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

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

Ответ 5

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

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

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

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

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

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

Для получения дополнительной информации: см. Эту ссылку.

Ответ 6

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

Ответ 7

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

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

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

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 - у обоих детей и родителей разные ID процесса.
  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 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(), родитель может узнать о статусе child, а затем попросить ядро освободить PCB. Если родитель не использует ожидание, ребенок останется в состоянии зомби.

Зачем нужен системный вызов exec()?

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

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

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

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