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

31

В общем, чтобы убить процессы, мы генерируем сигналы вроде SIGKILLи SIGTSTPт. Д.

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

Варун Чхангани
источник
Вопрос немного сложен для понимания. Я прошу прощения и не имею в виду неуважение. Вы хотите знать, кто мог запустить команду, которая убила процесс, или вы хотите узнать больше о SIGKILL и SIGSTP?
pullsumo
@mistermister Я хочу знать, кто мог запустить команду, которая убила процесс и как?
Варун Чхангани

Ответы:

35

50000 футов вид это то, что:

  1. Сигнал генерируется либо внутренним ядром (например, SIGSEGVпри обращении к недопустимому адресу, или SIGQUITкогда вы нажимаете Ctrl+ \), либо программой, использующей killсистемный вызов (или несколько связанных).

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

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

  4. Далее ядро ​​выясняет, что делать с сигналом:

    1. Для каждого процесса есть действие, связанное с каждым сигналом. Есть куча по умолчанию, и программы могут устанавливать различные те , используя sigaction, signalи т.д. Они включают в себя такие вещи , как «игнорировать его полностью», «убить процесс», «убить процесс с дампом памяти», «остановить процесс», и т.п.

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

    3. Программы могут запросить, чтобы вместо того, чтобы ядро ​​выполняло какое-то действие самостоятельно, оно доставляло сигнал процессу либо синхронно (с sigwait, и т. Д. Или signalfd), либо асинхронно (прерывая все, что делает процесс, и вызывая указанную функцию).

Существует второй набор сигналов, называемых «сигналами в реальном времени», которые не имеют конкретного значения и также позволяют ставить в очередь несколько сигналов (обычные сигналы ставятся в очередь только по одному из каждого, когда сигнал блокируется). Они используются в многопоточных программах для потоков, чтобы общаться друг с другом. Некоторые из них используются, например, в реализации потоков POSIX в glibc. Они также могут использоваться для связи между различными процессами (например, вы можете использовать несколько сигналов в реальном времени, чтобы программа fooctl отправляла сообщение демону foo).

Для просмотра не 50 000 футов попробуйте man 7 signalтакже и внутреннюю документацию ядра (или источник).

derobert
источник
«Два специальных сигнала - SIGKILL и SIGSTOP», так что же может быть SIGCONT ...
Хауке Лагинг
@HaukeLaging SIGCONT - это сигнал, который отменяет SIGSTOP. Документация не перечисляет это как особенное ... Так что я не уверен, что если технически процесс может настроить его на игнорирование, вы не сможете возобновить его (только SIGKILL).
Дероберт
22

Реализация сигнала очень сложна и специфична для ядра. Другими словами, разные ядра будут реализовывать сигналы по-разному. Упрощенное объяснение состоит в следующем:

ЦП, основанный на специальном значении регистра, имеет адрес в памяти, где он ожидает найти «таблицу дескрипторов прерываний», которая на самом деле является векторной таблицей. Существует один вектор для каждого возможного исключения, например деление на ноль, или ловушка, как INT 3 (отладка). Когда ЦП встречает исключение, он сохраняет флаги и текущий указатель команд в стеке, а затем переходит на адрес, указанный соответствующим вектором. В Linux этот вектор всегда указывает на ядро, где есть обработчик исключений. Процессор теперь готов, а ядро ​​Linux вступает во владение.

Обратите внимание, что вы также можете вызвать исключение из программного обеспечения. Например, пользователь нажимает CTRL- C, затем этот вызов переходит к ядру, которое вызывает собственный обработчик исключений. В общем, есть разные способы добраться до обработчика, но независимо от того, что происходит одна и та же базовая вещь: контекст сохраняется в стеке, а обработчик исключений ядра переходит к нему.

Затем обработчик исключений решает, какой поток должен получить сигнал. Если произошло что-то вроде деления на ноль, то это легко: поток, вызвавший исключение, получает сигнал, но для других типов сигналов решение может быть очень сложным, а в некоторых необычных случаях более или менее случайный поток может получить сигнал.

