Jaka jest różnica między sigaction a signal?

143

Miałem właśnie dodać dodatkowy moduł obsługi sygnału do aplikacji, którą tu mamy i zauważyłem, że autor użył sigaction()do skonfigurowania innych programów obsługi sygnału. Miałem zamiar użyć signal(). Zgodnie z konwencją powinienem się kierować, sigaction()ale jeśli pisałem od zera, którą wybrać?

Matthew Smith
źródło

Odpowiedzi:

167

Używaj, sigaction()chyba że masz bardzo ważne powody, aby tego nie robić.

signal()Interfejs ma antyku (i stąd dostępności) na swoją korzyść, a jest zdefiniowany w normie C. Niemniej jednak ma wiele niepożądanych cech, których sigaction()unika - chyba że używasz flag wyraźnie dodanych, sigaction()aby umożliwić wierne symulowanie starego signal()zachowania.

  1. signal()Funkcja nie (muszą) blokują inne sygnały z przybyciem gdy prąd obsługowy wykonania; sigaction()może blokować inne sygnały do ​​czasu powrotu aktualnej procedury obsługi.
  2. signal()Funkcja (zazwyczaj) resetuje plecy sygnał do działania SIG_DFL(domyślnie) dla prawie wszystkich sygnałów. Oznacza to, że program signal()obsługi musi przeinstalować się w pierwszej akcji. Otwiera to również okno podatności między momentem wykrycia sygnału a ponownym zainstalowaniem programu obsługi, podczas którego, jeśli nadejdzie druga instancja sygnału, zachodzi domyślne zachowanie (zwykle przerywane, czasem z uprzedzeniem - zwane też zrzutem jądra).
  3. Dokładne zachowanie signal()różni się w zależności od systemu - a normy dopuszczają takie różnice.

Są to ogólnie dobre powody, dla których warto używać sigaction()zamiast signal(). Jednak interfejs programu sigaction()jest niezaprzeczalnie bardziej skomplikowany.

Którykolwiek z nich używasz, nie daj się skusić alternatywnych interfejsów sygnałowych, takich jak sighold(), sigignore(), sigpause()i sigrelse(). Są nominalnie alternatywą dla sigaction(), ale są ledwo znormalizowane i są obecne w POSIX dla kompatybilności wstecznej, a nie do poważnego użytku. Zauważ, że standard POSIX mówi, że ich zachowanie w programach wielowątkowych jest niezdefiniowane.

Programy i sygnały wielowątkowe to zupełnie inna skomplikowana historia. AFAIK, oba signal()i sigaction()są OK w aplikacjach wielowątkowych.

Cornstalks zauważa :

Strona podręcznika Linux dla signal()mówi:

  Skutki signal()w procesie wielowątkowym są nieokreślone.

Dlatego myślę, że sigaction()jest to jedyny, który można bezpiecznie używać w procesie wielowątkowym.

To interesujące. W tym przypadku strona podręcznika Linux jest bardziej restrykcyjna niż POSIX. POSIX określa signal():

Jeśli proces jest wielowątkowy lub jeśli proces jest jednowątkowy, a procedura obsługi sygnału jest wykonywana inaczej niż w wyniku:

  • Powołanie proces abort(), raise(), kill(), pthread_kill(), lub sigqueue(), aby wygenerować sygnał, który nie jest zablokowany
  • Oczekujący sygnał, który został odblokowany i dostarczony przed wywołaniem, które go odblokowało, powraca

zachowanie jest niezdefiniowane, jeśli program obsługi sygnału odnosi się do dowolnego obiektu innego niż errnoze statycznym czasem przechowywania innym niż przypisanie wartości do obiektu zadeklarowanego jako volatile sig_atomic_t, lub jeśli program obsługi sygnału wywołuje jakąkolwiek funkcję zdefiniowaną w tym standardzie, inną niż jedna z funkcji wymienionych w Koncepcje sygnałów .

Zatem POSIX jasno określa zachowanie signal()w aplikacji wielowątkowej.

Niemniej jednak sigaction()powinno być preferowane w zasadzie we wszystkich okolicznościach - a przenośny kod wielowątkowy powinien być używany, sigaction()chyba że istnieje przytłaczający powód, dla którego nie może (na przykład „używaj tylko funkcji zdefiniowanych przez Standard C” - i tak, kod C11 może być wielowątkowy gwintowane). I tak właśnie jest w pierwszym akapicie tej odpowiedzi.

