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

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


Wyszukiwarka

Podobne podstrony:
Obsługa sygnałów zgodna ze standardem Posix
Pytania dodatkowe na zajęcia laboratoryjne z KSPD, Obsługa przyrządów pomiarowych ze standardem SCPI
Zmiany systemu oceny betonowej kostki brukowej zgodnie ze standardami europejskimi
10 Programowa obsługa sygnałów analogowych materiały wykładowe
10 Montaż i demontaż uchwytów w podsufitówce ze standardu na GTI
Skrypt - Obsługa przyrządów pomiarowych z wykorzystaniem standardu SCPI, Nauka i Technika, Automatyk
świadczenia w ciąży ze standardu
mie sciaga na egz ze standardowych zadan, Mechatronika, 2 Rok
CSS i Ajax Strony WWW zgodne ze standardami sieciowymi W3C cssaww
test ze znajomości Dziadów cz III
Zmiany systemu oceny betonowej kostki brukowej zgodnie ze standardami europejskimi
10 Programowa obsługa sygnałów analogowych materiały wykładowe

więcej podobnych podstron