Signály jsou přerušení generována softwarem, která jsou zaslána procesu, pokud nastane nějaká událost

Unix systémy používají signály, aby daly vědět procesu, že nastala určitá událost (proces je pak např. vzbuzen, přerušen, …). Oznamují se jen čísla signálu, žádné parametry.

Signál vs. přerušení

[link]

Na přerušení jde pohlížet jako na prostředek komunikace mezi procesorem a jádrem OS

Signály mohou být brány jako prostředek komunikace mezi jádrem OS a procesy

Mohou být započaty jádrem OS (SIGSEGV, SIGIO) nebo procesem (kill())

Jsou nakonec spravovány jádrem OS, které je doručí do cílového procesu/vlákna a spustí buď nějakou obecnou akci (ignorovat, ukončit, ukončit + dump core) a nebo obsluhu signálu, kterou poskytl proces

Signály mohou být buď synchronní nebo asynchronní, záleží na zdroji a důvodu, proč byla událost signalizována. 

Mezi synchronní signály patří např. neoprávněný přístup do paměti a dělení nulou. Pokud běžící program provede některou z těchto akcí, vygeneruje se signál. Synchronní signály jsou doručeny stejnému procesu, který provedl operaci, která ten signál způsobila (což je důvod, proč jsou považovány za synchronní).

Když je signál generován událostí, která je externí vzhledem k běžícímu procesu, tak ten proces přijme ten signál asynchronně. Příklady takových signálů jsou ukončení procesu pomocí konkrétní klávesové kombinace (třeba <control><C>) a vypršení časovače. Asynchronní signál je typicky zaslaný jinému procesu.

Jakmile byl signál generován výskytem nějaké události (např. dělením nulou, neoprávněným přístupem do paměti, uživatel stisknul CTRL+C = SIGINT signál), signál je dopraven procesu, kde musí být zpracován. Proces, který přijme signál, jej může zpracovat různými způsoby:

Ignorování signálu (kromě SIGKILL a SIGSTOP signálů)

Použití defaultního (výchozího) signal handleru (obsluhy signálu/funkce na zpracování signálu dle Košičana) 

Poskytnutí vlastní signal-handling funkce = vlastní obsluha signálu (kromě SIGKILL a SIGSTOP).

Každý signál má svou výchozí obsluhu signálu (default signal handler), která běží v jádře, když je signál zpracováván (???that is run by the kernel when handling that signal). Tato výchozí akce může být přepsána uživatelem definovanou obsluhou signálu, který je volán pro obsluhu daného signálu. Signály mohou být obslouženy různými způsoby. Některé signály (jako změna velikosti okna) mohou být jednoduše ignorovány; jiné (jako třeba neoprávněný přístup do paměti) mohou být obslouženy ukončením programu.

Signály mohou být obslouženy nastavením určitých proměnných v C struktuře struct sigaction a pak předáním této struktury sigaction() funkci. Signály jsou definovány v include souboru /usr/include/sys/signal.h. Např. signál SIGINT reprezentuje signál pro ukončení programu pomocí <Control> <C>. Výchozí obsluha signálu (signal handler) pro SIGINT je ukončit program.

Další možností je, že v programu může být nastavena vlastní funkce pro obsluhu signálu, a to nastavením sa_handler proměnné ve struktuře struct sigaction na název funkce, která obslouží ten signál, a pak zavoláním funkce sigaction(). Té se předají jako parametry (1) signál, pro který nastavujeme obsluhu, a (2) pointer na struct sigaction.

Fáze signálů

Všechny signály, ať už synchronní nebo asynchronní, mají stejný životní cyklus:

Signál je vygenerován a odeslán poté, co nastane nějaká událost.

PM (Process manager) nejprve zjistí, které procesy mají obdržet signál

V tabulce procesů je pro každý proces několik sigset_t proměnných (=bitmapy, definují ignorované, zachycované signály) 

2) Jádro přeruší standardní provádění posloupnosti instrukcí cílového procesu, uloží informace o stavu procesu, aby se pak mohl opět pokračovat v běhu. Informace jsou uloženy na zásobníku toho procesu (kterému má dorazit signál) + kontrola, že je dost místa na zásobníku (dělá PM). 

PM pak volá system task in jádře pro uložení informací do zásobníku. System task také manipuluje s program counterem procesu, aby proces mohl být spuště v kódu obsluhy. 

Vygenerovaný signál je doručen – přijat - procesu.

Jakmile je signál doručen, musí být zpracován.

Když obsluha skončí, je provedeno systémové volání sigreturn. Prostřednictvím tohoto volání se PM i jádro podílejí na obnově kontextu signálu a registrů „signalizovaného“ procesu, aby mohl pokračovat v normálním běhu. 

Pokud není signál zachycen (nemá def. handler), podnikne se defaultní akce, která se může týkat volání souborového systému pro vytvoření core dumpu (zápis obrazu paměti procesu do souboru, který může být prozkoumán debuggerem) či zabití procesu, pro něž je třeba zapojit PM, souborový systém a jádro. 

PM řídí jednu nebo více opakování akcí výše - podle toho, jestli je signál doručen jednomu procesu nebo skupině procesů.

Fáze vypořádání se signály jinak:

Příprava (Preparation) – kód programu se připraví pro možný signál

Několik systémových volání, která lze nastavit jako odpověď na signál

sigaction à co má proces dělat se signálem: ignorovat/zachytit/nastavit defaultní reakci

sigprocmask() à blokování signálu; signál bude zařazen do fronty či se jím bude řídit až jej process později odblokuje

