++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()