Яка різниця між сигакцією та сигналом?


143

Я збирався додати додатковий обробник сигналу в додаток, який ми тут маємо, і я помітив, що автор використовував sigaction()для налаштування інших обробників сигналів. Я збирався використовувати signal(). Для дотримання конвенції я повинен використовувати, sigaction()але якщо я писав з нуля, що мені вибрати?

Відповіді:


167

Використовуйте, sigaction()якщо у вас є дуже вагомі причини цього не робити.

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

  1. signal()Функція ні (обов'язково) блокувати інші сигнали від прибувають в той час як поточний обробник виконання; sigaction()може блокувати інші сигнали, поки поточний обробник не повернеться.
  2. signal()Функція (зазвичай) скидає зворотну дію сигналу до SIG_DFL( по замовчуванню) для майже всіх сигналів. Це означає, що signal()обробник повинен перевстановити себе як першу дію. Це також відкриває вікно вразливості між часом, коли сигнал виявляється, і обробник перевстановлюється, протягом якого, якщо надходить другий екземпляр сигналу, відбувається поведінка за замовчуванням (як правило, припиняється, іноді з упередженням - aka core dump).
  3. Точна поведінка signal()відрізняється між системами - і стандарти дозволяють ці зміни.

Це, як правило, вагомі причини використання sigaction()замість цього signal(). Однак інтерфейс sigaction(), безперечно, більш вигадливий.

Який з двох ви використовуєте, не піддавайтеся спокусі альтернативними інтерфейсами сигналів , таких як sighold(), sigignore(), sigpause()і sigrelse(). Вони номінально альтернативні sigaction(), але вони ледь стандартизовані і присутні в POSIX для зворотної сумісності, а не для серйозного використання. Зауважте, що стандарт POSIX говорить, що їх поведінка у багатопотокових програмах не визначена.

Багатопотокові програми та сигнали - це зовсім інша складна історія. AFAIK, обидва signal()і sigaction()добре в багатопотокових програмах.

Cornstalks зауважує :

Сторінка man для Linux signal()каже:

  Ефекти 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 СТОП ДОПОМОГА ВІД

1
якщо ви не використовуєте прапори, явно додані до sigaction (), щоб дозволити вірно імітувати стару поведінку сигналу (). Які прапори це будуть (конкретно)?
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 (), а не сигнал ()

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

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


5

Вони різні інтерфейси для засобів сигналізації ОС. Слід віддати перевагу використанню сигакції для подачі сигналу, якщо це можливо, оскільки сигнал () має певну реалізацію (часто схильну до гонки) поведінку і поводиться по-різному в Windows, OS X, Linux та інших системах UNIX.

Докладніше див. У цій примітці безпеки .


1
Я щойно переглянув вихідний код glibc і сигнал () просто викликає sigaction (). Також дивіться вище, де сторінка MacOS доводить те саме.
bmdhacks

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

5

сигнал () є стандартним C, перемикання () - ні.

Якщо ви можете використовувати будь-яке (тобто ви перебуваєте в системі POSIX), тоді використовуйте sigaction (); не визначено, чи скидає signal () скидання обробника, це означає, що для того, щоб бути переносним, потрібно знову викликати signal () всередині обробника. Найгірше те, що є гонка: якщо ви отримаєте два сигнали швидко, а другий буде доставлений перед перевстановленням обробника, у вас буде дія за замовчуванням, яка, ймовірно, буде вбивати ваш процес. sigaction () , з іншого боку, гарантовано використовує «надійну» семантику сигналу. Вам не потрібно перевстановлювати обробник, тому що він ніколи не буде скинутий. За допомогою SA_RESTART ви також можете отримати деякі системні дзвінки для автоматичного перезавантаження (тому вам не доведеться вручну перевіряти EINTR). sigaction () має більше варіантів і є надійним, тому рекомендується його використання.

Psst ... не кажи нікому, що я вам це казав, але в даний час POSIX має функцію bsd_signal (), яка діє як сигнал (), але дає семантику BSD, а це означає, що це надійно. Основне його використання - для перенесення старих програм, які приймали надійні сигнали, і POSIX не рекомендує використовувати його.


POSIX не має функції bsd_signal()- деякі реалізації POSIX можуть мати цю функцію, але сам POSIX не містить такої функції (див. POSIX ).
Джонатан Леффлер

4

Коротко:

sigaction()це добре і чітко визначено, але це функція Linux, тому вона працює лише в Linux. signal()погано і погано визначено, але це стандартна функція C, і вона працює на будь-що.

Що про це мають сказати сторінки man Linux?

man 2 signal(дивіться його онлайн тут ) говорить:

Поведінка сигналу () варіюється в різних версіях UNIX, а також історично змінюється в різних версіях Linux. Уникайте його використання: використовуйте sigaction(2)замість цього. Дивіться портативність нижче.

Переносимість Єдине портативне використання сигналу () - це встановлення диспозиції сигналу на SIG_DFL або SIG_IGN. Семантика при використанні сигналу () для встановлення обробника сигналу варіюється в різних системах (і 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/Termina-Signals.html#Terination-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 ++ Обробка сигналів [з відмінним демо-кодом]: 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.

Обидва використовують один і той же базовий об'єкт. Ви, мабуть, не повинні маніпулювати відповіддю на один сигнал з обома, але їх змішування не повинно спричинити порушення нічого ...


Це не на моїй сторінці чоловіка! Все, що я отримую, - "ОПИС" Виклик системи signal () встановлює новий обробник сигналу для сигналу з числовим знаком ". Мені потрібно оновити до корисного пакета сторінок man.
Меттью Сміт

1
Це поза Mac OS X 10,5 сторінок.
dmckee --- кошеня колишнього модератора

Також перевірено з вихідного коду glibc. signal () просто дзвонить sigaction ()
bmdhacks

2
Однак це не так у всіх реалізаціях сигналу. Якщо ви хочете призначити поведінку "сигакції", не покладайтеся на це припущення.
Бен Бернс

1

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


0

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

Цитування з документації GLIBC :

Можна використовувати як сигнальні, так і сигнальні функції в межах однієї програми, але ви повинні бути обережними, оскільки вони можуть взаємодіяти трохи дивними способами.

Функція перемикання задає більше інформації, ніж функція сигналу, тому повернене значення від сигналу не може виражати повний діапазон можливостей перемикання. Тому, якщо ви використовуєте сигнал для збереження та пізніше відновлення дії, можливо, він не зможе відновити належним чином обробник, встановлений за допомогою сигакції.

Щоб уникнути проблем у результаті, завжди використовуйте sigaction для збереження та відновлення обробника, якщо ваша програма взагалі використовує sigaction. Оскільки сигакція є більш загальною, вона може належним чином зберегти та відновити будь-яку дію, незалежно від того, була вона створена спочатку сигналом чи сигакцією.

У деяких системах, якщо встановити дію з сигналом, а потім перевірити його за допомогою перемикання, адреса обробника, яку ви отримаєте, може бути не такою, як вказана сигналом. Він може бути навіть непридатним для використання в якості аргументу дії з сигналом. Але ви можете розраховувати на використання його як аргументу сигакції. Ця проблема ніколи не трапляється в системі GNU.

Отже, вам краще використовувати той чи інший механізм послідовно в межах однієї програми.

Примітка до переносності: Основна функція сигналу є особливістю ISO C, тоді як сигагація є частиною стандарту POSIX.1. Якщо вас турбує портативність до систем, що не є POSIX, замість цього вам слід використовувати функцію сигналу.

Авторське право (C) 1996-2008 Фонд вільного програмного забезпечення, Inc.

Дозволяється копіювати, поширювати та / або змінювати цей документ на умовах ліцензії на безкоштовну документацію GNU, версія 1.2 або будь-якої пізнішої версії, опублікованої Фондом вільного програмного забезпечення; без інваріантних розділів, без текстів на передній обкладинці та без текстів із зворотною обкладинкою. Копія ліцензії міститься у розділі "Ліцензія на безкоштовну документацію GNU".


0

Сигнал із чоловічої сторінки (7)

Сигнал, орієнтований на процес, може бути доставлений до будь-якої з потоків, яка наразі не блокує сигнал. Якщо більше ніж один з потоків має сигнал розблокований, то ядро ​​вибирає довільну нитку, до якої доставити сигнал.

І я б сказав, що це "питання" існує для сигналу (2) та сигакції (2) . Тому будьте обережні з сигналами та pthreads.

... а сигнал (2), схоже, викликає сигакцію (2) під Linux з glibc.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.