Получение SIGINT и дескрипторов исключений в Linux - PullRequest
0 голосов
/ 05 декабря 2018

Допустим, у нас есть программа на C, которая использует функцию sleep ()

Программа выполняется и переходит в спящий режим.Затем мы набираем ctrl-c для отправки сигнала SIGINT процессу.

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

И в моем учебнике сказано, чтобы разрешить возврат функции sleep (), мы должны установить обработчик SIGINT следующим образом:

void handler(int sig){
    return; /* Catch the signal and return */
}
...
int main(int argc, char **argv) {
   ...
   if (signal(SIGINT, handler) == SIG_ERR) /* Install SIGINT handler */
      unix_error("signal error\n");
   ...
   sleep(1000)
}

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

Справочная информация: Когда процесс спит, и мы нажимаем ctrl-c, чтобы отправить SIGINT

Q1-Я понимаю,Ядро отправляет SIGINT процессу, обновляя соответствующий ожидающий бит SIGINT в векторе битов ожидания, верно ли мое понимание?

Q2-Процессор обнаруживает существование SIGINT, но, поскольку мы перезаписываем обработчик, чтобы он возвращалсявместо того, чтобы завершить процесс, чтобы наш обработчик выполнялся, а затем ядро ​​очищает ожидающий ответ SIGINT bверно, мое понимание верно?

Q3- Так как очищенный ожидающий бит SIGINT очищен, то как функция sleep () может получить возврат?Я думаю, что он должен быть в состоянии покоя, потому что теоретически функция sleep () не может узнать о существовании SIGINT (было очищено)

Ответы [ 3 ]

0 голосов
/ 05 декабря 2018

Q3- Поскольку очищенный ожидающий бит SIGINT очищен, то как функция sleep () может получить возврат?

Представьте себе функцию sleep() в ядре как функцию, которая:

  • выделяет и устанавливает поля в некоторой структуре «события таймера»
  • добавляет «событие таймера» в список событий таймера, чтобы обработчик IRQ таймера мог беспокоиться позже (когдаистекло время истечения)
  • переводит задачу из состояния «РАБОТА» в состояние «ВЫКЛЮЧЕНИЕ» (поэтому планировщик знает, что не нужно выделять время ЦП задачи), в результате чего планировщик переключается на некоторые задачидругая задача
  • настраивает параметры возврата для пользовательского пространства (оставшееся время или 0, если время истекло)
  • выясняет, почему планировщик снова дал ему время ЦП (сделал времяистек или был сон прерван сигналом?)
  • потенциально может исказить стек (так что ядро ​​возвращается к обработчику сигнала, если sleep() был прерван сигналомвместо возврата к коду, который вызывал sleep())
  • возвращается в пространство пользователя

Также представьте, что есть вторая функция (которую я собираюсь вызвать wake()без особой причины) что:

  • удаляет «событие таймера» из списка событий таймера (для беспокойства обработчика IRQ таймера)
  • перемещает задачу из «SLEEPING»"состояние в состояние" ГОТОВ К РАБОТЕ "(поэтому планировщик знает, что задаче может быть снова предоставлено время ЦП)

Естественно, если обработчик IRQ таймера замечает, что" событие таймера "истеклотогда обработчик IRQ таймера вызовет функцию wake(), чтобы снова вызвать задачу.

Теперь представьте, что есть третья функция (которую я собираюсь вызвать send_signal()), которая может быть вызвана другими функциями(например, вызывается kill()).Эта функция может установить флаг «ожидающий сигнал» для задачи, которая должна получить сигнал, а затем проверить, в каком состоянии находится принимающая задача;и если принимающая задача находится в состоянии «SLEEPING», она вызывает функцию wake(), чтобы разбудить ее (а затем позволяет последней части функции sleep() беспокоиться о доставке сигнала обратно в пространство пользователя всякий раз, когда планировщик чувствуеткак, например, задание времени процессора позже).