sigsuspend(sigmask) à nastaví se blokované signály podle sigmask a process přejde do stavu čekající až do zaslání signálu, který není blokován/ignorován. Dle  [Košičan, prednaska07, slide 13] není to samý, co sigprocmask() a sleep(), ???nechápu

Odpověď (Response) – signál je přijat a příslušná akce je vykonána

Vyčištění (Cleanup) – obnova normální operace procesu

Viz bod 3 předchozího rozfázování

Příklady scénářů synchronního a asynchronního signálu

výjimka (dělení nulou, nedovolená instrukce,...) způsobí přechod do módu jádro

jádro vykoná její obsluhu a zašle se odpovídající signál běžícímu procesu

při návratu z obsluhy proces najde signál

uživatel stiskne CTRL-C

generuje se přerušení (jako u každého stisknutí klávesy)

ovladač rozpozná, že jde o kombinaci generující signál, a odešle signál SIGINT procesu v popředí

když je proces naplánován jako běžící při návratu do uživatelského módu anebo byl-li běžící při návratu z přerušení, proces najde signál

Obsluha signálů v jednovláknových programech je přímočará; signály jsou vždy doručeny procesu. Doručení signálů je však komplikovanější u vícevláknových programů, kde proces může mít několik vláken. Kam se má potom signál doručit? 

Obecně existují následující možnosti: 

Doručit signál vláknu, ke kterému se signál vztahuje.

Doručit signál každému vláknu v procesu.

Doručit signál určitým vláknům v procesu.

Pověřit jedno konkrétní vlákno, aby přijímalo všechny signály pro proces.

Metoda pro doručení signálu záleží na typu generovaného signálu. Například synchronní signály je třeba doručit vláknu, které ten signál způsobilo a už ne ostatním vláknům procesu. U asynchronních signálů to ale není tak jasný. Některé asynchronní signály, třeba signál, který ukončuje proces (např. <control><C>), by měly být poslány všem vláknům.

Většina vícevláknových verzí UNIXu umožňuje vláknu určit, které signály bude přijímat a které blokovat. V některých případech proto může být asynchronní signál doručen pouze těm vláknům, která jej neblokují. Protože však signály musí být obslouženy pouze jednou, signál je obvykle doručen prvnímu nalezenému vláknu, které jej neblokuje. 

Standardní UNIXová funkce pro doručení signálu je kill (aid_t aid, int signal); uvádíme zde proces (aid), kterému bude příslušný signál doručen. POSIX Pthreads taky ještě poskytují funkci pthread_kill(pthread_t tid, int signal, která umožňuje doručit signál konkrétnímu vláknu (tid.)

Ačkoliv Windows neposkytuje přímo podporu signálů, mohou být emulovány pomocí asynchronních volání procedur - asynchronous procedure calls (APCs). APC umožňuje uživatelskému vláknu (user thread) uvést funkci, která má být zavolána, když tomu user threadu přijde oznámení o určité události. Jak už název napovídá, APC je zhruba to samý co asynchronní signál v UNIXu. Zatímco se však UNIX musí potýkat s pořešením signálů ve vícevláknovém prostředí, možnost APC je přímočařejší, protože APC je doručeno konkrétnímu vláknu a ne procesu.

Příklady signálů

SIGINT – signál pro přerušení od terminálu (stisknutím Ctrl+C)

SIGQUIT – ukončí proces + core dump (záznam stavu pracovní paměti procesu do souboru, často při abnormálním ukončení)

SIGKILL – zabije proces, nemůže být zachycen ani ignorován + proces nemůže po jeho přijetí provést úklid

Proč může dojít ke zpoždění vyřízení signálu?

signál je nevyřízen (pending), byl-li odeslán, ale nebyl přijat

jenom jeden signál každého typu může být nevyřízen

Reakci na signál vykonává proces, kterému je signál zaslán, včetně ukončení procesu. To znamená, že musí být aspoň plánován stát se běžícím

Má-li nízkou prioritu, může mezi odesláním signálu a jeho přijetím, kdy se vykoná odpovídající akce, uplynout dosti dlouhá doba

Další prodlení může způsobit, je-li proces v čase odeslání signálu ve stavu zastaven nebo  spící 

Co se má stát, když je odeslán signál spícímu procesu? 

činnost jádra záleží na tom, proč proces přešel do stavu spící

čeká-li na událost, která zakrátko nastane, např. dokončení diskové V/V operace, je spící v kategorii nepřerušitelný a signál je pouze zaznačen jako nevyřízen

čeká-li na událost, o které nevíme kdy nastane nebo dokonce nemusí nastat vůbec, např. skončení potomka, vstup z terminálu, je spící v kategorii přerušitelný, je jádrem vzbuzen a přejde do stavu připraven

Linux nemá stav spící, ale stavy úloha_přerušitelná (TASK_INTERRUPTIBLE) a úloha_nepřerušitelná (TASK_UNINTERRUPTIBLE) 

Nespolehlivé = můžou se ztratit; proces někdy zachytí signál, jindy ho ztratí; kvůli resetu signálu na implicitní akci.

Neumožňují ignorovat signál v daný moment, ale pamatovat si, že nastal, aby jej šlo blokovat a zpracovat později (třeba po skončení důležitého výpočtu).

Pokud to chceme emulovat (nastavit nějakou akci pro následující signál), musíme ji opět instalovat (= volat funkci signal(sig, function)) - vznik nedeterminismu, někdy se to stihne, někdy ne

Spolehlivé = perzistentní obslužné funkce signálů; blokování signálu, např. při obsluze signálů → nevznikne hnízdění

Další zdroje

Pěkně vysvětleno na [link]
