В чем разница между sigaction и signal?

144

Я собирался добавить дополнительный обработчик сигналов к приложению, которое у нас есть, и заметил, что автор использовал их sigaction()для настройки других обработчиков сигналов. Я собирался использовать signal(). Чтобы следовать соглашению, я должен использовать, sigaction()но если я писал с нуля, что мне выбрать?

Мэтью Смит
источник

Ответы:

169

Используйте, sigaction()если у вас нет веских причин не делать этого.

У signal()интерфейса есть древность (и, следовательно, доступность) в свою пользу, и он определен в стандарте C. Тем не менее, у него есть ряд нежелательных характеристик, которых можно sigaction()избежать - если вы не используете явно добавленные флаги, чтобы sigaction()позволить ему точно имитировать старое signal()поведение.

  1. signal()Функция не (обязательно) блокировать другие сигналы от прибывают в то время как текущий обработчик выполнения; sigaction()может блокировать другие сигналы, пока не вернется текущий обработчик.
  2. signal()Функция (обычно) сбрасывает обратное действие сигнала к SIG_DFL( по умолчанию) для почти всех сигналов. Это означает, что signal()обработчик должен переустановить себя в качестве своего первого действия. Он также открывает окно уязвимости между моментом обнаружения сигнала и переустановкой обработчика, во время которого, если приходит второй экземпляр сигнала, происходит поведение по умолчанию (обычно прекращается, иногда с предубеждением - также известный как дамп ядра).
  3. Точное поведение signal()зависит от системы - и стандарты допускают такие вариации.

Обычно это веские причины для использования sigaction()вместо signal(). Однако интерфейс, sigaction()несомненно, более неудобный.

Какой из двух вы используете, не поддавайтесь искушению альтернативными интерфейсами сигналов , таких как sighold(), sigignore(), sigpause()и sigrelse(). Они номинально являются альтернативой sigaction(), но они едва стандартизированы и присутствуют в POSIX для обратной совместимости, а не для серьезного использования. Обратите внимание, что в стандарте POSIX указано, что их поведение в многопоточных программах не определено.

Многопоточные программы и сигналы - совсем другая сложная история. AFAIK, оба signal()и sigaction()в порядке в многопоточных приложениях.

Cornstalks замечает :

На странице signal()руководства Linux говорится:

  Эффекты signal()в многопоточном процессе не определены.

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

Это интересно. В этом случае страница руководства Linux более строгая, чем POSIX. POSIX определяет signal():

Если процесс является многопоточным или если процесс является однопоточным и обработчик сигналов выполняется иначе, чем в результате:

  • Процесс вызова abort(), raise(), kill(), pthread_kill(), или , sigqueue()чтобы генерировать сигнал , который не блокирован
  • Ожидающий сигнал разблокируется и доставляется до вызова, который его разблокировал.

поведение не определено, если обработчик сигнала обращается к любому объекту, отличному от errnoстатического продолжительности хранения, кроме присвоения значения объекту, объявленному как volatile sig_atomic_t, или если обработчик сигнала вызывает любую функцию, определенную в этом стандарте, кроме одной из функций, перечисленных в Сигнальные концепции .

Итак, POSIX четко определяет поведение signal()в многопоточном приложении.

Тем не менее, sigaction()он должен быть предпочтительным практически во всех обстоятельствах - и переносимый многопоточный код следует использовать, sigaction()если нет веской причины, по которой он не может (например, «использовать только функции, определенные стандартом C» - и да, код C11 может быть многопоточным -резьбовой). Об этом, по сути, и говорится в первом абзаце этого ответа.

