Я хочу использовать sigaction (), но у меня проблема - PullRequest
0 голосов
/ 27 апреля 2020

Я хочу создать программу, в которой процесс получит SIGQUIT, распечатать какое-либо сообщение и завершить процесс. и вот мой код,

void signal_handler(int num)
{
  printf(Received SIGQUIT\n");
  kill(getpid(), SIGINT);
}

int main()
{
  struct sigaction sig;
  sig.sa_handler = signal_handler;
  sigemptyset(&sig.sa_mask);
  sig.sa_flags = 0;
  sigaction(SIGQUIT, &sig, NULL);

  for(;;) {
    printf("Input SIGQUIT : ");
  }

  return 0;
}

мой ожидаемый результат

Введите SIGQUIT: ^ \ Received SIGQUIT

и завершите процесс,

но фактический результат -

введите описание изображения здесь

сообщение «Вход SIGQUIT:» не распечатывается до получения SIGQUIT

Я хочу знать, почему. .

1 Ответ

1 голос
/ 27 апреля 2020

Во-первых: код, который вы цитируете, не компилируется. Пожалуйста, отправьте действительный код; это увеличивает ваши шансы получить ответ.

Я позволю себе переформулировать ваш вопрос примерно так:

Как правильно завершить программу?

Не надо переформулировать вопросы, я знаю. Но в интересах здравомыслия я должен распространить слово, что асинхронная доставка сигнала является ошибкой , которая была сделана , когда было изобретено UNIX . Читайте ниже для небольшого объяснения причин и некоторых альтернатив. Но сначала

TL; DR : не использовать асинхронную доставку сигнала. Очень трудно правильно настроить асинхронную доставку сигнала - ловушек много (спасибо @Shawn за комментарий). Скорее, синхронно , ожидайте поступления сигнала. Вот как это будет выглядеть,

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


int main(void)
{
    int error;

    // setup set of signals that are meant to terminate us
    sigset_t termination_signals;
    sigemptyset(&termination_signals);
    sigaddset(&termination_signals, SIGTERM);
    sigaddset(&termination_signals, SIGINT);
    sigaddset(&termination_signals, SIGQUIT);

    // block asynchronous delivery for those
    error = sigprocmask(SIG_BLOCK, &termination_signals, NULL);
    if (error) {
        perror("sigprocmask(SIGTERM|SIGINT|SIGQUIT)");
        exit(1);
    }

    // wait for one of these signals to arrive. EINTR handling is
    // always good to have in larger programs. for example, libraries
    // might make use of signals in their own weird way - thereby
    // disturbing their users most impolitely by interrupting every
    // operation they synchronously wait for.
    while (1) {
        int sig;
        error = sigwait(&termination_signals, &sig);
        if (error && errno == EINTR) {
            perror("sigwait");
            continue;
        }

        printf("received termination signal %d\n", sig);
        break;
    }

    return 0;
}

Асинхронная доставка сигнала

Сигналы - это уведомление бедного человека . Это как бросать целочисленные значения (с предопределенными значениями) в процессы. Первоначально, когда они изобретали UNIX, им пришлось придумать механизм уведомления для использования между процессами. Они сделали это способом, аналогичным популярному тогда механизму уведомлений: прерываниям. (Хотя это может показаться циничным, я бы поспорил, что я не за горами.)

Вопросы, которые следует задать себе, прежде чем вы действительно решите go этот маршрут:

  1. Действительно ли я знаю , что мне это нужно?
  2. Каковы последствия?
  3. Есть ли альтернативы?

Хотя я не могу помочь с 1., здесь некоторая информация для 2. и 3.

Последствия асинхронной обработки сигналов

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

Механизм прерывания сравним с тем, что вы знаете по многопоточности, на самом высоком уровне. Общим знаменателем этих двух может быть то, что «если вы не заботитесь о себе, вы мертвы, но вы можете даже не знать» . Условия гонки.

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

Как будто этого недостаточно, есть концепция прерванные системные вызовы .

Обоснование выглядит следующим образом (взято из гипотетического разговора между Кеном Томпсоном и Деннисом Ритчем ie, прямо перед эпохой),

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

Что теперь, если целевой процесс спит? Ждет что-нибудь случится? Таймер? Возможно, данные из последовательного порта?

A: давайте его разбудим!

В результате блокируются системные вызовы (например, чтение из TCP-соединения - они не был там в то время - или из последовательных портов) прервано . Когда вы читаете из сокета или иным образом блокируете, пока не произойдет какое-либо событие, вызов возвращает ненулевое значение с глобальной переменной errno (еще один артефакт начала семидесятых), установленной на EINTR.

Мне все еще неясно, каково предполагаемое поведение, когда целевой процесс имеет несколько потоков, где каждый поток блокирует что-то. Я ожидаю, что каждый такой блокирующий вызов прерывается. К сожалению, поведение не соответствует, даже на Linuxen, не говоря уже о других UNIXen, которые я не использовал в течение длительного времени. Я не юрист по стандартам, но это само по себе заставляет меня убегать от этого мистического механизма.

Тем не менее, если вы все еще не убегаете, пожалуйста, сообщите себе точные определения. На мой взгляд, лучше всего начать с прочтения руководства man 7 signal страница . Хотя это непросто, но точно.

Далее, чтобы узнать, что можно сделать в подпрограмме обработки прерываний, функции обработчика ошибок, прочитайте страницу руководства man 7 signal-safety . Вы увидите, что, например, ничего из перечисленного не является безопасным :

  • Нет printf(). Вы используете это в обработчике сигналов, поэтому, пожалуйста, не делайте. Ни один из братьев и сестер printf(), такие как fprintf() тоже не являются безопасными. Ни один из <stdio.h>.
  • <pthread.h> не является безопасным. Вы не можете изменять поточно-ориентированные структуры данных, потому что вы непреднамеренно заблокировали мьютекс, или сигнализировали (намеревался каламбур) переменную условия, или использовали какой-либо другой механизм потоков.

Альтернативы

Синхронно ждут сигналов . Код, который я привел выше, делает это.

Событийное программирование . Ниже приводится Linux конкретный c.

В основе этого лежит событие l oop некоторой формы. Инструментарий GUI работает таким образом, поэтому, если ваш код является частью такой большой картинки, вы можете подключить сигнальные уведомления на основе файлового дескриптора к чему-то, что уже есть.

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

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

Самостоятельная уловка . Смотрите здесь ; это обычно используется, когда ваша программа основана на событиях, но вы не можете использовать Linux -specifi c signalfd() выше.

...