Ядро Linux в комментариях

Do_signal


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

Если oldset не равен NULL, он используется для возврата набора заблокированных сигналов в текущем процессе. Поскольку do_signal набор заблокированных сигналов не изменяет, можно просто возвратить указатель на существующий набор.

Вход в цикл, который тянется практически до конца функции (строка ). Существует всего два пути выхода из цикла: когда все сигналы, требующие обработки, подошли к концу, или за счет обработки одного сигнала.

Удаление сигнала из очереди при помощи dequeue_signal (строка ). Функция dequeue_signal возвращает либо 0, либо число сигналов для обработки, а также заполняет всю дополнительную информацию в info.

Если больше нет ожидающих сигналов, цикл завершается. Обычно это происходит не после первой итерации.

Если текущий процесс трассируется родителем (скажем, отладчиком) и этот сигнал — не SIGKILL, который блокировке не подлежит, родитель процесса должен быть уведомлен о доставке сигнала.

Номер сигнала, доставленного для дочернего процесса, передается родителю в поле exit_code дочернего процесса; родитель будет собирать их с использованием sys_wait4 (строка , которая рассматривается в ). do_signal останавливает дочерний процесс, обращается к notify_parent (строка ) для отправки родителю сигнала SIGCHLD и затем вызывает функцию планировщика schedule (строка , см. ), чтобы предоставить остальным процессам (в частности, родителю) возможность продолжить выполнение. Функция schedule будет уступать ЦП другому процессу, поэтому возврата из нее не будет до тех пор, пока ядро не выполнит переключение вновь на этот процесс.

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

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


Если отладчик изменил номер сигнала, который должен обрабатываться do_signal, do_signal заполняет info в соответствие с новой информацией.

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

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

В том случае когда процесс пытается проигнорировать сигнал, do_signal продолжает цикл, если только это не сигнал SIGCHLD. Почему здесь не проверяется также, не пытается ли процесс проигнорировать SIGKILL, который по определению нельзя ни игнорировать, ни блокировать? Ответ заключается в том, что действие, соответствующее SIGKILL, никогда не может быть SIG_IGN, и тем более не SIG_DFL — это гарантируется в строке (в функции do_sigaction). Таким образом, если действием является SIG_IGN, номером сигнала не может быть SIGKILL.

Как гласит комментарий в строке , стандарт POSIX определяет, что действие «проигнорировать» для SIGCHLD означает автоматически отсечь дочерний процесс. Дочерний процесс отсекается при помощи sys_wait4 (строка ) и цикл продолжается.

Процесс выполняет для данного сигнала стандартное действие. Стандартные действия для всех сигналов, полученные от специального процесса init, сводятся к полному игнорированию сигнала.

Стандартные действия для сигналов SIGCONT, SIGCHLD и SIGWINCH — это не делать ничего, следовательно, просто продолжить цикл.

Для сигналов SIGTSTP, SIGTTIN и SIGTTOU стандартные действия изменяются. В том случае когда группа, к которой принадлежит данный процесс, оказывается висячей (ни с чем не связана) — это может означать, что она не присоединена к TTY — POSIX определяет, что стандартным действием для этих терминальных сигналов должно быть игнорирование. Если же группа процессов не является висячей, то стандартной реакцией на сигналы должен быть останов процесса — то же самое, что и случай с SIGSTOP, поэтому именно туда передается управление.



В ответ на SIGSTOP (либо в результате передачи управления из предыдущего случая) do_signal останавливает процесс. Кроме того, выполняется уведомление родительского процесса об останове одного из его дочерних процессов, если только родительский процесс не отменил необходимость такого уведомления. Как и в строке , выполняется вызов schedule для передачи ЦП другим процессам. После того как ядро вновь вернет ЦП текущему процессу, цикл продолжит свою работу и займется вытаскиванием из очереди следующего сигнала.

Это несколько неожиданно — я думал, что когда произойдет возврат из schedule, цикл должен завершиться, поскольку сигнал обработан. Кажется, рациональнее было бы пробуждать остановленный процесс только по приходу сигнала, например SIGCONT, чтобы можно было также и проверить и обработать сигнал.

Стандартная реакция на другие сигналы сводится к завершению процесса. Некоторые сигналы сначала порождают процесс, чтобы попытаться записать дамп ядра (см. ) — это SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGFPE и SIGSEGV. Если данный двоичный формат (см. ) знает, как записать дамп и дамп завершается успешно, в коде завершения процесса устанавливается соответствующий разряд, указывающий, что перед завершением процесс успел записать дамп. Далее управление передается на общий случай, который и завершает процесс. Из функции do_exit (строка , см. ) никогда не происходит возврата — отсюда и комментарий «NOTREACHED» в строке .

В этот момент do_signal изъяла из очереди сигнал, который не ассоциируется ни с действием SIG_IGN, ни с действием SIG_DFL. Единственное, что можно предположить, что сигнал имеет пользовательский обработчик. do_signal обращается к handle_signal (строка ), чтобы вызвать пользовательский обработчик сигнала, и возвращает 1, уведомляя отправителя о факте обработки сигнала.

Здесь do_signal не может получить из очереди сигнал для текущего процесса. (В эту строку можно попасть только после break в строке .) Если процесс прерван из середины системного вызова, do_signal настраивает таким образом, что системный вызов сможет возобновить выполнение.

Возврат 0, информирующего отправителя о том, что do_signal не обработала ни одного сигнала.


Содержание раздела