26.2. Представьте, что у вас имеется три процесса, которые соотносятся между собой как прародитель, родитель и потомок; при этом прародитель не вызывает wait() сразу же после завершения родителя, в результате чего последний становится «зомби». В какой момент потомок будет «удочерен» процессом init (на что будет указывать вызов getppid(), возвращающий 1): после завершения родителя или после того, как прародитель выполнит вызов wait()? Напишите программу, чтобы проверить свой ответ.
26.3. Замените в листинге 26.3 вызов waitpid() на waitid() (child_status.c). Вызов функции printWaitStatus() должен быть заменен кодом, который выводит подходящие поля из структуры siginfo_t, возвращенной из waitid().
26.4. В листинге 26.4 (make_zombie.c) используется вызов sleep(), благодаря которому дочерний процесс получает шанс выполниться и завершить работу до того, как родитель вызовет system(). Такой подход потенциально может привести к состоянию гонки. Измените программу, чтобы исключить эту возможность; используйте сигналы для синхронизации родителя и потомка.
27. Выполнение программы
Эта глава является логическим продолжением предыдущих глав, посвященных созданию и завершению процессов. Здесь мы рассмотрим системный вызов execve(), с помощью которого процесс может заменить текущую программу какой-то другой. Затем мы покажем, как реализовать функцию system(), которая позволяет вызывающему ее процессу выполнить произвольную консольную команду.
Системный вызов execve() загружает в память процесса новую программу. Во время этой операции старая программа удаляется вместе со стеком, данными и кучей и заменяется аналогичными частями новой программы. Выполнив инициализационный и загрузочный код из библиотеки С (например, статические конструкторы в C++ или функции языка С, объявленные с помощью атрибута constructor, доступного в gcc и описанного в разделе 42.4), новая программа начинает работу со своей функции main().
Чаще всего вызов execve() применяется в потомке, созданном с помощью функции fork(), хотя его можно использовать и без предварительного запуска этой функции.
Системный вызов execve() задействуется в качестве основы для различных библиотечных функций, имена которых начинаются с exec. Каждая из них предоставляет свой особый интерфейс к одним и тем же возможностям. Загрузка новой программы посредством любой из этих функций обычно называется операцией exec или просто обозначается как exec(). Мы начнем с описания вызова execve(), после чего перейдем к библиотечным функциям.
#include
int execve(const char *
Ничего не возвращает при успешном завершении; возвращает –1 при возникновении ошибки
Аргумент pathname содержит путь к новой программе, которая должна быть загружена в память процесса. Этот путь может быть абсолютным (на что указывает знак / в начале) или относительным (то есть зависеть от текущего каталога вызывающей программы).
Аргумент argv содержит параметры командной строки, которые будут переданы новой программе. Этот аргумент соответствует второму аргументу (argv) функции main() в языке С и имеет ту же форму; это список указателей, ссылающихся на символьные строки, который завершается значением NULL. Значение argv[0] соответствует имени команды и обычно совпадает с названием исполняемого файла (то есть последней части pathname).
Последний аргумент, envp, предоставляет новой программе список переменных среды. Он соответствует массиву environ новой программы; это список указателей, ссылающихся на символьные строки вида ИМЯ=значение, который завершается значением NULL (см. раздел 6.7).
Linux предоставляет файл /proc/PID/exe, который является символьной ссылкой и содержит абсолютный путь к исполняемому файлу, запущенному соответствующим процессом.
После вызова execve() идентификатор процесса остается неизменным, поскольку сам процесс продолжает существовать. Не меняются и некоторые другие атрибуты, как описывается в разделе 28.4.
Если для файла программы, указанной в аргументе pathname, выставлен бит установки ID пользователя (или группы), значение соответствующего действующего ID становится равным значению ID пользователя (группы) владельца файла. Это механизм временного предоставления привилегий пользователям на период выполнения программы (см. раздел 9.3).
Вне зависимости от того, были ли изменены действующие идентификаторы (имена) пользователя и группы, вызов execve() копирует их в свои сохраненные биты.
Поскольку в случае успеха вызов execve() заменяет вызвавшую его программу, он никогда не возвращает значения. Нам не нужно проверять результат выполнения execve(), поскольку он всегда будет равен –1. Иначе говоря, сам факт того, что вызов что-то вернул, уже свидетельствует об ошибке, причину которой обычно можно узнать с помощью аргумента errno. Среди ошибок, возвращаемых таким образом, можно выделить следующие.