Джонатан Леффлер
источник
12
Это описание signalфактически описывает поведение Unix System V. POSIX допускает либо такое поведение, либо гораздо более разумное поведение BSD, но, поскольку вы не можете быть уверены, какой из них вы получите, лучше использовать его sigaction.
R .. GitHub НЕ ПОМОГАЕТ ICE
1
если вы не используете флаги, явно добавленные к sigaction (), чтобы позволить ему точно имитировать поведение старого signal (). Какие это будут флаги (в частности)?
ChristianCuevas
@AlexFritz: В первую очередь SA_RESETHAND, тоже SA_NODEFER.
Джонатан Леффлер,
2
@BulatM. Если вы не можете использовать sigaction(), то вы по существу обязаны использовать спецификацию Standard C для signal(). Однако это дает вам чрезвычайно ограниченный набор вариантов того, что вы можете делать. Вы можете: изменять (область файла) переменные типа volatile sig_atomic_t; вызвать одну из функций «быстрого выхода» ( _Exit(), quick_exit()) или abort(); вызов signal()с текущим номером сигнала в качестве аргумента сигнала; возвращение. Вот и все. Переносимость всего остального не гарантируется. Это настолько строго, что большинство людей игнорируют эти правила, но полученный в результате код выглядит хитроумным.
Джонатан Леффлер,
1
Отличная sigaction()демонстрация от самого GCC: gnu.org/software/libc/manual/html_node/… ; и отличная signal()демонстрация от самого GCC: gnu.org/software/libc/manual/html_node/… . Обратите внимание, что в signalдемонстрации они избегают изменения обработчика ignore ( SIG_IGN), если это было намеренно установлено ранее.
Габриэль Стейплс
8

Для меня этой строки было достаточно, чтобы решить:

Функция sigaction () обеспечивает более полный и надежный механизм управления сигналами; новые приложения должны использовать sigaction (), а не signal ()

http://pubs.opengroup.org/onlinepubs/009695399/functions/signal.html#tag_03_690_07

Независимо от того, начинаете ли вы с нуля или изменяете старую программу, sigaction должен быть правильным вариантом.

Сан
источник
5

Это разные интерфейсы для сигнальных устройств ОС. Следует предпочесть использование sigaction для сигнализации, если это возможно, поскольку signal () имеет поведение, определяемое реализацией (часто склонное к гонке), и ведет себя по-разному в Windows, OS X, Linux и других системах UNIX.

Подробнее см. Это примечание по безопасности .

идидак
источник
1
Я только что посмотрел исходный код glibc, а signal () просто вызывает sigaction (). Также см. Выше, где на странице руководства MacOS утверждается то же самое.
bmdhacks
это хорошо знать. Я когда-либо видел только обработчики сигналов, используемые для аккуратного закрытия вещей перед выходом, поэтому я обычно не полагался бы на поведение, связанное с переустановкой обработчика.
Мэтью Смит,
5

signal () - это стандартный C, sigaction () - нет.

Если вы можете использовать любой из них (то есть вы работаете в системе POSIX), используйте sigaction (); не указано, сбрасывает ли signal () обработчик, а это означает, что для переносимости вы должны снова вызвать signal () внутри обработчика. Что еще хуже, есть гонка: если вы получите два сигнала в быстрой последовательности, а второй будет доставлен до того, как вы переустановите обработчик, у вас будет действие по умолчанию, которое, вероятно, приведет к уничтожению вашего процесса. sigaction () , с другой стороны, гарантированно использует «надежную» семантику сигнала. Вам не нужно переустанавливать обработчик, потому что он никогда не будет сброшен. С помощью SA_RESTART вы также можете получить некоторые системные вызовы для автоматического перезапуска (так что вам не нужно вручную проверять EINTR). sigaction () имеет больше возможностей и надежен, поэтому его использование приветствуется.

Psst ... не говорите никому, что я вам это сказал, но в POSIX в настоящее время есть функция bsd_signal (), которая действует как signal (), но дает семантику BSD, что означает ее надежность. Его основное использование - перенос старых приложений, которые предполагали надежные сигналы, и POSIX не рекомендует его использовать.

Хуззефахан
источник
POSIX не имеет функции bsd_signal()- некоторые реализации POSIX могут иметь эту функцию, но сам POSIX не содержит такой функции (см. POSIX ).
Джонатан Леффлер,
4

Короче говоря:

sigaction()это хорошо и четко определено, но это функция Linux, поэтому она работает только в Linux. signal()плохая и плохо определенная, но это стандартная функция C, поэтому она работает с чем угодно.

Что об этом говорится на страницах руководства Linux?

man 2 signal(см. здесь онлайн ) гласит:

Поведение signal () различается в разных версиях UNIX, а также исторически менялось в разных версиях Linux. Избегайте его использования: используйте sigaction(2)вместо него. См. Раздел «Переносимость» ниже.

Переносимость Единственное портативное использование signal () - установить для сигнала значение SIG_DFL или SIG_IGN. Семантика при использовании signal () для установки обработчика сигнала различается в зависимости от системы (и POSIX.1 явно разрешает это изменение); не используйте его для этой цели.

