background image

 

 

Obsługa sygnałów zgodna ze 
standardem Posix cz.2

background image

 

 

Blokowanie sygnałów

 

Może  się  zdarzyć,  że  będziemy  chcieli  poinformować  jądro  o 

tym, że nie chcemy żeby niektóre sygnały przekazywane  były 

bezpośrednio do danego procesu.

Możemy na przykład mieć w kodzie jakiś krytyczny fragment, 

którego  wykonanie  nie  powinno  zostać  przerwane  przez 

sygnały. 

Robi się to przez ustalenie tzw. maski sygnałów. 

Maska  sygnałów  jest  zbiorem  sygnałów,  tzn.  pewną  strukturą 

w której każdemu sygnałowi jest przypisany jeden bit (prawda 

lub fałsz). 

Struktura  taka  nosi  nazwę  sigset_t,  i  najczęściej  jest 

implementowana w postaci czterobajtowej liczby całkowitej.

background image

 

 

Funkcje obsługi struktury 
sigset_t
 

int sigemptyset(sigset_t *set); Inicjuje zbiór sygnałów set, 

wyłączając w nim wszystkie sygnały (czyli ustala wartość 

reprezentującej je liczby na 0). 

int sigfillset(sigset_t *set); Inicjuje zbiór sygnałów, włączając 

wszystkie sygnały. 

Przed użyciem struktury trzeba ją zawsze zainicjwać przez sigemptyset 

lub sigfillset. 

int sigaddset(sigset_t *set, int signo); Dodaje do zbioru sygnałów 

pojedynczy sygnał. 

int sigdelset(sigset_t *set, int signo); Usuwa ze zbioru sygnałów 

pojedynczy sygnał. 

int sigismember(sigset_t *set, int signo); Sprawdza czy podany 

sygnał jest włączony w danym zbiorze. 

background image

 

 

Funkcje obsługi struktury 
sigset_t

 

     

Kiedy jest już ustawiony  zbiór sygnałów powyższymi 

metodami, można użyć funkcji sigprocmask():

int sigprocmask(int how, const sigset_t *set, sigset_t 

*oset);

Pierwszy parametr wskazuje sposób uaktualnienia maski 

sygnałów: 

SIG_BLOCK - nowa maska będzie połączeniem starej maski i maski 

podanej w set (maska podana w set zawiera zbiór sygnałów, które 

chcemy zablokować) 

SIG_UNBLOCK - maska podana w set zawiera zbiór sygnałów, które 

chcemy odblokować 

SIG_SETMASK - stara maska jest nadpisywana maską zawartą w set 

Do parametru oset zostanie wpisana zawartość starej maski. 

background image

 

 

Funkcje obsługi struktury 
sigset_t

 

     

uwaga

Kiedy jądro próbuje przekazać do procesu sygnał, a ten 

aktualnie jest zablokowany (nie dotyczy sygnałów SIGKILL i 

SIGSTOP - tych nie można blokować), to sygnał jest 

przechowywany do czasu, kiedy proces albo ustawi akcję 

dla tego typu sygnału na ignorowanie, albo odblokuje dany 

sygnał (w tym przypadku sygnał jest mu przekazywany w 

momencie odblokowania).

background image

 

 

Funkcje obsługi struktury 
sigset_t

 

    

 

int sigpending(sigset_t *set);

Za pomocą tej funkcji można z wnętrza procesu odczytać 

listę sygnałów, które oczekują na odblokowanie przez niego. 

Do zmiennej set jest wpisywany zbiór oczekujących 

sygnałów, które można sprawdzić funkcją sigismember().

background image

 

 

Funkcja Sigaction

int sigaction(int signo, const struct sigaction *act, struct sigaction 

*oact);

Pierwszy argument, signum, to numer sygnału, którego nadejście spowoduje 

wywołanie procedury obsługi, określonej w drugim argumencie. Można podać 

dowolny numer, za wyjątkiem SIGKILL i SIGSTOP, ponieważ obsługi tych 

sygnałów nie da się przechwycić. 

Numeracja sygnałów nie musi być przenośna, dlatego zamiast wartości 