Jonathan Leffler
źródło
12
Ten opis signaldotyczy w rzeczywistości zachowania systemu Unix V. POSIX pozwala albo na to zachowanie, albo na znacznie bardziej rozsądne zachowanie BSD, ale ponieważ nie możesz być pewien, które z nich uzyskasz, nadal najlepiej jest go używać sigaction.
R .. GitHub PRZESTAŃ POMÓC W LODZIE
1
chyba że używasz flag jawnie dodanych do sigaction (), aby umożliwić wierne symulowanie starego zachowania signal (). Jakie to byłyby flagi (konkretnie)?
ChristianCuevas
@AlexFritz: Przede wszystkim SA_RESETHANDteż SA_NODEFER.
Jonathan Leffler
2
@BulatM. Jeśli nie możesz użyć sigaction(), zasadniczo jesteś zobowiązany do korzystania ze specyfikacji Standard C dla signal(). Jednak daje to niezwykle zubożały zestaw opcji tego, co możesz zrobić. Możesz: modyfikować (zakres plików) zmienne typu volatile sig_atomic_t; wywołaj jedną z funkcji „szybkiego wyjścia” ( _Exit(), quick_exit()) lub abort(); wywołanie signal()z bieżącym numerem sygnału jako argumentem sygnału; powrót. I to wszystko. Nie ma gwarancji, że cokolwiek innego będzie przenośne. Jest to tak surowe, że większość ludzi ignoruje te zasady - ale wynikowy kod jest podejrzany.
Jonathan Leffler
1
Doskonałe sigaction()demo od samych GCC: gnu.org/software/libc/manual/html_node/… ; oraz doskonałe signal()demo od samego GCC: gnu.org/software/libc/manual/html_node/… . Zauważ, że w wersji signaldemonstracyjnej unikają zmiany funkcji obsługi z ignore ( SIG_IGN), jeśli taka była wcześniej celowo ustawiona.
Gabriel Staples,
8

Dla mnie ta poniższa linijka wystarczyła do podjęcia decyzji:

Funkcja sigaction () zapewnia bardziej wszechstronny i niezawodny mechanizm sterowania sygnałami; nowe aplikacje powinny używać sigaction () zamiast signal ()

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

Niezależnie od tego, czy zaczynasz od zera, czy modyfikujesz stary program, sigaction powinna być właściwą opcją.

San
źródło
5

Są to różne interfejsy dla urządzeń sygnalizacyjnych systemu operacyjnego. Należy preferować używanie sigaction do sygnalizowania, jeśli to możliwe, ponieważ signal () ma zachowanie zdefiniowane w implementacji (często podatne na wyścigi) i zachowuje się inaczej w systemach Windows, OS X, Linux i innych systemach UNIX.

Szczegółowe informacje można znaleźć w tej uwadze bezpieczeństwa .

ididak
źródło
1
Właśnie spojrzałem na kod źródłowy glibc i signal () po prostu wywołuje sigaction (). Zobacz także powyżej, gdzie strona podręcznika MacOS twierdzi to samo.
bmdhacks
dobrze wiedzieć. Widziałem tylko programy obsługujące sygnały używane do porządnego zamykania rzeczy przed wyjściem, więc zwykle nie polegałbym na zachowaniu związanym z ponowną instalacją programu obsługi.
Matthew Smith,
5

signal () to standardowe C, sigaction () nie.

Jeśli możesz użyć jednego z nich (to znaczy jesteś w systemie POSIX), użyj sigaction (); nie jest określone, czy signal () resetuje procedurę obsługi, co oznacza, że ​​aby być przenośnym, należy ponownie wywołać funkcję signal () wewnątrz tej funkcji. Co gorsza, jest wyścig: jeśli otrzymasz dwa sygnały w krótkich odstępach czasu, a drugi zostanie dostarczony przed ponowną instalacją programu obsługi, będziesz mieć domyślną akcję, która prawdopodobnie będzie polegała na zabiciu twojego procesu. Z drugiej strony sigaction () gwarantuje użycie „niezawodnej” semantyki sygnału. Nie musisz ponownie instalować programu obsługi, ponieważ nigdy nie zostanie on zresetowany. Dzięki SA_RESTART możesz również ustawić automatyczne ponowne uruchamianie niektórych wywołań systemowych (więc nie musisz ręcznie sprawdzać EINTR). sigaction () ma więcej opcji i jest niezawodny, dlatego zaleca się jego stosowanie.

