Как работают сигналы?

Как работают сигналы в unix? Я прошел через W.R. Стивенса, но не смог понять. Пожалуйста, помогите мне.

Ответы

Ответ 1

Объяснение ниже не является точным, и некоторые аспекты того, как это работает, различаются между различными системами (и, возможно, даже одной и той же ОС на разных аппаратных средствах для некоторых частей), но я думаю, что это обычно достаточно хорошо для вас, чтобы удовлетворить ваши достаточно любопытства, чтобы использовать их. Большинство людей начинают использовать сигналы в программировании, даже не понимая этого уровня понимания, но прежде, чем мне стало удобно их использовать, я хотел их понять.

доставка сигнала

Ядро ОС имеет структуру данных, называемую блоком управления процессом для каждого процесса, который имеет данные об этом процессе. Это можно проверить идентификатором процесса (PID) и включить таблицу действий сигнала и ожидающих сигналов.

Когда сигнал отправляется процессу, ядро ​​ОС будет искать этот блок управления процессом и проверяет таблицу действий сигнала, чтобы найти действие для конкретного отправляемого сигнала. Если значение действия сигнала SIG_IGN, то новый сигнал забывается ядром. Если значение действия сигнала SIG_DFL, тогда ядро ​​просматривает действие обработки сигнала по умолчанию для этого сигнала в другой таблице и предирует это действие. Если значения - это что-то еще, то это предполагается, что это адрес функции в процессе, который посылается сигналу, на который должен быть вызван. Значения для SIG_IGN и SIG_DFL - это числа, присваиваемые указателям на функции, значения которых не являются допустимыми адресами в адресном пространстве процесса (например, 0 и 1, которые находятся на странице 0, которые никогда не отображаются в процессе).

Если функция обработки сигнала была зарегистрирована процессом (значение действия сигнала не было ни SIG_IGN, ни SIG_DFL), то запись в таблице ожидающих сигналов производится для этого сигнала, и этот процесс отмечен как готовый к RUN (он может иметь ожидали чего-то, например, чтобы данные стали доступны для вызова read, ожидая сигнала или нескольких других вещей).

В следующий раз, когда процесс будет запущен, ядро ​​ОС сначала добавит некоторые данные в стек и изменяет указатель инструкции для этого процесса, чтобы он выглядел почти так же, как сам процесс только что вызвал обработчик сигнала. Это не совсем правильно и фактически достаточно отклоняется от того, что на самом деле происходит, что я расскажу об этом еще немного.

Функция обработчика сигналов может делать все, что она делает (это часть процесса, которую она вызывала от имени, поэтому она была написана с учетом того, что эта программа должна делать с этим сигналом). Когда обработчик сигнала возвращается, регулярный код для процесса начинает выполняться снова. (опять же, неточно, но больше на следующем)

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

Очень часто ядро ​​ОС должно знать, когда возвращается обработчик сигнала. Это связано с тем, что обработчики сигналов принимают аргумент (который может потребовать пространство стека), вы можете заблокировать один и тот же сигнал от доставки два раза во время выполнения обработчика сигнала и/или перезапустить системные вызовы после подачи сигнала. Для этого немного больше, чем указатель на указатель стека и инструкции.

Что должно произойти, так это то, что ядро ​​должно заставить этот процесс сказать, что он завершил выполнение функции обработчика сигнала. Это может быть сделано путем сопоставления раздела ОЗУ в адресное пространство процесса, которое содержит код для выполнения этого системного вызова и создания обратного адреса для функции обработчика сигнала (верхнее значение в стеке, когда эта функция запущена), является адресом этот код. Я думаю, что так оно и делается в Linux (по крайней мере, более новые версии). Другой способ сделать это (я не знаю, будет ли это сделано, но это может быть), будет делать обратный адрес для функции обработчика сигнала недопустимым адресом (например, NULL), что приведет к прерыванию в большинстве систем, что дало бы ядро ​​управления ОС снова. Не имеет большого значения, как это происходит, но ядро ​​должно снова получить контроль, чтобы исправить стек и знать, что обработчик сигнала завершен.

ПОСМОТРЕТЬ В ДРУГОЙ ВОПРОС Я УЗНАТЬ

что ядро ​​Linux отображает страницу в процесс для этого, но фактический системный вызов для регистрации обработчиков сигналов (какие вызовы sigaction) принимает параметр sa_restore, который является адресом, который следует использовать в качестве адреса возврата из обработчика сигнала, и ядро ​​просто убеждается, что оно помещено туда. Код по этому адресу выдает системный вызов I'm done (sigreturn), и ядро ​​знает, что обработчик сигнала закончен.