liczbowych zaleca się skorzystanie z symboli SIG* zdefiniowanych w 

<signal.h>.

Drugi i trzeci argument wywołania, to wskaźniki do struktury sigaction

określającej sposób obsługi danego sygnału. Drugi argument określa żądany 

nowy sposób obsługi, za pomocą trzeciego funkcja zwraca informację o 

dotychczasowym sposobie obsługi.

Wartość zwracana

Funkcja sigaction zwraca 0 z przypadku sukcesu i -1 w razie niepowodzenia. W tym 

drugim przypadku wartość errno ustawiana jest na jedną z: EINVAL, EFAULT albo EINTR.

background image

 

 

Funkcja Sigaction

    W obu przypadkach wolno podać wskaźnik NULL zamiast argumentu, np. w celu 

ustawienia nowej procedury obsługi sygnału SIGINT bez pobierania informacji o 

dotychczasowej należy użyć:

#include <signal.h>
struct sigaction sa_new;
/* ... */
sigaction(SIGINT, &sa_new, 0);

a w celu pobrania informacji o dotychczasowej obsłudze sygnału (np. w celu jej 

modyfikacji lub dla późniejszego odtworzenia) bez ustanawiania nowej:

#include <signal.h>
struct sigaction sa_old;
sigaction(SIGINT, 0, &sa_old);

Podanie obu wskaźników jako NULL jest również dozwolone (i sensowne), pozwala 

bowiem sprawdzić, czy dany numer sygnału jest wspierany w danym systemie.

background image

 

 

Funkcja Sigaction

     Funkcja sigaction posługuje się strukturą sigaction do pobierania i 

odsyłania informacji o handlerze obsługi sygnału. Struktura ta była w 

przeszłości kilkakrotnie zmieniana, jednak podstawowe składowe 

pozostały takie same.

struct sigaction
{

void (*sa_handler)();
sigset_t sa_mask;
int sa_flags;

};

void (*sa_handler)(int); 

W składowej sa_handler należy umieścić adres funkcji, która zostanie wywołana 

po nadejściu sygnału. Można również nadać jej jedną z dwóch wartości 

predefiniowanych: SIG_DFT, co powoduje przywrócenie domyślnego sposobu 

obsługi sygnału albo SIG_IGN, co spowoduje zignorowanie sygnału. 

background image

 

 

Funkcja Sigaction

void (*sa_sigaction)(int, siginfo_t *, void *); 
Zamiast sa_handler można użyć składowej sa_sigaction, podając 

równocześnie flagę SA_SIGINFO w składowej sa_flags. Wówczas po 

nadejściu sygnału do handlera obsługi zostaną przekazane dodatkowe 

informacje o przyczynach wystąpienia sygnału. Nie można używać 

obu tych składowych równocześnie (w niektórych implementacjach 

mogą być one zadeklarowane jako unia). 

sigset_t sa_mask; 
Składowa sa_mask zawiera informacje o sygnałach, których obsługa 

zostanie na czas wykonania handlera wstrzymana (wskazane sygnały 

będą blokowane). Do manipulacji tą składową służą osobne funkcje. 

int sa_flags; 
Składowa sa_flags zawiera dodatkowe flagi. Flagi te w większości są 

specyficzne dla danego systemu operacyjnego, a standardowe to: 

SA_NOCLDSTOP (zdefiniowana w POSIX) oraz wspomniana już 

SA_SIGINFO (dodana w POSIX.1b). 

background image

 

 

Funkcja Sigaction

Składowa sa_mask

Składowa ta zawiera zestaw sygnałów, których obsługa będzie 

zawieszona na czas wykonywania przez proces procedury obsługi 

sygnału. Do manipulacji zestawami sygnałów służą funkcje:

int sigemptyset(sigset_t *set); 

    Wypełnia set tak, by wszystkie sygnały były z niego wyłączone. 

int sigfillset(sigset_t *set); 

   

Wypełnia set tak, by wszystkie sygnały były w nim zawarte. 

int sigaddset(sigset_t *set, int signum); 

   Dodaje sygnał signum do zestawu. 

int sigdelset(sigset_t *set, int signum); 

   Usuwa sygnał signum z zestawu. 

