Intereting Posts
Проблема с systemd & ksoftirqd Не все типы разделов fdisk отображаются? Однострочный шрифт для вставки новой строки текста (буквально регулярное выражение, а значит, и многих экранированных символов) в файле конфигурации перед определенной строкой? Ssh на несколько серверов и объединить файлы в один Как заменить текст между двумя маркерами в файле секцией текста из другого файла? kerberos preauth pkinit не удалось инициализировать: не было настроено правильно для поддержки pkinit Ubuntu 14.04 встроенная веб-камера не обнаружена USB-мышь не работает на Samsung ChromeBook 3 Строка состояния Tmux мигает командой из # () перед отображением результата Сделать readonly / etc доступным для записи Как просмотреть различия между страницами руководства для разных версий одной и той же программы? Есть ли причина для того, чтобы ssh и sftp были настолько неинтегрированы? Как установить mutt на Mac OS X? Как я могу настроить близость процессора к процессу в Linux? Преобразование строки в метку времени даты в gawk или awk

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

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

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

50 000 футов – это то, что:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Хотя на этот вопрос был дан ответ, позвольте мне опубликовать подробный поток событий в ядре Linux.
Это полностью копируется из сообщений Linux: Linux Signals – Internals на блоге «Linux posts» в 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 флаг 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; 

Системные вызовы и сигналы

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

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

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

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

Рекомендации

  • Сообщения Linux: сигналы Linux – внутренние