Чтобы отправить сигнал, то, что делает ядро, сначала устанавливает значение, указывающее тип сигнала SIGHUPили что-то еще. Это просто целое число. Каждый процесс имеет область памяти «ожидающий сигнал», где хранится это значение. Затем ядро ​​создает структуру данных с информацией о сигнале. Эта структура включает сигнал «расположение», который может быть по умолчанию, игнорировать или обрабатывать. Затем ядро ​​вызывает свою собственную функцию do_signal(). Следующий этап начинается.

do_signal()сначала решает, будет ли он обрабатывать сигнал. Например, если это убийство , то do_signal()просто убивает процесс, конец истории. В противном случае он смотрит на диспозицию. Если расположение по умолчанию, то do_signal()обрабатывает сигнал в соответствии с политикой по умолчанию, которая зависит от сигнала. Если расположение является дескриптором, то это означает, что в пользовательской программе есть функция, предназначенная для обработки рассматриваемого сигнала, и указатель на эту функцию будет находиться в вышеупомянутой структуре данных. В этом случае do_signal () вызывает другую функцию ядра,handle_signal(), который затем проходит процесс переключения обратно в режим пользователя и вызова этой функции. Детали этой передачи чрезвычайно сложны. Этот код в вашей программе обычно автоматически связывается с вашей программой, когда вы используете функции в signal.h.

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

Тайлер Дурден
источник
15

Хотя на этот вопрос дан ответ, позвольте мне опубликовать подробный поток событий в ядре Linux.
Это полностью скопировано из постов Linux: Linux Signals - Internals в блоге «Посты Linux» на sklinuxblog.blogspot.in.

Программа "Пространство пользователя сигнала C"

Давайте начнем с написания простой программы для пользователя на языке C:

#include<signal.h>
#include<stdio.h>

/* Handler function */
void handler(int sig) {
    printf("Receive signal: %u\n", sig);
};

int main(void) {
    struct sigaction sig_a;

    /* Initialize the signal handler structure */
    sig_a.sa_handler = handler;
    sigemptyset(&sig_a.sa_mask);
    sig_a.sa_flags = 0;

    /* Assign a new handler function to the SIGINT signal */
    sigaction(SIGINT, &sig_a, NULL);

    /* Block and wait until a signal arrives */
    while (1) {
            sigsuspend(&sig_a.sa_mask);
            printf("loop\n");
    }
    return 0;
};

Этот код назначает новый обработчик для сигнала SIGINT. SIGINT может быть отправлен в рабочий процесс с помощью комбинации клавиш Ctrl+ C. При нажатии Ctrl+ Cна задание отправляется асинхронный сигнал SIGINT. Это также эквивалентно отправке kill -INT <pid>команды в другой терминал.

Если вы сделаете kill -l(это строчная буква L, что означает «список»), вы узнаете различные сигналы, которые могут быть отправлены в работающий процесс.

[root@linux ~]# kill -l
 1) SIGHUP        2) SIGINT        3) SIGQUIT       4) SIGILL        5) SIGTRAP
 6) SIGABRT       7) SIGBUS        8) SIGFPE        9) SIGKILL      10) SIGUSR1
11) SIGSEGV      12) SIGUSR2      13) SIGPIPE      14) SIGALRM      15) SIGTERM
16) SIGSTKFLT    17) SIGCHLD      18) SIGCONT      19) SIGSTOP      20) SIGTSTP
21) SIGTTIN      22) SIGTTOU      23) SIGURG       24) SIGXCPU      25) SIGXFSZ
26) SIGVTALRM    27) SIGPROF      28) SIGWINCH     29) SIGIO        30) SIGPWR
31) SIGSYS       34) SIGRTMIN     35) SIGRTMIN+1   36) SIGRTMIN+2   37) SIGRTMIN+3
38) SIGRTMIN+4   39) SIGRTMIN+5   40) SIGRTMIN+6   41) SIGRTMIN+7   42) SIGRTMIN+8
43) SIGRTMIN+9   44) SIGRTMIN+10  45) SIGRTMIN+11  46) SIGRTMIN+12  47) SIGRTMIN+13
48) SIGRTMIN+14  49) SIGRTMIN+15  50) SIGRTMAX-14  51) SIGRTMAX-13  52) SIGRTMAX-12
53) SIGRTMAX-11  54) SIGRTMAX-10  55) SIGRTMAX-9   56) SIGRTMAX-8   57) SIGRTMAX-7
58) SIGRTMAX-6   59) SIGRTMAX-5   60) SIGRTMAX-4   61) SIGRTMAX-3   62) SIGRTMAX-2
63) SIGRTMAX-1   64) SIGRTMAX

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

  • Ctrl+ C- отправляет SIGINT действие по умолчанию для завершения приложения.
  • Ctrl+ \  - отправляет SIGQUIT, какое действие по умолчанию завершает работу ядра дампа приложения.
  • Ctrl+ Z- отправляет SIGSTOP, который приостанавливает работу программы.