Psst ... nie mów nikomu, że ci to powiedziałem, ale POSIX ma obecnie funkcję bsd_signal (), która działa jak signal (), ale zapewnia semantykę BSD, co oznacza, że ​​jest niezawodna. Jego głównym zastosowaniem jest przenoszenie starych aplikacji, które zakładały niezawodne sygnały, a POSIX nie zaleca jego używania.

Huzzefakhan
źródło
POSIX nie ma funkcji bsd_signal()- niektóre implementacje POSIX mogą mieć tę funkcję, ale sam POSIX nie zawiera takiej funkcji (patrz POSIX ).
Jonathan Leffler
4

W skrócie:

sigaction()jest dobry i dobrze zdefiniowany, ale jest funkcją Linuksa, więc działa tylko w Linuksie. signal()jest zły i słabo zdefiniowany, ale jest standardową funkcją C, więc działa na wszystkim.

Co na ten temat mają do powiedzenia strony podręcznika Linux?

man 2 signal(zobacz tutaj online ) stwierdza:

Zachowanie signal () różni się w różnych wersjach systemu UNIX, a także w przeszłości różniło się w różnych wersjach Linuksa. Unikaj jego używania: używaj sigaction(2)zamiast tego. Zobacz przenośność poniżej.

Przenośność Jedynym przenośnym zastosowaniem signal () jest ustawienie dyspozycji sygnału na SIG_DFL lub SIG_IGN. Semantyka użycia signal () do ustanowienia programu obsługi sygnału różni się w różnych systemach (a POSIX.1 wyraźnie zezwala na tę odmianę); nie używaj go w tym celu.

Innymi słowy: nie używaj signal(). Użyj sigaction()zamiast tego!

Co myśli GCC?

Uwaga dotycząca zgodności: Jak wspomniano powyżej w przypadku signal, należy unikać tej funkcji, gdy jest to możliwe. sigactionjest metodą preferowaną.

Źródło: https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling

Tak więc, jeśli zarówno Linux, jak i GCC mówią, żeby nie używać signal(), ale używać sigaction()zamiast tego, nasuwa się pytanie: jak u licha używamy tej mylącej sigaction()rzeczy !?

Przykłady użycia:

Przeczytaj DOSKONAŁY signal()przykład GCC tutaj: https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling

I ich DOSKONAŁY sigaction()przykład tutaj: https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html

Po przeczytaniu tych stron wymyśliłem następującą technikę sigaction():

1. sigaction(), ponieważ jest to właściwy sposób dołączania obsługi sygnału, jak opisano powyżej:

#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. I signal()nawet jeśli nie jest to dobry sposób na podłączenie modułu obsługi sygnału, jak opisano powyżej, nadal dobrze jest wiedzieć, jak go używać.

Oto kod demonstracyjny GCC skopiowany i wklejony, ponieważ jest mniej więcej tak dobry, jak będzie:

#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);
  
}

Główne linki, o których należy pamiętać:

  1. Sygnały standardowe: https://www.gnu.org/software/libc/manual/html_node/Standard-Signals.html#Standard-Signals
    1. Sygnały zakończenia: https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html#Termination-Signals
  2. Podstawowa obsługa sygnałów, w tym oficjalny signal()przykład użycia GCC : https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling
  3. Oficjalny sigaction()przykład użycia GCC : https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html
  4. Zestawy sygnałowe, w tym sigemptyset()i sigfillset(); Nadal nie rozumiem ich dokładnie, ale wiem, że są ważne: https://www.gnu.org/software/libc/manual/html_node/Signal-Sets.html

Zobacz też:

  1. Obsługa sygnałów w programie TutorialsPoint C ++ [z doskonałym kodem demonstracyjnym]: https://www.tutorialspoint.com/cplusplus/cpp_signal_handling.htm
  2. https://www.tutorialspoint.com/c_standard_library/signal_h.htm
Gabriel Staples
źródło
2

Ze strony podręcznika signal(3):

OPIS

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

