Приведенное ниже объяснение не является точным, и некоторые аспекты того, как это работает, различаются в разных системах (и, возможно, даже в одной и той же ОС на разных аппаратных средствах для некоторых частей), но я думаю, что обычно это хорошо достаточно , чтобы вы смогли удовлетворить свое любопытство и использовать их.Большинство людей начинают использовать сигналы в программировании даже без такого уровня понимания, но прежде чем я освоился с ними, я хотел их понять.
доставка сигналов
Ядро ОС имеет структуру данных, называемуюблок управления процессом для каждого запущенного процесса, который имеет данные об этом процессе.Это можно найти по идентификатору процесса (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
. Для сигналов, не связанных с таймером, отправляемых от имени текущего процесса, они генерируются, когда происходит прерывание, потому что текущий процесс делает что-то не так. Это прерывание обеспечивает управление ядром (так же, как системный вызов), а ядро генерирует сигнал для доставки текущему процессу.