Оба случая относятся к состоянию гонки. Одним решением для этих проблем является как можно большее упрощение обработчиков сигналов. Это можно сделать, создав флаговые переменные, указывающие на появление сигнала. Обработчик сигнала устанавливает переменную в 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. В частности, они должны избегать функций
<stdio.h>
. Проблема в том, что во время работы функции
<stdio.h>
может возникнуть прерывание, когда внутреннее состояние библиотечной функции находится в середине процесса обновления. Дальнейшие вызовы функций
<stdio.h>
могут повредить это внутреннее состояние.
Список в табл. 10.2 происходит из раздела 2.4 тома System Interfaces (Системные интерфейсы) стандарта POSIX 2001. Многие из этих функций относятся к сложному API и больше не рассматриваются в данной книге.
Таблица 10.2. Функции, которые могут быть вызваны из обработчика сигнала
_Exit()
| fpathconf()
| raise()
| sigqueue()
|
_exit()
| fstat()
| read()
| sigset()
|
accept()
| fsync()
| readlink()
| sigsuspend()
|
access()
| ftruncate()
| recv()
| sleep()
|
aio_error()
| getegid()
| recvfrom()
| socket()
|
aio_return()
| geteuid()
| recvmsg()
| socketpair()
|
aio_suspend()
| getgid()
| rename()
| stat()
|
alarm()
| getgroups()
| rmdir()
| sysmlink()
|
bind()
| getpeername()
| select()
| sysconf()
|
cfgetispeed()
| getpgrp()
| sem_post()
| tcdrain()
|
cfgetospeed()
| getpid()
| send()
| tcflow()
|
cfsetispeed()
| getppid()
| sendmsg()
| tcflush()
|
cfsetospeed()
| getsockname()
| sendto()
| tcgetattr()
|
chdir()
| getsockopt()
| setgid()
| tcgetpgrp()
|
chmod()
| getuid()
| setpgid()
| tcsendbreak()
|
chown()
| kill()
| setsid()
| tcsetattr()
|
clock_gettime()
| link()
| setsockopt()
| tcsetpgrp()
|
close()
| listen()
| setuid()
| time()
|
connect()
| lseek()
| shutdown()
| timer_getoverrun()
|
creat()
| lstat()
| sigaction()
| timer_gettime()
|
dup()
| mkdir()
| sigaddset()
| timer_settime()
|
dup2()
| mkfifo()
| sigdelset()
| times()
|
execle()
| open()
| sigemptyset()
| umask()
|
execve()
| pathconf()
| sigfillset()
| uname()
|
fchmod()
| pause()
| sigismember()
| unlink()
|
fchown()
| pipe()
| signal()
| utime()
|
fcntl()
| poll()
| sigpause()
| wait()
|
fdatasync()
| posix_trace_event()
| sigpending()
| waitpid()
|
fork()
| pselect()
| sigprocmask()
| write()
|