Другими словами: не используйте signal(). Используйте sigaction()вместо этого!

Что думает GCC?

Примечание по совместимости: как сказано выше, по signalвозможности следует избегать этой функции. sigactionявляется предпочтительным методом.

Источник: https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling

Итак, если и Linux, и GCC говорят не использовать signal(), а использовать sigaction()вместо этого, возникает вопрос: как, черт возьми, мы используем эту запутанную sigaction()вещь !?

Примеры использования:

Прочтите signal()здесь ОТЛИЧНЫЙ пример GCC : https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling

И их ОТЛИЧНЫЙ sigaction()пример здесь: https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html

Прочитав эти страницы, я придумал следующую технику sigaction():

1. sigaction(), так как это правильный способ прикрепить обработчик сигнала, как описано выше:

#include <errno.h>  // errno
#include <signal.h> // sigaction()
#include <stdio.h>  // printf()
#include <string.h> // strerror()

#define LOG_LOCATION __FILE__, __LINE__, __func__ // Format: const char *, unsigned int, const char *
#define LOG_FORMAT_STR "file: %s, line: %u, func: %s: "

/// @brief      Callback function to handle termination signals, such as Ctrl + C
/// @param[in]  signal  Signal number of the signal being handled by this callback function
/// @return     None
static void termination_handler(const int signal)
{
    switch (signal)
    {
    case SIGINT:
        printf("\nSIGINT (%i) (Ctrl + C) signal caught.\n", signal);
        break;
    case SIGTERM:
        printf("\nSIGTERM (%i) (default `kill` or `killall`) signal caught.\n", signal);
        break;
    case SIGHUP:
        printf("\nSIGHUP (%i) (\"hang-up\") signal caught.\n", signal);
        break;
    default:
        printf("\nUnk signal (%i) caught.\n", signal);
        break;
    }

    // DO PROGRAM CLEANUP HERE, such as freeing memory, closing files, etc.


    exit(signal);
}

/// @brief      Set a new signal handler action for a given signal
/// @details    Only update the signals with our custom handler if they are NOT set to "signal ignore" (`SIG_IGN`),
///             which means they are currently intentionally ignored. GCC recommends this "because non-job-control
///             shells often ignore certain signals when starting children, and it is important for children
///             to respect this." See
///             https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling
///             and https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html.
///             Note that termination signals can be found here:
///             https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html#Termination-Signals
/// @param[in]  signal  Signal to set to this action
/// @param[in]  action  Pointer to sigaction struct, including the callback function inside it, to attach to this signal
/// @return     None
static inline void set_sigaction(int signal, const struct sigaction *action)
{
    struct sigaction old_action;

    // check current signal handler action to see if it's set to SIGNAL IGNORE
    sigaction(signal, NULL, &old_action);
    if (old_action.sa_handler != SIG_IGN)
    {
        // set new signal handler action to what we want
        int ret_code = sigaction(signal, action, NULL);
        if (ret_code == -1)
        {
            printf(LOG_FORMAT_STR "sigaction failed when setting signal to %i;\n"
                   "  errno = %i: %s\n", LOG_LOCATION, signal, errno, strerror(errno));
        }
    }
}

int main(int argc, char *argv[])
{
    //...

    // Register callbacks to handle kill signals; prefer the Linux function `sigaction()` over the C function
    // `signal()`: "It is better to use sigaction if it is available since the results are much more reliable."
    // Source: https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling
    // and /programming/231912/what-is-the-difference-between-sigaction-and-signal/232711#232711.
    // See here for official gcc `sigaction()` demo, which this code is modeled after:
    // https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html

    // Set up the structure to specify the new action, per GCC's demo.
    struct sigaction new_action;
    new_action.sa_handler = termination_handler; // set callback function
    sigemptyset(&new_action.sa_mask);
    new_action.sa_flags = 0;

    // SIGINT: ie: Ctrl + C kill signal
    set_sigaction(SIGINT, &new_action);
    // SIGTERM: termination signal--the default generated by `kill` and `killall`
    set_sigaction(SIGTERM, &new_action);
    // SIGHUP: "hang-up" signal due to lost connection
    set_sigaction(SIGHUP, &new_action);

    //...
}

