Оба случая относятся к состоянию гонки. Одним решением для этих проблем является как можно большее упрощение обработчиков сигналов. Это можно сделать, создав флаговые переменные, указывающие на появление сигнала. Обработчик сигнала устанавливает переменную в true и возвращается. Затем основная логика проверяет флаговую переменную в стратегических местах:
int sig_int_flag = 0; /* обработчик сигнала устанавливает в true */
void int_handler(int signum) {
sig_int_flag = 1;
}
int main(int argc, char **argv) {
bsd_signal(SIGINT, int_handler);
/* ...программа продолжается... */
if (sig_int_flag) {
/* возник SIGINT, обработать его */
}
/* ...оставшаяся логика... */
}
(Обратите внимание, что эта стратегия
Стандарт С вводит специальный тип — sig_atomic_t — для использования с такими флаговыми переменными. Идея, скрывающаяся за этим именем, в том, что присвоение значений переменным этого типа является int осуществляется атомарно, тогда как инициализация значений в структуре осуществляется либо путем копирования всех байтов в (сгенерированном компилятором) цикле, либо с помощью инструкции «блочного копирования», которая может быть прервана. Поскольку присвоение значения sig_atomic_t является атомарным, раз начавшись, оно завершается до того, как может появиться другой сигнал и прервать его.
Наличие особого типа является лишь частью истории. Переменные sig_atomic_t должны быть также объявлены как volatile:
volatile sig_atomic_t sig_int_flag = 0; /* обработчик сигнала устанавливает в true */
/* ...оставшаяся часть кода как раньше... */
Ключевое слово volatile сообщает компилятору, что переменная может быть изменена извне, за спиной компилятора, так сказать. Это не позволяет компилятору применить оптимизацию, которая могла бы в противном случае повлиять на правильность кода
Структурирование приложения исключительно вокруг переменных sig_atomic_t ненадежно. Правильный способ обращения с сигналами показан далее, в разделе 10.7 «Сигналы для межпроцессного взаимодействия».
10.4.6. Дополнительные предостережения
Стандарт POSIX предусматривает для обработчиков сигналов несколько предостережений:
• Что случается, когда возвращаются обработчики для SIGFPE, SIGILL, SIGSEGV или любых других сигналов, представляющих «вычислительные исключения», не определено.
• Если обработчик был вызван в результате вызова abort(), raise() или kill(), он не может вызвать raise(). abort() описана в разделе 12.4 «Совершение самоубийства: abort()», a kill() описана далее в этой главе. (Описанная далее функция API sigaction() с обработчиком сигнала, принимающая три аргумента, дает возможность сообщить об этом, если это имеет место.)
• Обработчики сигналов могут вызвать лишь функции из табл. 10.2. В частности, они должны избегать функций . Проблема в том, что во время работы функции может возникнуть прерывание, когда внутреннее состояние библиотечной функции находится в середине процесса обновления. Дальнейшие вызовы функций могут повредить это внутреннее состояние.
Список в табл. 10.2 происходит из раздела 2.4 тома
Таблица 10.2. Функции, которые могут быть вызваны из обработчика сигнала