background image

 

 

Funkcja Sigaction

Włączenie blokowania wszystkich sygnałów (zazwyczaj tego właśnie 

chcemy):

#include <signal.h>
struct sigaction sa;
sigfillset(&(sa.sa_mask));
/* ... */
sigaction(SIGINT, &sa_new, 0);

Włączenie blokowania wszystkich sygnałów oprócz SIGTSTP:

#include <signal.h>
struct sigaction sa;
sigfillset(&(sa.sa_mask));

/* pełny zestaw sygnałów */

sigdelset(&(sa.sa_mask), SIGTSTP);

/* wyklucz sygnał z zestawu */

/* ... */
sigaction(SIGINT, &sa_new, 0);

background image

 

 

Funkcja Sigaction

Włączenie blokowania tylko SIGTSTP i SIGALRM:

#include <signal.h>
struct sigaction sa;
sigemptyset(&(sa.sa_mask));

/* pusty zestaw sygnałów */

sigaddset(&(sa.sa_mask), SIGTSTP);

/* dodaj SIGTSTP */

sigaddset(&(sa.sa_mask), SIGALRM);

/* dodaj SIGALRM */

/* ... */
sigaction(SIGINT, &sa_new, 0);Włączenie blokowania wszystkich sygnałów 

oprócz SIGTSTP:

Należy podkreslić , że sygnał, który przechwytujemy, jest „z urzędu” blokowany na 

czas obsługi.

background image

 

 

Funkcja Sigaction

Składowa sa_flags

W pełni standardowa i przenośna jest tylko flaga SA_NOCLDSTOP. 

Działanie kilku wybranych flag, które wydają się przydatne:

SA_ONESHOT lub SA_RESETHAND 

   Przywraca domyślną obsługę sygnałów po wywołaniu procedury obsługi. Historycznie 

rzecz biorąc, było to typowe zachowanie systemów UNIX po zainstalowaniu handlera 
za pomocą funkcji signal i źródło nieustannych kłopotów.

SA_RESTART 
Powoduje zmianę zachowania przerwanych nadejściem sygnału niektórych wywołań 
funkcji systemowych: zamiast zwracać błąd i ustawiać errno na wartość EINTR, 
funkcje te będą wywołane ponownie. Praktyczne (w sposób typowy dla BSD, skąd 
zaczerpnięto koncept) rozwiązanie dla takich operacji jak np. read. 

SA_NOMASK lub SA_NODEFER 
Nie blokuj sygnałów podczas działania handlera (jak przy signal). 

SA_SIGINFO 
Użyj adresu podanego w składowej sa_sigaction, a nie sa_handler do zainstalowania 
handlera. 

background image

 

 

Funkcja Sigaction

Składowa sa_flags powinna być sumą bitową ustawionych flag. Jeśli 
nie ustawiamy żadnej, sa_flags trzeba koniecznie wyzerować:

#include <signal.h>
struct sigaction sa;
sigfillset(&(sa.sa_mask));
sa.sa_flags = 0;
/* ... */
sigaction(SIGINT, &sa_new, 0);

background image

 

 

Przykłady cz.1

obsługa sygnału SIGINT

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void int_handler(int signum)
{
  fprintf(stderr, "-- Signal %i catched\n", signum);
}
int main(void)
{
  int i;
  struct sigaction sa;
  sa.sa_handler = int_handler;
  sigfillset(&(sa.sa_mask));
  sa.sa_flags = 0;
  sigaction(SIGINT, &sa, 0);
  for(i = 0; i < 20; ++i) {
    printf("Running (%i)\n", i);
    sleep(1);
  }

  return 0;
}

background image

 

 

Przykłady cz.1

sa.sa_handler = int_handler;
ustala nazwę (w istocie adres) funkcji wywoływanej, gdy przyjdzie 
sygnał. Wywołanie

sigfillset(&(sa.sa_mask));
ustawia blokowanie wszystkich sygnałów na czas wykonywania 
handlera. Następnie zerowane są flagi:

sa.sa_flags = 0;
i handler zostaje zainstalowany:

