++sigusr1_count;
}
int main() {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = &handler
sigaction(SIGUSR1, &sa, NULL);
/* далее идет основной текст. */
/* ... */
printf("SIGUSR1 was raised %d times\n", sigusr1_count);
return 0;
}
3.4. Завершение процесса
Обычно процесс завершается одним из двух способов: либо выполняющаяся программа вызывает функцию exit(), либо функция main() заканчивается. У каждого процесса есть код завершения — число, возвращаемое родительскому процессу. Этот код передается в качестве аргумента функции exit() или возвращается функцией main().
Возможно также аварийное завершение процесса, в ответ на получение сигнала. Таковыми могут быть, например, упоминавшиеся выше сигналы SIGBUS, SIGSEGV и SIGFPE. Есть сигналы, явно запрашивающие прекращение работы процесса. В частности, сигнал SIGINT посылается, когда пользователь нажимает SIGTERM посылается процессу командной kill по умолчанию. Если программа вызывает функцию abort(), она посылает сама себе сигнал SIGABRT. Самый "могучий" из всех сигналов — SIGKILL: он приводит к безусловному уничтожению процесса и не может быть ни блокирован, ни обработан.
Любой сигнал можно послать с помощью команды kill, указав дополнительный флаг. Например, чтобы уничтожить процесс, послав ему сигнал SIGKILL, воспользуйтесь следующей командой:
% kill -KILL
Для отправки сигнала из программы предназначена функция kill(). Ее первым аргументом является идентификатор процесса. Второй аргумент — номер сигнала (стандартному поведению команды kill соответствует сигнал SIGTERM). Например, если переменная child_pid содержит идентификатор дочернего процесса, то следующая функция, вызываемая из родительского процесса, вызывает завершение работы потомка:
kill(child_pid, SIGTERM);
Для использования функции kill() необходимо включить в программу файлы и .
По существующему соглашению код завершения указывает на то, успешно ли выполнилась программа. Нулевой код говорит о том, что все в порядке, ненулевой код свидетельствует об ошибке. В последнем случае конкретное значение кода может подсказать природу ошибки. Подобным образом функционируют все компоненты GNU/Linux. Например, на это рассчитывает интерпретатор команд, когда в командных сценариях вызовы программ объединяются с помощью операторов && (логическое умножение) и || (логическое сложение) Таким образом, функция main() должна явно возвращать 0 при отсутствии ошибок.
Помните о следующем ограничении: несмотря на то что тип параметра функции exit(), как и тип возвращаемого значения функции main(), равен int, операционная система Linux записывает код завершения лишь в младший из четырех байтов. Это означает, что значение кода должно находиться в диапазоне от 0 до 127. Коды, значение которых больше 128, интерпретируются особым образом: когда процесс уничтожается вследствие получения сигнала, его код завершения равен 128 плюс номер сигнала.
3.4.1. Ожидание завершения процесса
Читатели, запускавшие программу fork-exec (см. листинг 3.4), должно быть, обратили внимание на то, что вывод команды ls часто появляется после того, как основная программа уже завершила свою работу. Это связано с тем, что дочерний процесс, в котором выполняется команда ls, планируется независимо от родительского процесса. Linux — многозадачная операционная система, процессы в ней выполняются одновременно, поэтому нельзя заранее предсказать, кто — предок или потомок — завершится раньше.
Но бывают ситуации, когда родительский процесс должен дождаться завершения одного или нескольких своих потомков. Это можно сделать с помощью функций семейства wait(). Они позволяют родительскому процессу получать информацию о завершении дочернего процесса. В семейство входят четыре функции, различающиеся объемом возвращаемой информации, а также способом задания дочернего процесса.
3.4.2. Системные вызовы wait()