0 голосов
/ 07 декабря 2018
  • Q1: ядро ​​проверяет, заблокировал ли процесс принятый сигнал, если это так, оно обновляет бит ожидающего сигнала (ненадежно, в системах с реляционными сигналами это должно бытьсчетчик) в записи процесса для вызова обработчика сигнала при повторной разблокировке сигналов (см. ниже).Если не заблокирован, системный вызов подготавливает возвращаемое значение и значение errno и возвращается в пользовательский режим со специальным кодом, установленным в виртуальном стеке программы, который позволяет ему вызывать обработчик сигналов (уже в пользовательском режиме) перед возвратом из универсальногоsyscall код.Возврат из системного вызова дает -1 коду вызывающего абонента, а переменная errno установлена ​​в EINTR.Это требует, чтобы процесс установил обработчик сигнала, потому что по умолчанию действие должно прервать процесс, поэтому он не вернется из системного вызова, который ожидает.Подумайте, что когда кто-то говорит ядро ​​, фактический исполняемый код находится в пробуждении системного вызова и уведомляется об особом условии (принятом сигнале) Прерванный вызов обнаруживает, что должен быть вызван обработчик сигнала, иподготавливает пользовательский стек для перехода в нужное место (обработчик прерываний в коде пользователя) перед возвратом из оболочки syscall().

  • Q2: ожидающий бит равен only используется для сохранения того, что должен быть вызван ожидающий обработчик сигнала, так что это не тот случай.В части выполнения процесса загрузчик unix-программы устанавливает некоторый базовый код для перехода к обработчику сигналов перед возвратом из системного вызова.Это связано с тем, что обработчик сигнала должен выполняться в пользовательском режиме (не в режиме ядра), поэтому все происходит после завершения системного вызова.Выполнен обработчик сигнала SIGINT, но прерванный код является системным вызовом, и ничего не происходит до тех пор, пока системный вызов не вернется (с фиксированным кодом возврата и переменной errno)

  • Q3: хорошо, ваши рассуждения основывались на неверной предпосылке, то есть флаг ожидания прерывания указывает, что прерывание было получено .Этот бит только сигнализирует о том, что необработанное прерывание было помечено для доставки , как только вы разблокируете его , и это происходит только при другом системном вызове (чтобы разблокировать сигнал).Как только сигнал разблокирован, код возврата системного вызова sigsetmask(2) выполнит обработчик сигнала.В этом случае сигнал будет доставлен процессу, как только истечет таймер, системный вызов будет прерван и, если вы не установили обработчик сигнала для сигнала SIGALRM (но реализация sleep(2) делает это -- по крайней мере, старые реализации сделали) программа будет прервана.

NOTE

Когда я говорю, что программа прерывается ядром, но в обоих случаях,задействованные сигналы (SIGINT и SIGALRM) не позволяют сбросить файл ядра.Программа прерывается без генерации core. Это отличается от поведения подпрограммы abort(), которая отправляет SIGABRT и, таким образом, заставляет ядро ​​выгружать файл ядра процесса.

0 голосов
/ 05 декабря 2018

Ваше понимание верно.

Подумайте об этом.Процесс заблокирован в ядре.Нам нужно вернуться в пользовательское пространство, чтобы запустить обработчик.Как мы можем сделать это, не прерывая работающий блокирующий вызов ядра?У нас только один контекст процесса / потока для работы здесь.Процесс не может быть одновременно и спящим, и запущенным обработчиком сигнала.

Последовательность:

  1. Блоки процесса в некоторых блокирующих вызовах ядра.
  2. Сигнал отправлен
  3. Бит установлен, процесс подготовлен к запуску.
  4. Процесс возобновляет работу в режиме ядра, проверяет наличие ожидающих неблокированных сигналов.
  5. Сигналдиспетчер вызывается.
  6. Контекст процесса изменяется для выполнения обработчика сигнала при возобновлении.
  7. Процесс возобновляется в пользовательском пространстве
  8. Запуск обработчика сигнала.
  9. Сигналвозвращается обработчик.
  10. Ядро вызывается по окончании обработчика сигнала.
  11. Ядро принимает решение, возобновить ли системный вызов или вернуть ошибку прерывания.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...