В разделе 6.8 описаны функции setjmp() и longjmp(), позволяющие выполнять нелокальный переход из функции в код одной из процедур, вызвавших эту функцию. Мы можем это сделать и из обработчика. Это позволит восстановить работу программы после получения сигнала, сгенерированного аппаратным исключением (например, при ошибке доступа к памяти). Кроме того, этот метод позволяет перехватить сигнал и вернуть управление в конкретный участок программы. Например, оболочка по получении сигнала SIGINT (генерируемого, как правило, нажатием Ctrl+C) выполняет нелокальный переход для возвращения управления в основной цикл ввода (и, таким образом, чтения новой команды).
Однако при использовании стандартной функции longjmp() для выхода из обработчика сигнала возникает одна проблема. Ранее уже было отмечено, что после входа в обработчик ядро автоматически заносит активирующий его сигнал, так же как и любой другой указанный в поле act.sa_mask, в сигнальную маску процесса, а затем удаляет эти сигналы из маски, когда обработчик выполняет нормальный возврат.
Но что случается с сигнальной маской, когда мы выходим из обработчика с помощью функции longjmp()? Ответ зависит от происхождения конкретной реализации UNIX. В System V функция longjmp() не восстанавливает сигнальную маску, таким образом, блокированные сигналы так и остаются заблокированными до выхода из обработчика. Linux унаследовала модель поведения System V. (И, как правило, нам это мешает, ведь такое поведение оставляет сигнал, активировавший обработчик, заблокированным). В реализациях, восходящих к BSD, функция setjmp() сохраняет сигнальную маску в аргументе env, а функция longjmp() восстанавливает сохраненную маску. Иными словами, мы, возможно, не сможем использовать функцию longjmp() для выхода из обработчика сигнала.
В случае если при компиляции программы мы определяем макрос тестирования возможности _BSD_SOURCE, то функция setjmp() (glibc) следует семантике BSD.
Из-за вышеописанной разницы между двумя основными вариантами UNIX, комитет по подготовке стандарта POSIX.1-1990 решил не включать в него способ обработки сигнальной маски функциями setjump() и longjump(). Вместо этого комитет определил две новые функции — sigsetjump() и siglongjump(), предоставляющие явный контроль над сигнальной маской при выполнении нелокального перехода.
#include
int sigsetjmp(sigjmp_buf
Возвращает 0 при первом вызове, ненулевое значение при возврате через siglongjmp()
void siglongjmp(sigjmp_buf
Функции sigsetjump() и siglongjump() работают аналогично функциям setjump() и longjump(). Единственное отличие заключается в типе аргумента env (sigjmp_buf вместо jmp_buf), а также в дополнительном аргументе savings функции sigsetjump(). Если значение savings не нуль, значит, сигнальная маска процесса, актуальная на момент вызова функции sigsetjump(), сохранена в переменной env и восстановлена последующим вызовом siglongjump() с указанным именем аргумента env. Если же значение аргумента savings равно 0, значит, сигнальная маска процесса не была ни сохранена, ни восстановлена.
Функции longjump() и siglongjump() не включены в перечень безопасных для асинхронных сигналов (см. табл. 21.1), так как вызов небезопасной для асинхронных сигналов функции после выполнения нелокального перехода несет в себе такие же риски, как и вызов этой функции из обработчика сигнала. Более того, если обработчик прерывает основную программу во время обновления структуры данных, после чего происходит выход из обработчика с помощью нелокального перехода, это может оставить структуру в недозаполненном состоянии. Применение функции sigprocmask(), временно блокирующей сигнал во время выполнения важных обновлений, — один из способов избежать проблем в такой ситуации.
В листинге 21.2 демонстрируется различие в методах обработки сигнальной маски для нелокальных переходов двух типов. Эта программа устанавливает обработчик для сигнала SIGINT. Она создана таким образом, чтобы разрешать использование сочетания функций setjmp() плюс longjmp() или sigsetjmp() плюс siglongjmp() для выхода из обработчика сигнала в зависимости от того, был ли определен макрос компиляции USE_SIGSETJMP. Программа отображает текущие установки сигнальной маски как на момент входа в обработчик сигнала, так и после выполнения нелокального перехода, переносящего управление из обработчика сигнала обратно в основную программу.
При создании программы с тем расчетом, что для выхода из обработчика сигнала применяется функция longjump(), на экране мы увидим примерно следующее:
$ make — s sigmask_longjmp
$ ./sigmask_longjmp
Signal mask at startup:
Calling setjmp()
Received signal 2 (Interrupt), signal mask is:
2 (Interrupt)
After jump from handler, signal mask is:
2 (Interrupt)