Как 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");
}

Итак, при запуске этого кода я ожидал чего-то вроде это:

  1. запуск кода.... ^C
  2. введите inthandler, выполнение цикла, нажмите ^
  3. выход из inthandler, переход в quithandler, выполнение цикла quithandler
  4. ^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
Другими словами, он, по-видимому, обрабатывается следующим образом:
  1. Прием первого сигнала ^C
  2. принять сигнал^, "задержать" inthandler и перейти в quithandler
  3. получите следующий сигнал ^C, но поскольку мы уже "вложены" в inthandler, поместите его в конец "очереди" inthandler
  4. Прием quithandler, место в конце очереди quithandler.
  5. выполняйте quithandler, пока очередь не опустеет. Игнорируйте третий quithandler, потому что он, кажется, имеет только глубину очереди 2.
  6. оставьте quithandler и выполните 2 оставшихся inthandlers. Игнорируйте последний inthandler, потому что queue-глубина 2.

Я показал поведение моему профессору, и он, кажется, согласен, что" вложенная глубина очереди 2 " - это то, что происходит, но мы не уверены на 100%, почему (он исходит из аппаратного обеспечения фон и только начал преподавать этот класс). Я хотел написать в SO, чтобы посмотреть, может ли кто-нибудь пролить свет на то, почему/как Linux обрабатывает эти сигналы, поскольку мы не совсем ожидали некоторого поведения, т. е. вложенности.

Я думаю, что тестового случая, который я написал, должно быть достаточно, чтобы проиллюстрировать, что происходит, но вот куча скриншотов дополнительных тестовых случаев:

Http://imgur.com/Vya7JeY,fjfmrjd, 30YRQfk, uHHXFu5, Pj35NbF

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

Спасибо!

2 5

2 ответа:

Правила (для сигналов не реального времени, таких как SIGQUIT и SIGINT, которые вы используете):

  1. по умолчанию, сигнал маскируется, когда вошла в свой обработчик, и разоблачен, когда обработчик завершает работу;
  2. Если сигнал поднят во время маскировки, он остается ожидающим и будет доставлен, если/когда этот сигнал будет снят.

Состояниеожидания является двоичным - сигнал либо находится в состоянии ожидания, либо не находится в состоянии ожидания. Если сигнал поднимается несколько раз, пока замаскированный, он все равно будет доставлен только один раз, когда разоблачен.

Итак, что происходит в вашем примере:

  1. SIGINT вызывается, и обработчик сигнала inthandler() начинает выполняться с маскировкой SIGINT.
  2. SIGQUIT вызывается, и обработчик сигнала quithandler() начинает выполнение (прерывание inthandler()) с маскировкой SIGQUIT.
  3. SIGINT поднимается, добавляя SIGINT к набору ожидающих сигналов (поскольку он замаскирован).
  4. SIGQUIT поднимается, добавляя SIGQUIT к набору отложенных сигналов (потому что он замаскирован).
  5. SIGQUIT поднимается, но ничего не происходит, потому что SIGQUIT уже находится в ожидании.
  6. SIGINT поднимается, но ничего не происходит, потому что SIGINT уже находится в ожидании.
  7. quithandler() завершает выполнение, и SIGQUIT снимается маска. Поскольку SIGQUIT находится в ожидании, он затем доставляется, и quithandler() снова начинает выполняться (с SIGQUIT снова замаскированным).
  8. quithandler() завершает выполнение во второй раз, и SIGQUIT снимается маска. SIGQUIT не находится в ожидании, поэтому inthandler() затем возобновляется выполнение (с SIGINT все еще в маске).
  9. inthandler() завершает выполнение, и SIGINT разоблачается. Поскольку SIGINT находится в ожидании, он затем доставляется, и inthandler() снова начинает выполняться (с SIGINT снова замаскированным).
  10. inthandler() завершает выполнение во второй раз, и SIGINT разоблачается. Затем основная функция возобновляет выполнение.

В Linux вы можете увидеть текущий набор замаскированных и ожидающих сигналов для процесса, изучив /proc/<PID>/status. Маскированные сигналы показаны в SigBlk: битовая маска и отложенные сигналы в битовой маске SigPnd:.

Если вы устанавливаете обработчики сигналов с помощью sigaction(), а не signal(), вы можете указать флаг SA_NODEFER для запроса, чтобы сигнал не маскировался во время выполнения обработчика. Вы можете попробовать это в своей программе-с одним или обоими сигналами-и попытаться предсказать, как будет выглядеть выход.

Я нашел это в manpage signal (7), который кажется уместным:

В режиме реального времени сигналы передаются в процедуру оформления заказа. Множественный сигналы реального времени одного и того же типа доставляются по порядку они были посланы. Если различные сигналы в реальном времени посылаются в процесс, они поставляются начиная с самого нижнего номера сигнал. (То есть сигналы с низким номером имеют наивысший приоритет.) По контраст, если ожидаются несколько стандартных сигналов для процесса, порядок, в котором они поставляются, является неуказанным.

Просмотр документации sigprocmask и sigpending, в дополнение к signal (7), должен улучшить ваше понимание гарантий в отношении ожидающих сигналов.

Чтобы перейти от слабой "неопределенной" гарантии к тому, что на самом деле происходит в Вашей версии OpenSUSE, вам, вероятно, потребуется проверить код доставки сигнала в ядре.