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

rm sequence_1*.hmf

удаляет начало файлов sequence_1 и окончание .hmf.


Globbing является процессом, в котором Ваша оболочка берет шаблон и разворачивает его в список имен файлов, соответствующих тому шаблону. Не путайте его с регулярными выражениями, который отличается. Если Вы проводите большую часть своего времени в bash, Wiki Wooledge имеет хорошую страницу на globbing (расширение пути). Если Вы захотите максимальную мобильность, то Вы захотите считать спецификацию POSIX на сопоставлении с образцом также / вместо этого.


В маловероятном случае Вы сталкиваетесь со "Списком аргументов слишком долго" ошибка, можно смотреть на BashFAQ 95, который обращается к этому. Самое простое обходное решение должно разбить шаблон шарика в несколько меньших блоков, пока ошибка не уходит. В Вашем случае Вам могло, вероятно, сойти с рук разделение соответствия цифрами префикса 0 до 9, следующим образом:

for c in {0..9}; do rm sequence_1_"$c"*.hmf; done
rm sequence_1*.hmf  # catch-all case
31
27.11.2016, 16:53
3 ответа

50 000-футовое представление состоит в том что:

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

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

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

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

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

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

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

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

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

35
27.01.2020, 19:37
  • 1
    "Эти два специальных сигнала является SIGKILL и SIGSTOP" поэтому, что может, SIGCONT быть... –  Hauke Laging 20.06.2013, 00:36
  • 2
    @HaukeLaging SIGCONT является сигналом, который отменяет SIGSTOP. Документация не перечисляет его как особенный... Таким образом, я не уверен, может ли технически процесс установить его для игнорирования, то Вы не смогли бы возобновить его (только SIGKILL он). –  derobert 20.06.2013, 00:40

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

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

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

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

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

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

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

22
27.01.2020, 19:37

Хотя на этот вопрос был дан ответ, позвольте мне опубликовать подробный поток событий в ядре 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 в другом терминале.

Если вы выполните 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 процесс не будет завершен. Вместо этого он выполнит обработчик сигнала и вернется.

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

Если мы увидим внутреннюю структуру сигнала, отправляемого процессу, и поместим 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 означает, что процесс может быть запланирован.

Если выполняется, его обработчик сигнала будет запущен до завершения «медленного» системного вызова. Системный вызов по умолчанию не завершается.

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

Ссылки

15
27.01.2020, 19:37

Теги

Похожие вопросы