Если вы скомпилируете и запустите вышеуказанную программу на C, вы получите следующий вывод:

[root@linux signal]# ./a.out
Receive signal: 2
loop
Receive signal: 2
loop
^CReceive signal: 2
loop

Даже с Ctrl+ Cили kill -2 <pid>процесс не закончится. Вместо этого он выполнит обработчик сигнала и вернется.

Как сигнал отправляется в процесс

Если мы увидим внутреннюю часть сигнала, отправляемого процессу, и поместим Jprobe с dump_stack в __send_signalфункцию, то увидим следующую трассировку вызова:

May  5 16:18:37 linux kernel: dump_stack+0x19/0x1b
May  5 16:18:37 linux kernel: my_handler+0x29/0x30 (probe)
May  5 16:18:37 linux kernel: complete_signal+0x205/0x250
May  5 16:18:37 linux kernel: __send_signal+0x194/0x4b0
May  5 16:18:37 linux kernel: send_signal+0x3e/0x80
May  5 16:18:37 linux kernel: do_send_sig_info+0x52/0xa0
May  5 16:18:37 linux kernel: group_send_sig_info+0x46/0x50
May  5 16:18:37 linux kernel: __kill_pgrp_info+0x4d/0x80
May  5 16:18:37 linux kernel: kill_pgrp+0x35/0x50
May  5 16:18:37 linux kernel: n_tty_receive_char+0x42b/0xe30
May  5 16:18:37 linux kernel:  ? ftrace_ops_list_func+0x106/0x120
May  5 16:18:37 linux kernel: n_tty_receive_buf+0x1ac/0x470
May  5 16:18:37 linux kernel: flush_to_ldisc+0x109/0x160
May  5 16:18:37 linux kernel: process_one_work+0x17b/0x460
May  5 16:18:37 linux kernel: worker_thread+0x11b/0x400
May  5 16:18:37 linux kernel: rescuer_thread+0x400/0x400
May  5 16:18:37 linux kernel:  kthread+0xcf/0xe0
May  5 16:18:37 linux kernel:  kthread_create_on_node+0x140/0x140
May  5 16:18:37 linux kernel:  ret_from_fork+0x7c/0xb0
May  5 16:18:37 linux kernel: ? kthread_create_on_node+0x140/0x140

Таким образом, основные вызовы функций для отправки сигнала выглядят так:

First shell send the Ctrl+C signal using n_tty_receive_char
n_tty_receive_char()
isig()
kill_pgrp()
__kill_pgrp_info()
group_send_sig_info() -- for each PID in group call this function
do_send_sig_info()
send_signal()
__send_signal() -- allocates a signal structure and add to task pending signals
complete_signal()
signal_wake_up()
signal_wake_up_state()  -- sets TIF_SIGPENDING in the task_struct flags. Then it wake up the thread to which signal was delivered.

Теперь все настроено и необходимые изменения внесены task_structв процесс.

Обработка сигнала

Сигнал проверяется / обрабатывается процессом, когда он возвращается из системного вызова или если выполняется возврат из прерывания. Возврат из системного вызова присутствует в файле entry_64.S.

Функция int_signal вызывается функцией, из entry_64.Sкоторой вызывается функция do_notify_resume().

