Категории
Самые читаемые
PochitayKnigi » Компьютеры и Интернет » Программное обеспечение » Linux программирование в примерах - Арнольд Роббинс

Linux программирование в примерах - Арнольд Роббинс

Читать онлайн Linux программирование в примерах - Арнольд Роббинс

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 71 72 73 74 75 76 77 78 79 ... 165
Перейти на страницу:

Время использовать _exit() наступает, когда exec в порожденном процессе завершается неудачей. В этом случае вам не нужно использовать обычный exit(), поскольку это сбрасывает на диск данные буферов, хранящиеся в потоках FILE*. Когда позже родительский процесс сбрасывает на диск свои копии буферов, данные буфера оказываются записанными дважды; это очевидно нехорошо.

Например, предположим, что вы хотите запустить команду оболочки и хотите сами выполнить fork и exec. Такой код выглядел бы следующим образом:

char *shellcommand = "...";

pid_t child;

if ((child = fork()) == 0) { /* порожденный процесс */

 execl("/bin/sh", "sh", "-c", shellcommand, NULL);

 _exit(errno == ENOENT ? 127 : 126);

}

/* родитель продолжает */

Проверка значения errno и завершающего значения следуют соглашениям, используемым оболочкой POSIX. Если запрошенная программа не существует (ENOENT — нет для неё элемента в каталоге), завершающее значение равно 127. В противном случае, файл существует, но exec не могла быть выполнена по какой-то другой причине, поэтому статус завершения равен 126. Хорошая мысль следовать этим соглашениям также и в ваших программах. Вкратце, чтобы хорошо использовать exit() и atexit(), следует делать следующее:

• Определить небольшой набор значений статуса завершения, которые ваша программа будет использовать для сообщения этой информации вызывающему. Используйте для них в своем коде константы #define или enum.

• Решить, имеет ли смысл наличие функций обратного вызова для использования с atexit(). Если имеет, зарегистрировать их в main() в соответствующий момент; например, после анализа опций и инициализации всех структур данных, которые функция обратного вызова должна очищать. Помните, что функции должны вызываться в порядке LIFO (последняя вызывается первой).

• Использовать exit() для выхода из программы во всех местах, когда что-то идет не так и когда выход является правильным действием. Используйте коды ошибок, которые определили.

• Исключением является main(), для которой можно использовать при желании return. Наш собственный стиль заключается обычно в использовании exit() при наличии проблем и 'return 0' в конце main(), если все прошло хорошо.

• Использовать _exit() или _Exit() в порожденном процессе, если exec() завершается неудачей.

9.1.6. Использование статуса завершения порожденного процесса

Когда процесс заканчивается, нормальным ходом событий для ядра является освобождение всех его ресурсов. Ядро сохраняет статус завершения законченного процесса, также, как сведения о ресурсах, которые он использовал в своей работе, a PID продолжает считаться используемым. Такой завершившийся процесс называется зомби.

Родительский процесс, будь то первоначальный родитель или init, может получить статус завершения порожденного процесса. Или, посредством использования функций BDS, которые не стандартизованы POSIX, можно получить статус завершения вместе со сведениями об использовании ресурсов. Использование статуса осуществляется ожиданием окончания процесса: это известно также как пожинание (reaping) процесса[91].

Между механизмами, которые ожидают завершения потомков, и сигнальными механизмами, которые мы еще не обсуждали, есть значительное взаимодействие. Что из них описать вначале представляет собой нечто вроде проблемы курицы и яйца; мы решили сначала поговорить сначала о механизмах ожидания порожденного процесса, а глава 10 «Сигналы» дает полный рассказ о сигналах.

Пока достаточно понять, что сигнал является способом уведомления процесса о том, что произошло некоторое событие. Процессы могут генерировать сигналы, которые посылаются самим себе, или сигналы могут посылаться извне другими процессами или пользователем за терминалом. Например, CTRL-C посылает сигнал «прерывания», a CTRL-Z посылает сигнал управления работой «стоп».

По умолчанию, многие сигналы, такие, как сигнал прерывания, заставляют получающий процесс закончиться. Другие, такие, как сигналы управления работами, вызывают изменение его состояния. Механизмы ожидания порожденного процесса могут определить, претерпел ли процесс сигнал завершения, и если да, какой это был сигнал. То же верно и для остановки процесса и, на некоторых системах возобновления процесса.

