Чтение онлайн

на главную - закладки

Жанры

Linux программирование в примерах
Шрифт:

void callback1(void) { printf("callback1 called\n"); }

void callback2(void) { printf("callback2 called\n"); }

void callback3(void) { printf("callback3 called\n"); }

/* main --- регистрация функций и завершение */

int main(int argc, char **argv) {

 printf("registering callback1\n"); atexit(callback1);

 printf("registering callback2\n"); atexit(callback2);

 printf("registering callback3\n"); atexit(callback3);

 printf("exiting now\n");

 exit(0);

}

Вот что происходит при запуске:

$ ch09-atexit

registering callback1 /* Запуск главной программы */

registering callback2

registering callback3

exiting now

callback3 called /* Функции обратного вызова запускаются в обратном

порядке */

callback2 called

callback1 called

Как показывает пример, функции, зарегистрированные с помощью

atexit
, запускаются в порядке, обратном порядку их регистрации: последние первыми. (Это обозначается также LIFO — last-in-first-out — вошедший последним выходит первым).

POSIX определяет функцию

_exit
. В отличие от
exit
, которая вызывает функции обратного вызова и выполняет
<stdio.h>
– очистку,
_exit
является «сразу заканчивающейся» функцией:

#include <unistd.h> /* POSIX */

void _exit(int status);

Системе передается

status
, как и для
exit
, но процесс завершается немедленно. Ядро все еще делает обычную очистку: все открытые файлы закрываются, использованная адресным пространством память освобождается, любые другие ресурсы, использованные процессом, также освобождаются.

На практике функция

_Exit
ISO С идентична
_exit
. Стандарт С говорит, что от реализации функции зависит, вызывает ли
_Exit
зарегистрированные
atexit
функции и закрывает ли открытые файлы. Для систем GLIBC это не так, и функция ведет себя подобно
_exit
.

Время использовать

_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] .

91

Мы это не придумываем. Терминология, конечно, не совсем правильна, но таким было чувство юмора разработчиков оригинальной Unix — Примеч. автора.

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

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

Поделиться:
Популярные книги

Далекие горизонты (сборник)

Ле Гуин Урсула Кребер
Фантастика:
фэнтези
научная фантастика
космическая фантастика
5.00
рейтинг книги
Далекие горизонты (сборник)

Язык программирования Си. Издание 3-е, исправленное

Ритчи Деннис М.
Компьютеры и Интернет:
программирование
5.00
рейтинг книги
Язык программирования Си. Издание 3-е, исправленное

Последний герой

Грэйдо Эллениэль и Эстель
2. Последний герой
Фантастика:
фэнтези
юмористическое фэнтези
6.60
рейтинг книги
Последний герой

Трилогия «Хроники Сиалы»

Пехов Алексей Юрьевич
3. В одном томе
Фантастика:
фэнтези
9.47
рейтинг книги
Трилогия «Хроники Сиалы»

Бессонница (др.перевод)

Кинг Стивен
17. Темная Башня
Фантастика:
ужасы и мистика
8.33
рейтинг книги
Бессонница (др.перевод)

Крымская война

Семанов Сергей Николаевич
Великие войны
Проза:
историческая проза
5.00
рейтинг книги
Крымская война

Старый, но крепкий 3

Крынов Макс
3. Культивация без насилия
Фантастика:
рпг
уся
фэнтези
5.00
рейтинг книги
Старый, но крепкий 3

"Фантастика 2025-77". Компиляция. Книги 1-33

Текшин Антон
Фантастика 2025. Компиляция
Фантастика:
боевая фантастика
попаданцы
альтернативная история
5.00
рейтинг книги
Фантастика 2025-77. Компиляция. Книги 1-33

#Бояръ-Аниме. Газлайтер. Том 36

Володин Григорий Григорьевич
36. История Телепата
Фантастика:
боевая фантастика
аниме
фэнтези
5.00
рейтинг книги
#Бояръ-Аниме. Газлайтер. Том 36

Локки 10. Потомок бога

Решетов Евгений Валерьевич
10. Локки
Фантастика:
фэнтези
юмористическое фэнтези
героическая фантастика
боевая фантастика
5.00
рейтинг книги
Локки 10. Потомок бога

Газлайтер. Том 39

Володин Григорий Григорьевич
39. История Телепата
Фантастика:
боевая фантастика
юмористическая фантастика
аниме
попаданцы
6.25
рейтинг книги
Газлайтер. Том 39

Дважды одаренный. Том VI

Тарс Элиан
6. Дважды одаренный
Фантастика:
аниме
альтернативная история
фэнтези
фантастика: прочее
5.25
рейтинг книги
Дважды одаренный. Том VI

Гобелены Фьонавара (сборник)

Кей Гай Гэвриел
Гобелены Фьонавара
Фантастика:
фэнтези
эпическая фантастика
6.25
рейтинг книги
Гобелены Фьонавара (сборник)

Газлайтер. Том 28

Володин Григорий Григорьевич
28. История Телепата
Фантастика:
боевая фантастика
аниме
попаданцы
5.60
рейтинг книги
Газлайтер. Том 28