Межпроцессное взаимодействие Linux — сигналы

Linux

1. Распознайте сигнал

Сигналы — это ограниченный способ связи между процессами в Unix, Unix-подобных и других POSIX-совместимых операционных системах. Это асинхронный механизм уведомления, используемый для оповещения процесса о том, что произошло событие. Когда процессу посылается сигнал, операционная система прерывает нормальный поток управления процессом, и в этот момент любая неатомарная операция будет прервана. Если процесс определяет обработчик сигнала, то он будет выполнен, в противном случае будет выполнен обработчик по умолчанию.

Сигнал является единственным механизмом асинхронной связи в механизме межпроцессного взаимодействия, его можно рассматривать как асинхронное уведомление, которое информирует процесс, получивший сигнал, о том, что произошло. Также можно просто понять, что сигнал представляет собой мягкое прерывание в той или иной форме.

2. Источник сигнала

В целом источники сигналов можно разделить на следующие три типа:

  • Аппаратный режим: деление на ноль, неверный доступ к хранилищу и другие аппаратные исключения генерируют сигналы. Эти события обычно обнаруживаются аппаратным обеспечением (например, ЦП) и уведомляются ядру операционной системы Linux, которое затем генерирует соответствующие сигналы и отправляет сигналы программе, которая выполнялась в момент возникновения события.
  • Программный режим: пользователь звонит под терминаломkillКоманды посылают сигналы задач процессам, вызовы процессовkillилиsigqueueФункция отправляет сигнал, когда обнаруживает, что определенное условие программного обеспечения было выполнено, например,alarmилиsettimerбудет сгенерировано, когда установленное время таймера истечетSIGALRMСигналы могут генерироваться по множеству сценариев, включая сигналы.
  • Ввод с клавиатуры: когда пользователь нажимает клавишу на терминале, генерируется сигнал. как комбинация клавишCtrl+CпроизведетSIGINTсигнал,Ctrl+\сформировал одинSIGQUITсигнал и т. д.

3. Типы сигналов

работоспособныйkill -lОзнакомьтесь со списком поддерживаемых Linux сигналов:

sl@Li:~/Works$ 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

Видно, что система в Linux поддерживает в общей сложности 64 сигнала, из которых сигналы с 1 по 31 являются обычными сигналами (также ненадежными сигналами), а с 34 по 64 являются сигналами реального времени (надежными сигналами).

Разница между надежными и ненадежными сигналами:

  • Ненадежность здесь в основном из-за того, что не поддерживается очередь сигналов, то есть при возникновении в процессе множественных сигналов (когда скорость приема сигналов превышает скорость обработки процесса) эти сигналы, не пришедшие и не обработанные, будут отбрасываться Остался всего один сигнал.
  • Надежный сигнал — это когда в процесс посылается несколько сигналов (когда скорость получения сигнала превышает скорость, с которой процесс может обработать сигнал), эти не пришедшие и не обработанные сигналы будут поставлены в очередь в процессе. Когда у процесса появится возможность обработки, он будет обработан по очереди, и сигнал не будет потерян.

Вот несколько часто используемых сигналов:

Сигнал описывать
SIGHUP Когда пользователь выходит из терминала, все процессы, запущенные терминалом, получат этот сигнал, и действие по умолчанию — завершить процесс.
SIGINT Программа завершает (прерывает) сигнал после того, как пользователь вводит символ INTR (обычноCtrl+C) выдается, чтобы уведомить группу процессов переднего плана о завершении процесса.
SIGQUIT а такжеSIGINTпохож, но состоит из символа ВЫХОД (обычноCtrl+\) контролировать.SIGQUITбудет сгенерировано при выходеcoreфайл, в этом смысле подобен сигналу ошибки программы.
SIGKILL Используется для немедленного завершения выполнения программы.Этот сигнал нельзя заблокировать, обработать или проигнорировать.
SIGTERM сигнал окончания (завершения) программы иSIGKILLРазница в том, что сигнал можно блокировать и обрабатывать. Обычно используется, чтобы попросить программу завершиться самостоятельно.
SIGSTOP Остановить (остановить) выполнение процесса.Обратите внимание на разницу между ним и терминацией и прерыванием: процесс не завершился, просто приостановлено выполнение.Этот сигнал нельзя заблокировать, обработать или проигнорировать.

4. Реентерабельные функции

