Как Linux определяет приоритеты пользовательских обработчиков сигналов?
На прошлой неделе мы читали лекцию о том, как ОС (в данном случае Linux, а в данном конкретном случае наш школьный сервер использует SUSE Linux 11) обрабатывает прерывания. Следует отметить, что для большинства сигналов вы можете перехватить прерывание и определить свой собственный обработчик сигнала для запуска вместо стандартного. Мы использовали пример, чтобы проиллюстрировать это, и я обнаружил то, что сначала показалось мне интересным поведением. Вот код:
#include <stdio.h>
#include <signal.h>
#define INPUTLEN 100
main(int ac, char *av[])
{
void inthandler (int);
void quithandler (int);
char input[INPUTLEN];
int nchars;
signal(SIGINT, inthandler);
signal(SIGQUIT, quithandler);
do {
printf("nType a messagen");
nchars = read(0, input, (INPUTLEN - 1));
if ( nchars == -1)
perror("read returned an error");
else {
input[nchars] = '';
printf("You typed: %s", input);
}
}
while(strncmp(input, "quit" , 4) != 0);
}
void inthandler(int s)
{
printf(" Received Signal %d ....waitingn", s);
int i = 0;
for(int i; i<3; ++i){
sleep(1);
printf("inth=%dn",i);
}
printf(" Leaving inthandler n");
}
void quithandler(int s)
{
printf(" Received Signal %d ....waitingn", s);
for(int i; i<7; ++i){
sleep(1);
printf("quith=%dn",i);
} printf(" Leaving quithandler n");
}
Итак, при запуске этого кода я ожидал чего-то вроде это:
- запуск кода.... ^C
- введите inthandler, выполнение цикла, нажмите ^
- выход из inthandler, переход в quithandler, выполнение цикла quithandler
- ^C назад к интхандлеру. Если я выполняю ^C снова, пока я нахожусь в inthandler, игнорируйте последовательные сигналы inthandler, пока текущий inthandler не закончит обработку.
Я нашел нечто, что, основываясь на наблюдениях, похоже на вложенное "планирование" сигналов на глубину 2 очереди. Если, например, я ввожу следующие прерывания в быстрой последовательности:
- ^C,^, ^C,^,^, ^C
Я получу следующее поведение / вывод из кода:
^CReceived signal 2 ....waiting
^Received Signal 3 ....waiting
^C^^^C quith=0
quith=1
quith=2
quith=3
quith=4
quith=5
quith=6
quith=7
Leaving quithandler
Received Signal 3 ....waiting
quith=1
quith=2
quith=3
quith=4
quith=5
quith=6
quith=7
Leaving quithandler
inth=0
inth=1
inth=2
inth=3
Leaving inthandler
Received Signal 2 ....waiting
inth=0
inth=1
inth=2
inth=3
Leaving inthandler
Другими словами, он, по-видимому, обрабатывается следующим образом:
- Прием первого сигнала ^C
- принять сигнал^, "задержать" inthandler и перейти в quithandler
- получите следующий сигнал ^C, но поскольку мы уже "вложены" в inthandler, поместите его в конец "очереди" inthandler
- Прием quithandler, место в конце очереди quithandler.
- выполняйте quithandler, пока очередь не опустеет. Игнорируйте третий quithandler, потому что он, кажется, имеет только глубину очереди 2.
- оставьте quithandler и выполните 2 оставшихся inthandlers. Игнорируйте последний inthandler, потому что queue-глубина 2.
Я показал поведение моему профессору, и он, кажется, согласен, что" вложенная глубина очереди 2 " - это то, что происходит, но мы не уверены на 100%, почему (он исходит из аппаратного обеспечения фон и только начал преподавать этот класс). Я хотел написать в SO, чтобы посмотреть, может ли кто-нибудь пролить свет на то, почему/как Linux обрабатывает эти сигналы, поскольку мы не совсем ожидали некоторого поведения, т. е. вложенности.
Я думаю, что тестового случая, который я написал, должно быть достаточно, чтобы проиллюстрировать, что происходит, но вот куча скриншотов дополнительных тестовых случаев:
Http://imgur.com/Vya7JeY,fjfmrjd, 30YRQfk, uHHXFu5, Pj35NbF
Я хотел уехать из города. дополнительные тестовые случаи в качестве ссылки, поскольку они являются своего рода большими скриншотами.
Спасибо!
2 ответа:
Правила (для сигналов не реального времени, таких как
SIGQUIT
иSIGINT
, которые вы используете):
- по умолчанию, сигнал маскируется, когда вошла в свой обработчик, и разоблачен, когда обработчик завершает работу;
- Если сигнал поднят во время маскировки, он остается ожидающим и будет доставлен, если/когда этот сигнал будет снят.
Состояниеожидания является двоичным - сигнал либо находится в состоянии ожидания, либо не находится в состоянии ожидания. Если сигнал поднимается несколько раз, пока замаскированный, он все равно будет доставлен только один раз, когда разоблачен.
Итак, что происходит в вашем примере:
SIGINT
вызывается, и обработчик сигналаinthandler()
начинает выполняться с маскировкойSIGINT
.SIGQUIT
вызывается, и обработчик сигналаquithandler()
начинает выполнение (прерываниеinthandler()
) с маскировкойSIGQUIT
.SIGINT
поднимается, добавляяSIGINT
к набору ожидающих сигналов (поскольку он замаскирован).SIGQUIT
поднимается, добавляяSIGQUIT
к набору отложенных сигналов (потому что он замаскирован).SIGQUIT
поднимается, но ничего не происходит, потому чтоSIGQUIT
уже находится в ожидании.SIGINT
поднимается, но ничего не происходит, потому чтоSIGINT
уже находится в ожидании.quithandler()
завершает выполнение, иSIGQUIT
снимается маска. ПосколькуSIGQUIT
находится в ожидании, он затем доставляется, иquithandler()
снова начинает выполняться (сSIGQUIT
снова замаскированным).quithandler()
завершает выполнение во второй раз, иSIGQUIT
снимается маска.SIGQUIT
не находится в ожидании, поэтомуinthandler()
затем возобновляется выполнение (сSIGINT
все еще в маске).inthandler()
завершает выполнение, иSIGINT
разоблачается. ПосколькуSIGINT
находится в ожидании, он затем доставляется, иinthandler()
снова начинает выполняться (сSIGINT
снова замаскированным).inthandler()
завершает выполнение во второй раз, иSIGINT
разоблачается. Затем основная функция возобновляет выполнение.В Linux вы можете увидеть текущий набор замаскированных и ожидающих сигналов для процесса, изучив
/proc/<PID>/status
. Маскированные сигналы показаны вSigBlk:
битовая маска и отложенные сигналы в битовой маскеSigPnd:
.Если вы устанавливаете обработчики сигналов с помощью
sigaction()
, а неsignal()
, вы можете указать флагSA_NODEFER
для запроса, чтобы сигнал не маскировался во время выполнения обработчика. Вы можете попробовать это в своей программе-с одним или обоими сигналами-и попытаться предсказать, как будет выглядеть выход.
Я нашел это в manpage
signal (7)
, который кажется уместным:В режиме реального времени сигналы передаются в процедуру оформления заказа. Множественный сигналы реального времени одного и того же типа доставляются по порядку они были посланы. Если различные сигналы в реальном времени посылаются в процесс, они поставляются начиная с самого нижнего номера сигнал. (То есть сигналы с низким номером имеют наивысший приоритет.) По контраст, если ожидаются несколько стандартных сигналов для процесса, порядок, в котором они поставляются, является неуказанным.
Просмотр документации
sigprocmask
иsigpending
, в дополнение кsignal (7)
, должен улучшить ваше понимание гарантий в отношении ожидающих сигналов.Чтобы перейти от слабой "неопределенной" гарантии к тому, что на самом деле происходит в Вашей версии OpenSUSE, вам, вероятно, потребуется проверить код доставки сигнала в ядре.