Как отмечалось в разделе 22.5, синхронно генерируемые сигналы доставляются незамедлительно. Например, аппаратное исключение приводит к немедленной генерации сигнала, а когда процесс посылает самому себе сигнал с помощью функции raise(), то сигнал доставляется перед тем, как происходит возврат из вызова raise().
При асинхронной генерации сигнала может наблюдаться (небольшая) задержка во время нахождения его в режиме ожидания после генерации и перед фактической доставкой, даже если этот сигнал не заблокирован. Причиной этого является тот факт, что ядро доставляет в процесс ожидающий сигнал только при следующем переключении из режима ядра в режим пользователя во время выполнения данного процесса. На практике это означает, что процесс доставляется в один из следующих моментов:
• когда процесс заново запланирован после предшествующего тайм-аута (то есть в начале тайм-слота);
• по завершении системного вызова (доставка сигнала может привести к преждевременному завершению блокирующего системного вызова).
Если несколько сигналов ожидают доставки в процесс и были одновременно разблокированы функцией sigprocmask(), то все эти сигналы будут незамедлительно доставлены в процесс.
В текущих версиях реализаций ядро Linux доставляет сигналы в порядке возрастания. Например, при одновременном разблокировании сигналов SIGINT (сигнал 2) и SIGQUIT (сигнал 3) сигнал SIGINT будет доставлен раньше, чем сигнал SIGQUIT вне зависимости от того, в какой последовательности эти сигналы были сгенерированы.
Впрочем, мы не можем всецело полагаться на то, что (стандартные) сигналы будут доставляться в какой-то конкретной очередности, так как согласно стандарту SUSv3 очередность доставки нескольких сигналов зависит от реализации системы. (Это высказывание действительно только для стандартных сигналов. Как мы увидим в разделе 22.8, стандарты, специфицирующие сигналы реального времени, гарантируют очередность доставки нескольких разблокированных сигналов реального времени.)
В случаях, когда несколько разблокированных сигналов ожидают доставки, если переключение между режимами ядра и пользователя происходит во время выполнения обработчика, то выполнение этого обработчика будет прервано активацией второго обработчика сигнала (и т. д.), как показано на рис. 22.1.
Рис. 22.1.
В этом разделе будет показано, как реализовать функцию signal() через функцию sigaction(). Реализация прямолинейна, однако следует учесть тот факт, что исторически и в разных реализациях UNIX семантика функции signal() была различной. В частности, ранние реализации сигналов были ненадежными, а это означало следующее.
• По входу в обработчик сигнала диспозиция этого сигнала сбрасывалась до значения по умолчанию. (Это соответствует флагу SA_RESETHAND, описанному в разделе 20.13.) Чтобы при повторной доставке этого же сигнала был активирован обработчик сигнала, программист должен вызывать signal() внутри обработчика, и тогда обработчик явным образом переустановится. Проблема такого подхода заключается в наличии небольшого временного промежутка между входом в обработчик и переустановкой обработчика. Если сигнал прибывает как раз в этот промежуток, то он будет обработан в соответствии с диспозицией по умолчанию.
• Доставка последующих копий сигнала не блокировалась во время выполнения обработчика сигнала. (Соответствует флагу SA_NODEFER, описанному в разделе 20.13.) Это означало, что если сигнал был повторно доставлен во время выполнения обработчика, то происходила рекурсивная активация обработчика. При достаточно интенсивном потоке сигналов происходящие рекурсивные активации могли переполнять стек.
В добавок к ненадежности ранние версии UNIX не предоставляли автоматический перезапуск системных вызовов (иными словами, поведение, описанное для флага SA_RESTART в разделе 21.5).
Надежные сигналы 4.2BSD сняли эти ограничения, чему также последовали некоторые другие реализации UNIX. Однако старая семантика сохраняется и сегодня в реализации функции signal() в System V, более того, в современных стандартах, таких как SUSv3 и С99, эти аспекты намеренно не специфицированы.
Соединяя все вышесказанное, мы реализуем функцию signal() как показано в листинге 22.1. По умолчанию эта реализация предоставляет современную семантику сигналов. При компиляции с опцией — DOLD_SIGNAL эта реализация предоставит раннюю ненадежную семантику сигналов, автоматический перезапуск системных вызовов также будет отключен.
Листинг 22.1. Реализация функции signal()
signals/signal.c
#include
typedef void (*sighandler_t)(int);
sighandler_t
signal(int sig, sighandler_t handler)
{
struct sigaction newDisp, prevDisp;
newDisp.sa_handler = handler;
sigemptyset(&newDisp.sa_mask);