Обратите внимание на повторный вход функции обработки сигнала, потому что выполнение функции обработки сигнала может быть снова прервано другими сигналами.В это время программа перейдет к функции обработки другого сигнала и вернется к текущей функции обработки. после завершения обработки Write Вы должны обратить на это внимание, когда будете определять свои собственные функции обработки сигналов. Сигналы можно понимать как «мягкие прерывания», поэтому реентерабельные функции хорошо понятны.

5. Захват сигнала

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

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

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

Вот пример, иллюстрирующий этот процесс:

  1. Пользовательская программа зарегистрированаSIGQUITобработчик сигналовsighandler.
  2. в настоящее время выполняетсяmainфункция, когда происходит прерывание или исключение, она переключается в режим ядра.
  3. Возврат в пользовательский режим после обработки прерыванияmainСигнал был обнаружен до того, как функцияSIGQUITдоставлен.
  4. Ядро решает не возобновлять работу после возврата в пользовательский режим.mainКонтекст функции продолжает выполнение, но вместо этого выполняетсяsighandlerфункция,sighandlerа такжеmainФункции используют разные стековые пространства, между ними нет отношений вызова и вызова, и они представляют собой два независимых потока управления.
  5. sighandlerАвтоматически выполнять специальный системный вызов после возврата из функцииsigreturnВойдите в режим ядра снова.
  6. Если нет нового сигнала для доставки, возврат в пользовательский режим на этот раз является восстановлением.mainКонтекст функции продолжает выполнение.

6. Пример кода 1

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

#include<stdlib.h>
#include<stdio.h>
#include<signal.h>
#include<sys/types.h>
#include<unistd.h>

void sig_handle(int sig) {
    printf("received signal: %d, quit.\n", sig);
    exit(0);
}

int main () {
    signal(SIGINT, sig_handle);
    signal(SIGKILL, sig_handle);
    signal(SIGSEGV, sig_handle);
    signal(SIGTERM, sig_handle);

    int i = 0;
    while (1) {
        printf("%d\n", ++i);
        sleep(2);
    }

    printf("main quit.");

    return 0;
}

результат операции:

1
2
received signal: 15, quit.

Семь, пример кода 2

Функция этого кода аналогична приведенному выше примеру.Сигнал также может передавать параметры, но это больше функция уведомления.Информация, которая может быть передана, очень ограничена.Если необходимо передать большое количество информации, другие можно рассмотреть методы межпроцессного взаимодействия.

#include<signal.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>

void new_op(int, siginfo_t*, void*);

int main() {
    if (NULL == freopen("sigproc.log", "w", stdout)) {
        fprintf(stderr, "error redirecting stdout\n");
    }

    struct sigaction act;

    sigemptyset(&act.sa_mask);  //sa_mask指定在信号处理程序执行过程中,哪些信号应当被阻塞。缺省情况下当前信号本身被阻塞,防止信号的嵌套发送
    sigaddset(&act.sa_mask, SIGTERM);
    sigaddset(&act.sa_mask, SIGINT);
    act.sa_flags = SA_SIGINFO;  //SA_SIGINFO,当设定了该标志位时,表示信号附带的参数可以被传递到信号处理函数中
    act.sa_sigaction = new_op;

    if (sigaction(SIGINT, &act, NULL) < 0) {
        printf("install sigal error\n");
    }

    if (sigaction(SIGTERM, &act, NULL) < 0) {
        printf("install sigal error\n");
    }

    if (sigaction(SIGHUP, &act, NULL) < 0) {
        printf("install sigal error\n");
    }

    int i = 0;
    while (1) {
        printf("%d\n", ++i);
        sleep(1);
    }

    printf("end.");

    return 0;
}

void new_op(int signum, siginfo_t *info, void *myact) {
    printf("receive signal %d\n", signum);
    for (int i = 0; i < 5; ++i) {
        printf("signal processing: %d\n", i);
        sleep(1);
    }
    printf("process quit.");

    exit(0);
}

результат операции:

1
2
3
receive signal 15
signal processing: 0
signal processing: 1
signal processing: 2
signal processing: 3
signal processing: 4
process quit.

Справочная документация:
Сигнал
Межпроцессное взаимодействие в среде Linux (2) Сигналы (1)
Межпроцессное взаимодействие в среде Linux (2) Сигналы (2)

Наконец, добро пожаловать, чтобы обратить внимание на публичный аккаунт WeChat, Поехали!