Как асинхронные обработчики сигналов выполняются в Linux? - PullRequest
51 голосов
/ 05 августа 2011

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

По первому вопросу я прочитал два разных, казалось бы, противоречивых объяснения:

  1. Ядро Linux, Андриес Брауэр, §5.2 «Прием сигналов», состояния :

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

  2. Вопрос StackOverflow «Работа с асинхронными сигналами в многопоточной программе» заставляет меня думать, что поведение Linux похоже на поведение Unix SCO * *

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

    1. Поток заблокирован в системном вызове sigwait (2) , аргумент которого содержит , включая тип перехваченного сигнала.

    2. Поток, маска сигнала которого не включает тип перехваченного сигнала.

    Дополнительные соображения:

    • Поток, заблокированный в sigwait (2) получает преимущество перед потоком, не блокирующим тип сигнала.
    • Если более чем один поток удовлетворяет этим требованиям (возможно, два потока вызывают sigwait (2) ), то будет выбран один из них. Этот выбор не предсказуем прикладными программами.
    • Если ни один поток не подходит, сигнал останется "ожидающим" на уровне процесса, пока какой-то поток не станет подходящим.

    Кроме того, «Модель обработки сигналов Linux» Моше Бара гласит: «Асинхронные сигналы доставляются первому найденному потоку, который не блокирует сигнал», что, как я понимаю, означает, что сигнал доставляется какой-то поток, имеющий сигма , а не , включая сигнал.

Какой из них правильный?

Что касается второго вопроса, что происходит со стеком и регистрирует содержимое для выбранного потока? Предположим, что обработчик потока для обработки сигнала T находится в середине выполнения функции do_stuff(). Используется ли стек потока T непосредственно для выполнения обработчика сигнала (т. Е. Адрес батута сигнала помещается в стек T , а поток управления поступает в обработчик сигнала)? В качестве альтернативы используется отдельный стек? Как это работает?

Ответы [ 2 ]

24 голосов
/ 05 августа 2011

Эти два объяснения на самом деле не противоречат друг другу, если принять во внимание тот факт, что хакеры Linux, как правило, путают разницу между потоком и процессом, главным образом из-за исторической ошибки при попытке притворяться, что потоки могут быть реализованы какпроцессы, которые разделяют память.: -)

С учетом вышесказанного, объяснение # 2 гораздо более подробное, полное и правильное.

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

3 голосов
/ 11 января 2018

Источник № 1 (Андриес Брауэр) подходит для однопоточного процесса. Источник № 2 (SCO Unix) не подходит для Linux, потому что Linux не предпочитает потоки в sigwait (2). Моше Бар прав насчет первой доступной темы.

Какая нить получает сигнал? Справочные страницы Linux являются хорошим справочным материалом. Процесс использует clone (2) с CLONE_THREAD для создания нескольких потоков. Эти потоки принадлежат «группе потоков» и имеют общий идентификатор процесса. В руководстве клону (2) написано:

Сигналы могут быть отправлены группе потоков в целом (т.е. TGID), используя kill (2) , или в конкретный поток (т. Е. TID), используя tgkill (2) . * * 1014

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

Каждый поток имеет свою собственную маску сигналов, установленную sigprocmask (2) , но сигналы могут быть ожидающими либо: для всего процесса (т.е. доставляется любому члену группы потоков), когда отправлено с kill (2); или для отдельной темы, когда отправлено с tgkill (2). Вызов sigpending (2) возвращает набор сигналов, который является объединение сигналов, ожидающих для всего процесса и сигналы, ожидающие для вызывающего потока.

Если kill (2) используется для отправки сигнала группе потоков, а группа потоков установила обработчик для сигнала, затем обработчик будет вызываться ровно в одном, произвольно выбранном член группы потоков, которая не заблокировала сигнал. Если несколько потоков в группе ожидают принятия одного и того же сигнал с помощью sigwaitinfo (2) , ядро ​​будет произвольно выберите один из этих потоков, чтобы получить сигнал, отправленный с помощью убить (2).

Linux - это не SCO Unix, потому что Linux может дать сигнал любому потоку, даже если некоторые потоки ожидают сигнала (с sigwaitinfo, sigtimedwait или sigwait), а некоторые потоки - нет. Руководство по sigwaitinfo (2) Предупреждения,

При нормальном использовании вызывающая программа блокирует сигналы в наборе через предыдущий вызов sigprocmask (2) (так что расположение по умолчанию для эти сигналы не происходят, если они становятся ожидающими между последовательные вызовы sigwaitinfo () или sigtimedwait ()) и не установить обработчики для этих сигналов. В многопоточной программе сигнал должен быть заблокирован во всех потоках, чтобы предотвратить сигнал обрабатывается в соответствии с его расположением по умолчанию в потоке кроме того, который вызывает sigwaitinfo () или sigtimedwait ()).

Код для выбора потока для сигнала находится в linux / kernel / signal.c (ссылка указывает на зеркало GitHub). Смотрите функции want_signal () и complete_signal (). Код выбирает первый доступный поток для сигнала. Доступен поток, который не блокирует сигнал и не имеет других сигналов в своей очереди. Код сначала проверяет основной поток, затем проверяет другие потоки в неизвестном мне порядке. Если поток недоступен, то сигнал застревает до тех пор, пока какой-либо поток не разблокирует сигнал или не освободит свою очередь.

Что происходит, когда поток получает сигнал? Если есть обработчик сигнала, то ядро ​​заставляет поток вызывать обработчик. Большинство обработчиков работают в стеке потока. Обработчик может работать в альтернативном стеке, если процесс использует sigaltstack (2) для предоставления стека и sigaction (2) с SA_ONSTACK для установки обработчика. Ядро помещает некоторые вещи в выбранный стек и устанавливает некоторые регистры потока.

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

Обработчик сигнала является функцией C, поэтому ядро ​​подчиняется соглашению архитектуры для вызова функций C,Каждая архитектура, такая как arm, i386, powerpc или sparc, имеет свое соглашение.Для powerpc, чтобы вызвать обработчик (signum), ядро ​​устанавливает регистр r3 в signum.Ядро также устанавливает адрес возврата обработчика для батута сигнала.Адрес возврата помещается в стек или в регистр по соглашению.

Ядро помещает один батут сигнала в каждый процесс.Этот батут вызывает sigreturn (2) , чтобы восстановить нить.В ядре sigreturn (2) считывает некоторую информацию (например, сохраненные регистры) из стека.Ядро поместило эту информацию в стек перед вызовом обработчика.Если произошел прерванный системный вызов, ядро ​​может перезапустить вызов (только если обработчик использовал SA_RESTART), или прервать вызов с помощью EINTR, или вернуть короткое чтение или запись.

...