2. И signal()хотя это не лучший способ прикрепить обработчик сигнала, как описано выше, все же хорошо знать, как его использовать.

Вот скопированный демонстрационный код GCC, и он настолько хорош, насколько это возможно:

#include <signal.h>

void
termination_handler (int signum)
{
  struct temp_file *p;

  for (p = temp_file_list; p; p = p->next)
    unlink (p->name);
}

int
main (void)
{
  …
  if (signal (SIGINT, termination_handler) == SIG_IGN)
    signal (SIGINT, SIG_IGN);
  if (signal (SIGHUP, termination_handler) == SIG_IGN)
    signal (SIGHUP, SIG_IGN);
  if (signal (SIGTERM, termination_handler) == SIG_IGN)
    signal (SIGTERM, SIG_IGN);
  …
}

Основные ссылки, о которых следует знать:

  1. Стандартные сигналы: https://www.gnu.org/software/libc/manual/html_node/Standard-Signals.html#Standard-Signals
    1. Сигналы завершения: https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html#Termination-Signals
  2. Базовая обработка сигналов, включая официальный signal()пример использования GCC : https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling
  3. Официальный sigaction()пример использования GCC : https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html
  4. Наборы сигналов, включая sigemptyset()и sigfillset(); Я до сих пор не совсем понимаю это, но знаю, что они важны: https://www.gnu.org/software/libc/manual/html_node/Signal-Sets.html

Смотрите также:

  1. TutorialsPoint C ++ Signal Handling [с отличным демонстрационным кодом]: https://www.tutorialspoint.com/cplusplus/cpp_signal_handling.htm
  2. https://www.tutorialspoint.com/c_standard_library/signal_h.htm
Габриэль Скобы
источник
2

На signal(3)странице руководства :

ОПИСАНИЕ

 This signal() facility is a simplified interface to the more
 general sigaction(2) facility.

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

dmckee --- котенок экс-модератора
источник
Этого нет на моей странице руководства! Все, что я получаю, это «ОПИСАНИЕ. Системный вызов signal () устанавливает новый обработчик сигнала для сигнала с номером signum». Мне нужно перейти на полезный пакет страниц руководства .
Мэтью Смит,
1
Это не на страницах Mac OS X 10.5.
dmckee --- котенок экс-модератора
Также проверено из исходного кода glibc. signal () просто вызывает sigaction ()
bmdhacks
2
Однако это не так для всех реализаций signal. Если вы хотите санкционировать «сигакционное» поведение, не полагайтесь на это предположение.
Бен Бернс
1

Я бы также предложил использовать sigaction () вместо signal () и хотел бы добавить еще один момент. sigaction () предоставляет дополнительные параметры, такие как pid умершего процесса (возможно с использованием структуры siginfo_t).

Прадьюмна Каушик
источник
0

Я бы использовал signal (), поскольку он более переносимый, по крайней мере теоретически. Я проголосую за любого комментатора, который сможет предложить современную систему, не имеющую уровня совместимости с POSIX и поддерживающую signal ().

Цитата из документации GLIBC :

В одной программе можно использовать функции signal и sigaction, но вы должны быть осторожны, потому что они могут взаимодействовать несколько странным образом.

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

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

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

Итак, вам лучше использовать один или другой механизм последовательно в рамках одной программы.

Примечание о переносимости: основная функция сигнала - это функция ISO C, а функция sigaction - часть стандарта POSIX.1. Если вас беспокоит переносимость в системы, отличные от POSIX, вам следует использовать вместо этого функцию signal.

Авторское право (C) 1996-2008 Free Software Foundation, Inc.

Разрешается копировать, распространять и / или изменять этот документ в соответствии с условиями лицензии GNU Free Documentation License версии 1.2 или любой более поздней версии, опубликованной Free Software Foundation; без неизменяемых разделов, без текстов на лицевой обложке и без текстов на задней обложке. Копия лицензии включена в раздел «Лицензия свободной документации GNU».

bmdhacks
источник
0

Из справочной страницы signal (7)

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

И я бы сказал, что эта «проблема» существует для signal (2) и sigaction (2) . Так что будьте осторожны с сигналами и потоками.

... и signal (2), кажется, вызывает sigaction (2) в Linux с помощью glibc.

Роберт Бергер
источник