sigaction(SIGINT, &sa, 0);
Program powyższy po uruchomieniu co sekundę wyświetla komunikat 
Running[…]. Po wysłaniu SIGINT (zazwyczaj jest to kombinacja 
klawiszy Ctrl+C) sterowanie trafia do funkcji obsługi sygnału, która 
wypisuje na stderr komunikat -- Signal 2 catched 
W tym przykładzie raz zainstalowany handler działa aż do zakończenia 
procesu, co łatwo sprawdzić w praktyce, naciskając kilkakrotnie 
Ctrl+C.

background image

 

 

Przykłady cz.2

flaga SA_ONESHOT

W poprzednim przykładzie wprowadzono jedną tylko, drobną zmianę.
Instrukcję:

  sa.sa_flags = 0;

zastępiono przez:

  sa.sa_flags = SA_ONESHOT;

Tak zainstalowany handler obsługi sygnału zadziała jeden raz, po 
czym zostanie przywrócona obsługa domyślna. W taki sposób działały 
handlery sygnałów instalowane za pomocą funkcji signal 

background image

 

 

Przykłady cz.3

przywracanie obsługi

Do przykładu cz. 2 wprowadzamy kolejną zmianę: w funkcji 
int_handler będziemy przywracali obsługę sygnału za pomocą 
handlera. 

Struktura sigaction zostanie zmienną globalną, żeby handler mógł 
skorzystać z wartości ustawionych w funkcji main:

background image

 

 

Przykłady cz.3

#include <signal.h>

#include <stdio.h>

#include <unistd.h>

struct sigaction sa;

void int_handler(int signum)

{

  sigaction(SIGINT, &sa, 0);

  fprintf(stderr, "-- Signal %i catched\n", signum);

}

int main(void)

{

  int i;

  sa.sa_handler = int_handler;

  sigfillset(&(sa.sa_mask));

  sa.sa_flags = SA_ONESHOT;

  sigaction(SIGINT, &sa, 0);

  for(i = 0; i < 20; ++i) {

    printf("Running (%i)\n", i);

    sleep(1);

  }

  return 0;

}

background image

 

 

Przykłady cz.3

Kod ten jest podobny do tego, który należało dawniej zastosować, by 
uzyskać  wielokrotne  użycie  handlera  zainstalowanego  za  pomocą 
funkcji  signal.  Jest  to  jednak  rozwiązanie  niepewne:  pomiędzy 
wywołaniem handlera i przywróceniem obsługi sygnału istnieje krótki 
odcinek czasu, kiedy sygnał obsługiwany jest w sposób domyślny.
 

background image

 

 

Przykłady cz.4

funkcja raise

Funkcja  raise,  to  po  prostu  okrojona  wersja  funkcji  kill,  wysyłająca 
sygnał  do  tego  samego  procesu,  który  ją  uruchomił.  Po  co  wysyłać 
sygnał do samego siebie? 

Załóżmy,  że  program  otrzymujący  sygnał  przerwania  SIGINT  musi 
najpierw  posprzątać,  na  przykład  usunąć  utworzone  pliki 
tymczasowe. W takim przypadku najlepiej przechwycić sygnał, zrobić 
porządki,  a  następnie  (przy  przywróconej  domyślnej  obsłudze 
sygnału) ponownie wysłać go do siebie.

background image

 

 

Przykłady cz.4

#include <signal.h>

#include <stdio.h>

#include <unistd.h>

void int_handler(int signum)

{

  fprintf(stderr, "Final cleanup.\n");

  raise(SIGINT);

  fprintf(stderr, "Preparing to die...\n");

}

int main(void)

{

  int i;

  struct sigaction sa;

  sa.sa_handler = int_handler;

  sigfillset(&(sa.sa_mask));

  sa.sa_flags = SA_ONESHOT;

  sigaction(SIGINT, &sa, 0);

  for(i = 0; i < 20; ++i) {

    printf("Running (%i)\n", i);

    sleep(1);

  }

  return 0;

}

background image

 

 

Funkcja sigsuspend

 

int sigsuspend(const sigset_t *sigmask);