Давайте проверим функцию do_notify_resume(). Эта функция проверяет, установлен ли у нас TIF_SIGPENDINGфлаг в task_struct:

 /* deal with pending signal delivery */
 if (thread_info_flags & _TIF_SIGPENDING)
  do_signal(regs);
do_signal calls handle_signal to call the signal specific handler
Signals are actually run in user mode in function:
__setup_rt_frame -- this sets up the instruction pointer to handler: regs->ip = (unsigned long) ksig->ka.sa.sa_handler;

СИСТЕМНЫЕ звонки и сигналы

«Медленные» системные вызовы, например, блокировка чтения / записи, перевод процессов в состояние ожидания: TASK_INTERRUPTIBLEили TASK_UNINTERRUPTIBLE.

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

В случае выполнения его обработчик сигнала будет запущен до завершения «медленного» системного вызова. По syscallумолчанию не завершается.

Если SA_RESTARTфлаг установлен, syscallперезапускается после завершения обработчика сигнала.

Ссылки

K_K
источник
Спасибо за то, что вы приложили усилия, чтобы внести свой вклад в сайт, но (1) если вы собираетесь копировать материал с другого сайта (слово в слово, буква в букву, включая грамматические ошибки и ошибки пунктуации), вы должны сказать, что вы делаете так, намного понятнее. Перечисление источника в качестве «ссылки», хотя и необходимо, но недостаточно. Если вы не являетесь автором блога (K_K = sk?), В этом случае вам не нужно указывать ссылку на него, но, если вы это сделаете, вы должны сообщить (то есть сказать), что он ваш. … (Продолжение)
G-Man говорит «Восстановить Монику»
(Продолжение)… (2) Ваш источник (блог, с которого вы скопировали) не очень хорош. Прошло четыре года с тех пор, как вопрос был задан; не могли бы вы найти лучшую ссылку для копирования? (Если вы оригинальный автор, извините.) В дополнение к вышеупомянутым ошибкам грамматики и пунктуации (и вообще небрежной формулировке и плохому форматированию), это неправильно. (2a) Ctrl + Z отправляет SIGTSTP, а не SIGSTOP. (SIGTSTP, как и SIGTERM, может быть пойман; SIGSTOP, как и SIGKILL, не может.)… (Продолжение)
G-Man говорит: «Восстановить Монику»
(Продолжение)… (2b) Оболочка не отправляет сигнал Ctrl + C. Оболочка не играет роли в отправке сигналов (кроме случаев, когда пользователь использует killкоманду, которая является встроенной в оболочку). (2c) Хотя точки с запятой после закрытия }функции, строго говоря, не являются ошибками, они не нужны и крайне неортодоксальны. (3) Даже если бы все было правильно, это не было бы очень хорошим ответом на вопрос. (3a) Вопрос, хотя и несколько неясный, по-видимому, сосредоточен на том, как субъекты (пользователи и процессы) инициируют (т.е. отправляют ) сигналы. … (Продолжение)
G-Man говорит «Восстановить Монику»
(Продолжение)… Ответ, кажется, сосредоточен на сигналах, генерируемых ядром (в частности, на сигналах, генерируемых клавиатурой) и на том, как процесс получателя реагирует на сигналы. (3b) Вопрос, кажется, находится на уровне «Кто-то убил мой процесс - кто это сделал и как?» В ответе рассматриваются API обработки сигналов, процедуры ядра, отладка ядра (Jprobe?), Трассировки стека ядра и структуры данных ядра. IMO, это неоправданно низкий уровень, особенно потому, что он не дает никаких ссылок, где читатель мог бы больше узнать об этих внутренних принципах работы.
G-Man говорит «Восстановить Монику»
1
Это мой собственный блог ... мои собственные следы ... это то, что я хочу ... каждый должен знать такой подробный поток ... разговоры в эфире не имеют смысла ... даже если после того, как это нарушит правила сообщества, пожалуйста, удалите мой ответ через надлежащий канал .. это внутренний ответ ядра, а не внутренние грамматики.
K_K