9.1.6.1. Использование функций POSIX: wait() и waitpid()

Первоначальным системным вызовом V7 был wait(). Более новым вызовом POSIX, основанным на возможностях BSD, является waitpid(). Объявления функций следующие:

#include <sys/types.h> /* POSIX */

#include <sys/wait.h>

pid_t wait(int *status);

pid_t waitpid(pid_t pid, int *status, int options);

wait() ждет завершения любого порожденного процесса; сведения о том, как он завершился, возвращаются в *status. (Вскоре мы обсудим, как интерпретировать *status.) Возвращаемое значение является PID завершившегося процесса или -1, если возникла ошибка.

Если порожденных процессов нет, wait() возвращает -1 с errno, установленным в ECHILD (отсутствует порожденный процесс). В противном случае, функция ждет завершения первого порожденного процесса или поступления сигнала.

Функция waitpid() дает возможность ждать завершения определенного порожденного процесса. Она предоставляет значительную гибкость и является предпочтительной для использования функцией. Она также возвращает PID закончившегося процесса или -1 при возникновении ошибки. Аргументы следующие:

pid_t pid

Значение указывает, завершения какого порожденного процесса ждать как по-настоящему pid, так и по группе процесса. Смысл значения pid следующий:

pid < -1 Ждать завершения любого порожденного процесса с ID группы процесса, равной абсолютному значению pid.

pid = -1 Ждать завершения любого порожденного процесса. Таким способом работает wait().

pid = 0  Ждать завершения любого порожденного процесса с ID группы процесса, равной ID группе родительского процесса.

pid > 0  Ждать завершения конкретного процесса с PID, равным pid.

int *status

То же, что и для wait(). <sys/wait.h> определяет различные макросы, которые интерпретируют значение в *status, которые мы вскоре опишем

int options

Этот параметр должен быть равен либо 0, либо побитовым ИЛИ одного или более из следующих флагов:

 WNOHANG

 Если ни один порожденный процесс не завершился, вернуться немедленно. Таким способом можно периодически проверять, не закончился ли какой- нибудь порожденный процесс. (Такая периодическая проверка известна как опрашивание события.)

 WUNTRACED

 Вернуть сведения о порожденном процессе, который остановился, но еще не завершился. (Например, для управления работой.)

 WCONTINUED

 (XSI.) Вернуть сведения о порожденном процессе, который продолжился, если его статус не сообщался с момента изменения. Это также для управления работой. Этот флаг является расширением XSI и не доступен под GNU/Linux.

С заполненным значением *status работают несколько макросов, определяющие, что случилось. Они имеют тенденцию образовывать пары: один макрос для определения, что что-то случилось, и если этот макрос истинен, еще один макрос позволяет получить подробности. Макросы следующие:

WIFEXITED(status)

Этот макрос не равен нулю (true), если процесс завершился (в противоположность изменению состояния).

WEXITSTATUS(status)

Этот макрос дает статус завершения; он равен восьми наименее значимым битам значения, переданного exit() или возвращенного из main(). Этот макрос следует использовать лишь если WIFEXIDED(status) равен true.

WIFSIGNALED(status)

Этот макрос не равен нулю, если процесс подвергся действию завершающего сигнала death-by-signal.

WTERMSIG(status)

Этот макрос предоставляет номер сигнала, который завершил процесс. Этот макрос следует использовать, лишь когда WIFSIGNALED(status) равен true.

WIFSTOPPED(status)

Этот макрос не равен нулю, если процесс был остановлен.

WSTOPSIG(status)

Этот макрос предоставляет номер сигнала, который остановил процесс. (Процесс остановить могут несколько сигналов.) Этот макрос следует использовать лишь когда WIFSTOPPED(status) равен true. Сигналы управления работами обсуждаются в разделе 10.8.2 «Сигналы управления работой».

WIFCONTINUED(status)

(XSI.) Этот макрос не равен нулю, если процесс был продолжен. Соответствующего макроса WCONTSIG() нет, поскольку лишь один сигнал может вызвать продолжение процесса.

1 ... 71 72 73 74 75 76 77 78 79 ... 165
Перейти на страницу:
Тут вы можете бесплатно читать книгу Linux программирование в примерах - Арнольд Роббинс.
Комментарии