Oba wywołują ten sam instrument bazowy. Prawdopodobnie nie powinieneś manipulować odpowiedzią pojedynczego sygnału z obydwoma, ale mieszanie ich nie powinno powodować żadnych przerw ...

dmckee --- kociak ex-moderator
źródło
Tego nie ma na mojej stronie podręcznika! Otrzymuję tylko „OPIS Wywołanie systemowe signal () instaluje nowy program obsługi sygnału dla sygnału z numerem signum.” Muszę zaktualizować pakiet przydatnych stron podręcznika .
Matthew Smith
1
To jest poza stronami Mac OS X 10.5.
dmckee --- ex-moderator kitten
Zweryfikowano również na podstawie kodu źródłowego glibc. signal () po prostu wywołuje sigaction ()
bmdhacks
2
Nie dotyczy to jednak wszystkich implementacji signal. Jeśli chcesz narzucić zachowanie „sigaction”, nie polegaj na tym założeniu.
Ben Burns
1

Sugerowałbym również użycie sigaction () nad signal () i chciałbym dodać jeszcze jeden punkt. sigaction () daje więcej opcji, takich jak pid procesu, który umarł (możliwe przy użyciu struktury siginfo_t).

pradyumna kaushik
źródło
0

Użyłbym signal (), ponieważ jest bardziej przenośny, przynajmniej w teorii. Zagłosuję na każdego komentatora, który wymyśli nowoczesny system, który nie ma warstwy kompatybilności z POSIX i obsługuje funkcję signal ().

Cytat z dokumentacji GLIBC :

Możliwe jest użycie zarówno funkcji sygnału, jak i sigaction w jednym programie, ale musisz być ostrożny, ponieważ mogą one współdziałać w nieco dziwny sposób.

Funkcja sigaction określa więcej informacji niż funkcja signal, więc wartość zwracana z signal nie może wyrazić pełnego zakresu możliwości sigaction. Dlatego jeśli użyjesz signal do zapisania i późniejszego przywrócenia działania, może on nie być w stanie poprawnie przywrócić procedury obsługi, która została utworzona z sigaction.

Aby uniknąć problemów, zawsze używaj sigaction do zapisywania i przywracania procedury obsługi, jeśli twój program w ogóle używa sigaction. Ponieważ sigaction jest bardziej ogólny, może prawidłowo zapisać i przywrócić dowolną akcję, niezależnie od tego, czy została pierwotnie ustanowiona z sygnałem, czy z sigaction.

W niektórych systemach, jeśli utworzysz akcję z sygnałem, a następnie zbadasz ją z sigaction, otrzymany adres modułu obsługi może nie być taki sam, jak ten, który podałeś w signal. Może nawet nie nadawać się do użycia jako argument akcji z sygnałem. Ale możesz polegać na używaniu go jako argumentu do sigakcji. Ten problem nigdy nie występuje w systemie GNU.

Dlatego lepiej jest używać jednego lub drugiego mechanizmu konsekwentnie w ramach jednego programu.

Przenośność Uwaga: Podstawowa funkcja sygnału jest cechą ISO C, podczas gdy sigaction jest częścią standardu POSIX.1. Jeśli obawiasz się przenośności na systemy inne niż POSIX, powinieneś zamiast tego użyć funkcji signal.

Copyright (C) 1996-2008 Free Software Foundation, Inc.

Udziela się zgody na kopiowanie, rozpowszechnianie i / lub modyfikowanie tego dokumentu zgodnie z warunkami Licencji GNU Wolnej Dokumentacji w wersji 1.2 lub nowszej opublikowanej przez Free Software Foundation; bez sekcji niezmiennych, bez tekstów przedniej okładki i bez tekstu tylnej okładki. Kopia licencji znajduje się w sekcji zatytułowanej „Licencja GNU Wolnej Dokumentacji”.

bmdhacks
źródło
0

Ze strony man signal (7)

Sygnał skierowany na proces może być dostarczony do dowolnego z wątków, w których sygnał aktualnie nie jest zablokowany. Jeśli więcej niż jeden wątek ma odblokowany sygnał, to jądro wybiera dowolny wątek, do którego ma dostarczyć sygnał.

I powiedziałbym, że ten „problem” istnieje dla sygnału (2) i sigaction (2) . Uważaj więc na sygnały i wątki.

... a signal (2) wydaje się wywoływać sigaction (2) underneath w Linuksie z glibc.

robert.berger
źródło