Funkcja ta działa następująco: podmienia tymczasowo maskę 
sygnałów procesu na tą wskazaną w sigmask, a następnie 
wstrzymuje wykonanie programu do czasu otrzymania 
jakiegoś sygnału. Przydaje się to np. w sytuacji kiedy 
chcielibyśmy wykonać funkcję pause() aby zaczekać na jakiś 
sygnał, a jest on w danej chwili zablokowany.

background image

 

 

Uwagi dotyczące własnej obsługi 
sygnałów

Dziedziczenie obsługi sygnałów

Proces potomny, utworzony za pomocą exec dziedziczy obsługę sygnałów, 
wszelako z pewnymi zmianami:

Obsługa sygnałów SIG_DFL i SIG_IGN dziedziczona jest bez zmian, 

Obsługa sygnałów, dla których zainstalowane zostały handlery zmieniana jest 
na SIG_DFL. Jest to konieczne, ponieważ po wykonaniu exec handlerów już 
nie ma w pamięci. 

Ograniczenia procedur obsługi sygnałów

W procedurze obsługującej sygnały nie wolno używać funkcji bibliotecznych, 
które zmieniają wartości zmiennych globalnych lub statycznych. Ogólnie, 
pamięci innej, niż na stosie. Zatem funkcji oznaczonych w angielskojęzycznej 
dokumentacji jako non-reentrant należy używać z rozwagą.

Ze zmienną errno akurat łatwo sobie poradzić, zachowując jej wartość przed 
wywołaniem funkcji bibliotecznej, a następnie ją odtwarzając. Ogólnie, jeśli 
jakaś funkcja zmienia zmienną globalną, do której mamy dostęp, można 
zapobiec kłopotom, tworząc kopię tej zmiennej w celu odtworzenia wartości 
po wywołaniu.

background image

 

 

Uwagi dotyczące własnej obsługi 
sygnałów

Samo tylko odczytywanie wartości zmiennych globalnych programu 

jest bezpieczne, jednak jeśli taka zmienna nie jest atomic, można 

trafić z odczytem akurat na moment jej modyfikacji i otrzymać 

niepoprawną wartość.

Natomiast użycie takich funkcji jak malloc, które utrzymują gdzieś 

własne dane, nie jest na ogół bezpieczne. Z malloc mogą z kolei 

korzystać niejawnie rozmaite funkcje biblioteczne (jak choćby użyta 

w przykładach funkcja printf).

Podobnie, korzystanie w handlerze sygnałów z tych samych 

deskryptorów plików, których używa główny program nie jest 

wskazane. (W przykładach częściowo zapobiegłem kłopotom, 

używając stderr w obsłudze sygnałów i stdout w programie głównym, 

jednak skierowania strumieni łatwo mogą zostać zmienione z 

zewnątrz.)

I tak dalej. Sprawdź w dokumentacji, które funkcje biblioteczne są 

reentrant. Sprawdź też napisane przez siebie funkcje pod tym kątem.

Z drugiej strony, jeśli program główny nie używa w ogóle jakiejś 

funkcji (albo używa jej wyłącznie w sytucjach, gdy sygnały sa 

ignorowane), można jej użyć w procedurze obsługi sygnałów 

background image

 

 

Uwagi dotyczące własnej obsługi 
sygnałów

Ogólne zasady tworzenia handlerów

Nie są to niepodważalne prawa, a bardziej zalecenia, którymi należy się 

kierować:

O ile to możliwe, procedura obsługi sygnału powinna tylko ustawiać flagę 

informującą o nadejściu sygnału i powracać, a program główny powinien 

okresowo sprawdzać stan tej flagi. Zmienna pełniąca funkcję flagi 

powinna mieć atrybut volatile. 

Jeśli trzeba wywołać jakąś funkcję, powinna to być funkcja reentrant. W 

przeciwnym przypadku trzeba zapewnić, że wykonanie tej funkcji w 

programie głównym nie może zostać przerwane nadejściem sygnału. 

Wykonanie procedury obsługi sygnału powinno trwać krótko, ponieważ 

zazwyczaj na ten czas obsługa nadchodzących sygnałów jest 

zawieszona. 


Document Outline