генерация сигнала

В основном я предполагаю, что вы знаете, как генерируются сигналы в первую очередь. ОС может генерировать их от имени процесса из-за чего-то, например, истечения таймера, дочернего процесса, умирающего, доступа к памяти, к которой он не должен обращаться, или выдачи инструкции, которой он не должен (либо инструкции, которая не существует или тот, который является привилегированным), или многое другое. Случай таймера функционально немного отличается от других, потому что это может произойти, когда процесс не запущен, и это больше похоже на сигналы, отправленные с системным вызовом kill. Для сигналов, не связанных с таймером, отправленных от имени текущего процесса, они генерируются, когда происходит прерывание, потому что текущий процесс делает что-то неправильно. Это прерывание дает управление ядром (как и системный вызов), и ядро ​​генерирует сигнал, который должен быть доставлен в текущий процесс.

Ответ 2

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

Как ваша программа весело перемещает свой локус выполнения, внедренный в main(), эти прерывания могут возникать, вызывая передачу программы в вектор (обработчик), запуск кода там, а затем возвращение в место, где оно было прерываться.

Эти прерывания (сигналы) могут исходить из множества источников, например. аппаратные ошибки, такие как доступ к плохим или несогласованным адресам, смерть дочернего процесса, генерируемые пользователем сигналы с использованием команды kill или из других процессов с использованием системного вызова kill. То, как вы потребляете сигналы, - это указать для них обработчики, которые отправляются ОС при появлении сигналов. Обратите внимание, что некоторые из этих сигналов не могут быть обработаны и приводят к простому умиранию процесса.

Но те, которые могут быть обработаны, могут быть весьма полезными. Вы можете использовать их для связи между процессами, то есть один процесс отправляет сигнал другому процессу, который обрабатывает его, а в обработчике делает что-то полезное. Многие демоны будут делать полезные вещи, например, перечитывать конфигурационный файл, если вы отправляете им правильный сигнал.

Ответ 3

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

Вот несколько вопросов, которые следует учитывать:

  • Что делать, если ядро ​​знает, что нужно передать сигнал процессу X, который работает на CPU_X, но ядро ​​узнает об этом во время работы на CPU_Y (CPU_X!= CPU_Y). Таким образом, ядро ​​должно остановить запуск процесса на другом ядре.
  • Что делать, если процесс запускается в пространстве ядра при получении сигнала? Каждый раз, когда процесс выполняет системный вызов, он вводит пространство ядра и возится с структурами данных и выделениями памяти в пространстве ядра. Все ли это взлом происходит и в пространстве ядра?
  • Что делать, если процесс спал в пространстве ядра, ожидая какого-то другого события? (чтение, запись, сигнал, опрос, мьютекс - это только некоторые варианты).

Ответы:

  • Если процесс запущен на другом CPU, ядро ​​через перекрестное взаимодействие с ЦП передаст прерывание другому CPU и сообщение для него. Другой процессор будет в аппаратном обеспечении сохранить состояние и перейти к ядру на другом процессоре, а затем выполнить доставку сигнала на другом CPU. Все это является частью попытки не выполнять обработчик сигнала процесса на другом процессоре, который приведет к поломке локализации кэша.
  • Если процесс запущен в ядре, он не прерывается. Вместо этого записывается, что этот процесс получил сигнал. Когда процесс выходит из пространства ядра (в конце каждого системного вызова), ядро ​​настраивает батут для выполнения обработчика сигнала.
  • Если процесс во время работы в ядре, после получения сигнала, достигает функции сна, тогда эта функция сна (и это является общей для всех функций сна в ядре) проверит, ожидает ли процесс ожидающий сигнал, Если это так, это не приведет к тому, что процесс засыпает, и вместо этого отменит все, что было сделано, когда он спустился в ядро, и выйдет из пользовательского пространства при настройке батута для выполнения обработчика сигнала, а затем перезапустите систему вызов. Фактически вы можете контролировать, какие сигналы вы хотите прервать системные вызовы и которые вы не используете системным вызовом siginterrupt(2). Вы можете решить, хотите ли вы перезапускать системные вызовы для определенного сигнала, когда вы регистрируете сигнал с помощью sigaction(2) с флагом SA_RESTART. Если системный вызов выдается и обрезается сигналом и не перезапускается автоматически, вы получите возвращаемое значение EINTR (прерванное), и вы должны обработать это значение. Вы также можете посмотреть системный вызов restart_syscall(2) для получения более подробной информации.
  • Если процесс уже спящий/ожидающий в пространстве ядра (на самом деле все спящие/ожидающие всегда находятся в пространстве ядра), он пробуждается из сна, код ядра очищается после себя и переходит к обработчику сигнала при возврате в пространство пользователя после который системный вызов автоматически перезапускается, если пользователь так желает (очень похоже на предыдущее объяснение того, что произойдет, если процесс запущен в пространстве ядра).

