Чтение онлайн

ЖАНРЫ

Linux программирование в примерах

Роббинс Арнольд

Шрифт:

 /* ...оставшаяся логика... */

}

(Обратите внимание, что эта стратегия уменьшает окно уязвимости, но не устраняет его).

Стандарт С вводит специальный тип —

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

10.4.7.

Наша история до настоящего времени, эпизод 1

Сигналы являются сложной темой, и она становится еще более сбивающей с толку. Поэтому давайте на время сделаем остановку, сделаем шаг назад и подведем итог обсужденному до сих пор:

• Сигналы являются указанием того, что произошло некоторое внешнее событие.

• 

raise
является функцией ISO С для отправки сигнала текущему процессу. Как отправлять сигналы другим процессам, нам еще предстоит описать.

• 

signal
контролирует диспозицию сигнала, т.е. реакцию процесса на сигнал, когда он появляется. Сигнал можно оставить системе для обработки по умолчанию, проигнорировать или перехватить.

• Когда сигнал перехватывается, вызывается функция-обработчик. Вот где сложность начинает поднимать свою безобразную голову:

 • ISO С не определяет, восстанавливается ли диспозиция сигнала по умолчанию до вызова обработчика или она остается на месте. Первое является поведением V7 и современных систем System V, таких, как Solaris. Последнее является поведением BSD, используемым также в GNU/Linux. (Для форсирования поведения BSD может использоваться функция POSIX

bsd_signal
.)

• То, что случается при прерывании сигналом системного вызова, также различается в традиционной и BSD линейках. Традиционные системы возвращают -1 с errno, установленным в

EINTR
. BSD системы повторно запускают системный вызов после возвращения из обработчика. Макрос GLIBC
TEMP_FAILURE_RETRY
может помочь вам написать код для обработки системных вызовов, возвращающих -1 с
errno
, установленным в
EINTR
.

POSIX требует, чтобы частично выполненный системный вызов возвращал успешное завершение, указав, сколько работы было выполнено. Системный вызов, который еще не начал выполняться, вызывается повторно.

• Механизм

signal
предоставляет плодотворную почву для появления условий гонки. В этой ситуации помогает тип данных ISO С
sig_atomic_t
, но он не решает проблему, и определенный таким способом механизм не может обезопасить от проявления условий гонки.

• Применяется ряд дополнительных предостережений, и в частности, из обработчика сигнала безопасно может вызываться лишь часть стандартных библиотечных функций.

Несмотря на эти проблемы интерфейса

signal
для простых программ достаточно, и он все еще широко используется.

10.5. API сигналов System V Release 3:

sigset
и др.

BSD 4.0 (примерно с 1980 г.) ввел дополнительные функции API для предоставления «надежных» сигналов. [109] В частности, стало возможным блокировать сигналы. Другими словами, программа могла сообщить ядру: «Зависни на этих конкретных сигналах в течении следующего небольшого промежутка времени, затем доставь их мне, когда я буду готов их принять». Большим преимуществом является то, что эта особенность упрощает обработчики сигналов, которые автоматически запускаются со своим заблокированным сигналом (чтобы избежать проблемы одновременной обработки двух сигналов) и, возможно, также и с другими заблокированными сигналами.

109

Для использования API требуется компоновка с отдельной библиотекой, —

ljobs
Примеч. автора.

System V Release 3 (примерно с 1984 г.) приняла эти API и популяризовала их, в большинстве связанных с Unix документации и книгах вы, возможно, увидите, что на эти API ссылаются, как ведущие начало от System V Release 3. Эти функции следующие:

#include <signal.h> /* XSI */

int sighold(int sig); /* Добавить sig к маске сигналов процесса */

int sigrelse(int sig); /* Удалить sig из маски сигналов процесса */

int sigignore(int sig); /* Сокращение для sigset(sig, SIG_IGN) */

Поделиться с друзьями: