Читаем Linux программирование в примерах полностью

 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(), которая вызывает функции обратного вызова и выполняет -очистку, _exit() является «сразу заканчивающейся» функцией:

#include /* 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 продолжает считаться используемым. Такой завершившийся процесс называется зомби.

Перейти на страницу:

Похожие книги

C++ Primer Plus
C++ Primer Plus

C++ Primer Plus is a carefully crafted, complete tutorial on one of the most significant and widely used programming languages today. An accessible and easy-to-use self-study guide, this book is appropriate for both serious students of programming as well as developers already proficient in other languages.The sixth edition of C++ Primer Plus has been updated and expanded to cover the latest developments in C++, including a detailed look at the new C++11 standard.Author and educator Stephen Prata has created an introduction to C++ that is instructive, clear, and insightful. Fundamental programming concepts are explained along with details of the C++ language. Many short, practical examples illustrate just one or two concepts at a time, encouraging readers to master new topics by immediately putting them to use.Review questions and programming exercises at the end of each chapter help readers zero in on the most critical information and digest the most difficult concepts.In C++ Primer Plus, you'll find depth, breadth, and a variety of teaching techniques and tools to enhance your learning:• A new detailed chapter on the changes and additional capabilities introduced in the C++11 standard• Complete, integrated discussion of both basic C language and additional C++ features• Clear guidance about when and why to use a feature• Hands-on learning with concise and simple examples that develop your understanding a concept or two at a time• Hundreds of practical sample programs• Review questions and programming exercises at the end of each chapter to test your understanding• Coverage of generic C++ gives you the greatest possible flexibility• Teaches the ISO standard, including discussions of templates, the Standard Template Library, the string class, exceptions, RTTI, and namespaces

Стивен Прата

Программирование, программы, базы данных