Несколько замечаний о том, почему все это настолько сложно:

  • Вы не можете просто остановить процесс, запущенный в пространстве ядра, так как разработчик ядра выделяет память, делает вещи для структур данных и многое другое. Если вы просто возьмете управление, вы повредите состояние ядра и повредите машину. Код ядра должен быть уведомлен контролируемым образом, что он должен остановить его выполнение, вернуться в пространство пользователя и разрешить пространство пользователя обрабатывать сигнал. Это делается с помощью возвращаемого значения всех (ну, почти всех) спящих функций в ядре. И программисты ядра, как ожидается, будут относиться к этим возвращаемым значениям с уважением и действовать соответственно.
  • Сигналы являются асинхронными. Это означает, что они должны быть доставлены как можно скорее. Представьте себе процесс, в котором есть только один поток, спал час и раздавался сигнал. Сон находится внутри ядра. Таким образом, вы, за исключением кода ядра, чтобы проснуться, очиститесь после себя, вернитесь в пространство пользователя и выполните обработчик сигнала, возможно, перезапустив системный вызов после завершения обработчика сигнала. Вы, конечно же, не ожидаете, что этот процесс обработает обработчик сигнала через час. Затем вы ожидаете, что сон возобновится. Большие проблемы предпринимает пользовательское пространство и люди ядра, чтобы это разрешить.
  • Все сигналы похожи на обработчики прерываний, но для пользовательского пространства. Это хорошая аналогия, но не идеальная. В то время как обработчики прерываний генерируются аппаратным обеспечением, некоторые обработчики сигналов берутся из аппаратного обеспечения, но большинство из них - просто программное обеспечение (сигнал о смерти дочернего процесса, сигнал от другого процесса с использованием syscall kill(2) и т.д.).

Итак, какова латентность обработки сигналов?

  • Если при получении сигнала запускается какой-то другой процесс, то это зависит от планировщика ядра, чтобы решить, разрешить ли другому процессу завершить его срез времени и только затем передать сигнал или нет. Если вы находитесь в обычной системе Linux/Unix, это означает, что вы можете задерживаться на 1 или более временных фрагментов, прежде чем вы получите сигнал (что означает миллисекунды, эквивалентные вечности).
  • Когда вы получаете сигнал, если ваш процесс является высокоприоритетным, или другие процессы уже получили свой временной срез, вы получите сигнал довольно быстро. Если вы работаете в пользовательском пространстве, вы получите его "немедленно", если вы работаете в ядре, вы скоро достигнете функции сна или вернетесь из ядра, и в этом случае, когда вы вернетесь в пространство пользователя, будет вызываться обработчик вашего сигнала. Обычно это короткое время, так как в ядре не так много времени.
  • Если вы спали в ядре, и ничто больше не находится выше вашего приоритета или его нужно запустить, поток ядра, обрабатывающий ваш системный вызов, разбуждается, очищается после всего, что он делал по пути вниз в ядро, возвращается в пространство пользователя и выполняет ваш сигнал. Это не занимает слишком много времени (говорили здесь микросекунды).
  • Если вы используете в режиме реального времени версию Linux, и ваш процесс имеет самый высокий приоритет в реальном времени, вы получите сигнал очень скоро после его запуска. Разговаривали 50 микросекунд или даже лучше (в зависимости от других факторов, в которые я не могу вдаваться).

Ответ 4

Сигнал - это не что иное, как прерывания при выполнении процесса. Процесс может сигнализировать сам или может даже вызвать сигнал к другому процессу. Может быть, родитель может отправить сигнал для своего ребенка на завершение или сам процесс может заставить сигнал остановить себя или убить и т.д.,

Проверьте следующую ссылку, это поможет вам понять.

https://unix.stackexchange.com/info/80044/how-signals-work-internally

http://www.linuxjournal.com/article/3985

http://www.linuxprogrammingblog.com/all-about-linux-signals?page=show