В рассмотренном алгоритме обработки сигналов имеются некоторые несоответствия. Первое из них и наиболее важное связано с очисткой перед возвращением процесса в режим задачи того поля в пространстве процесса, которое содержит адрес пользовательской функции обработки сигнала. Если процессу снова понадобится обработать сигнал, ему опять придется прибегнуть к помощи системной функции signal. При этом могут возникнуть нежелательные последствия: например, могут создаться условия для конкуренции, если второй раз сигнал поступит до того, как процесс получит возможность запустить системную функцию. Поскольку процесс выполняется в режиме задачи, ядру следовало бы произвести переключение контекста, чтобы увеличить тем самым шансы процесса на получение сигнала до момента сброса значения поля функции обработки сигнала.
#include ‹signal.h›
main()
{
extern catcher();
signal(SIGINT, catcher);
kill(0, SIGINT);
}
catcher()
{
}
Рисунок 7.9. Исходный текст программы приема сигналов
**** VAX DISASSEMBLER ****
_main()
e4:
e6: pushab 0x18(pc)
ec: pushl $0x2
# в следующей строке вызывается функция signal
ee: calls $0x2,0x23(pc)
f5: pushl $0x2
f7: clrl -(sp)
# в следующей строке вызывается библиотечная процедура kill
f9: calls $0x2,0x8(pc)
100: ret
101: halt
102: halt
103: halt
_catcher()
104:
106: ret
107: halt
_kill()
108:
# в следующей строке вызывается внутреннее прерывание операционной системы
10a: chmk $0x25
10c: bgequ 0x6 ‹0x114›
10e: jmp 0x14(pc)
114: clrl r0
116: ret
Рисунок 7.10. Результат дисассемблирования программы приема сигналов
Рисунок 7.11. Стек задачи и область сохранения структур ядра до и после получения сигнала
Эту ситуацию можно разобрать на примере программы, представленной на Рисунке 7.12. Процесс обращается к системной функции signal для того, чтобы дать указание принимать сигналы о прерываниях и исполнять по их получении функцию sigcatcher. Затем он порождает новый процесс, запускает системную функцию nice, позволяющую сделать приоритет запуска процесса-родителя ниже приоритета его потомка (см. главу 8), и входит в бесконечный цикл. Порожденный процесс задерживает свое выполнение на 5 секунд, чтобы дать родительскому процессу время исполнить системную функцию nice и снизить свой приоритет. После этого порожденный процесс входит в цикл, в каждой итерации которого он посылает родительскому процессу сигнал о прерывании (посредством обращения к функции kill). Если в результате ошибки, например, из-за того, что родительский процесс больше не существует, kill завершается, то завершается и порожденный процесс. Вся идея состоит в том, что родительскому процессу следует запускать функцию обработки сигнала при каждом получении сигнала о прерывании. Функция обработки сигнала выводит сообщение и снова обращается к функции signal при очередном появлении сигнала о прерывании, родительский же процесс продолжает исполнять циклический набор команд.
Однако, возможна и следующая очередность наступления событий:
1. Порожденный процесс посылает родительскому процессу сигнал о прерывании.
2. Родительский процесс принимает сигнал и вызывает функцию обработки сигнала, но резервируется ядром, которое производит переключение контекста до того, как функция signal будет вызвана повторно.
3. Снова запускается порожденный процесс, который посылает родительскому процессу еще один сигнал о прерывании.
4. Родительский процесс получает второй сигнал о прерывании, но перед тем он не успел сделать никаких распоряжений относительно способа обработки сигнала. Когда выполнение родительского процесса будет возобновлено, он завершится.
#include ‹signal.h›
sigcatcher()
{
printf("PID %d принял сигнал\n", getpid()); /* печать PID */
signal(SIGINT, sigcatcher);
}
main()
{
int ppid;
signal(SIGINT, sigcatcher);
if (fork() == 0)
{
/* дать процессам время для выполнения установок */
sleep(5); /* библиотечная функция приостанова на 5 секунд */
ppid = getppid(); /* получить идентификатор родителя */