Principy počítačů a operačních systémů Operační systém procesy a vlákna plánování interakce a synchronizace
Základní pojmy proces vykonávaný program vlákno (thread) oddělení místa vykonávání instrukcí od adresového prostoru
Procesy, vlákna proces spuštěný program adresový prostor, prostředky, práva, signály kontext vlákno / Thread místo v procesu, kde se vykonává program IC, registry CPU, zásobník
Podpora vláken v OS původně jen procesy spouštění procesů je drahé obtížnější komunikace zavedení vláken na úrovni userspace (knihovny) v jádře hybridní
Podpora vláken v OS Linux nezná vlákna procesy shodou okolností sdílí jisté prostředky speciální vlákna v jádře Windows, Solaris, explicitní podpora pro vlákna
Stavy procesu připraven naplánován vznik blokován běží moc dlouho běží
Plánování entita plánování (proces / vlákno) přidělování CPU entitám plánovač cíle plánování kritéria plánování
Cíle plánování Spravedlnost Efektivita Doba reakce Průchodnost Co nejnižší režie
Kritéria plánování Vázanost procesu na prostředky (CPU, I/O) Typ procesu (dávkový, interaktivní) Priorita Skutečný čas CPU Výpadky stránek / segmentů
Základní kategorie nepreemptivní plánování proces běží, dokud uzná za vhodné starší a menší OS (Win3.1, PalmOS) preemptivní plánování OS má kontrolu a je schopen vynutit si odebrání CPU větší OS (WinNT, Unix)
Plánovací algoritmy FIFO (FCFS) kdo dřív přijde, ten dřív mele Připravené procesy Dokončeno č C B A CPU
Plánovací algoritmy Round Robin proces může být přeplánován Připravené procesy Dokončeno A C B A CPU Preempce
Plánovací algoritmy více front se zpětnou vazbou Úroveň 1 (FIFO) C B A CPU Dokončeno P Úroveň ň 2 (FIFO) E D B P Úroveň ň n (RR) F B P
Real-time systémy Real-time high performance computing! Důraz na limity (výkonové, časové) dokazatelnost správnosti Hard / soft real time Plánování je problém
Interakce mezi procesy Přístup ke sdíleným prostředkům data, jednotky i v rámci jednoho procesu Komunikace paralelní / distribuované aplikace mezi vlákny, mezi procesy Zablokování na sdílených jednotkách
Implicitní sdílení dat synchronní poskytování služeb procesům procesy volají služby jádra asynchronní reakce na vnější přerušení např. vstup dat z klávesnice modifikace dat v reakci na události v kontextu přerušení nebo systémového volání reentrantnost podpora souběžného zpracování více událostí
Explicitní sdílení dat souběžný přístup k datům např. v rámci různých vláken procesu důsledky chyb obvykle méně fatální
Problémy přístupu ke sdíleným prostředkům / datům Operace nejsou atomické Při souběžném přístupu může dojít k přerušení nebo přeplánování v nekonzistentním stavu Výsledek může záviset na pořadí provádění operací
Základní pojmy Race condition ( chyba souběhu ) výsledek operace závisí na konkrétním plánování Critical section ( kritická sekce ) část programu, kde se provádí kritická operace Mutual exclusion ( vzájemné vyloučení ) kritickou operaci provádí nejvýše jeden proces
Jednoduchý příklad Přidání do tiskové fronty: TAIL = TAIL + 1 QUEUE[TAIL] = filename Therac-25, 2003 Blackout
Podmínky 1. Žádné dva procesy nemohou být najednou v jejich kritické sekci 2. Nemohou být kladeny žádné předpoklady o rychlosti nebo počtu CPU 3. Žádný proces mimo kritickou sekci nesmí blokovat jiný proces 4. Žádný proces nesmí čekat nekonečně dlouho v kritické sekci
Metody dosažení vzájemného vyloučení Aktivní čekání spotřebovává čas procesoru nepotřebovává prostředky OS rychlejší vhodnější pro krátké předpokládané p čekání Pasivní čekání / blokování proces čeká ve stavu blokován spotřebovává prostředky OS pomalejší vhodnější pro delší předpokládané čekání
Zakázání přerušení Atomicita akce se zajistí zakázáním přerušení před akcí a jeho povolením po skončení disable_interrupt(); kritická_sekce(); enable_interrupt(); nefunguje!
Zámky Přidání sdílené proměnné pro řízení přístupu ke kritické sekci while (locked); locked = 1; kritická_sekce(); locked = 0; nefunguje!
Důsledné střídání int turn = 0; void p1(void) void p2(void) { { for (;;) { for (;;) { while (turn!= 0); while (turn!= 1); kritická_sekce(); kritická_sekce(); turn = 1; turn = 0; nekritická_sekce(); nekritická_sekce(); } } } }... celkem funguje, ale může porušit pravidlo 3
Petersonovo řešení (pro 2) #define N 2 /* počet procesů */ int turn; int interested[n]; /* kdo má zájem */ void enter_region(int proc) { /* proc: kdo vstupuje */ int other; other = 1-proc; /* číslo opačného procesu */ interested[proc] = TRUE; /* mám zájem o vstup */ turn = proc; /* nastav příznak */ while (turn == proc && interested[other] == TRUE); } void leave_region(int proc) { /* proc: kdo vystupuje */ interested[proc] = FALSE; /* už odcházím */ }... funguje, ale pro víc se nepoužívá
spin-lock / instrukce TSL Nutná podpora HW Atomicky test and set lock enter_region: tsl R,lock cmp R,#0 jnz enter_region ret leave_region: ; načti zámek do registru R a ; nastav zámek na 1 ; byl zámek nulový? ; byl-li nenulový, znova ; návrat k volajícímu - vstup do ; kritické sekce mov lock,#0 ; ulož do zámku 0 ret ; návrat k volajícímu
spin-lock / instrukce TSL enter_region: load R, lock cmp R, #0 jnz spin_lock tsl R,lock cmp R,#0 jnz enter_region ret leave_region: ; načti zámek do registru R a ; nastav zámek na 1 ; byl zámek nulový? ; byl-li nenulový, znova ; návrat k volajícímu - vstup do ; kritické sekce mov lock,#0 ; ulož do zámku 0 ret ; návrat k volajícímu
Priority inversion problem Proces H s vysokou prioritou, L s nízkou L v kritické sekci jakmile H může běžet, je přeplánováno H běží a aktivně čeká, L je ve stavu ready L nikdy nebude moci běžet a tudíž kritickou sekci nikdy neuvolní a H bude čekat do nekonečna.
Zakázání přerušení Aktivní čekání vhodné éleda pro jádro OS Zámky nefungují!!! Důsledné střídání porušuje podmínku 3 Petersonovo řešení Instrukce TSL spin-lock
Pasivní čekání (spin-lock je při zamknutém zámku aktivní) SLEEP / WAKEUP Semafory Monitory Zprávy
SLEEP / WAKEUP Služby OS: SLEEP uspí proces, který ho zavolá WAKEUP probudí uspaný proces nefunguje! test a uspání musí být atomické k frontě je potřeba zámek, který se při SLEEP uvolní
Klasické synchronizační problémy Producent konzument Obědvající filozofové Ospalý holič
Problém producent-konzument Producent (továrna) produkuje předměty. Konzument (obchod) je spotřebovává (prodává). Mezi nimi je sklad pevné velikosti. Konzument nemá co prodávat, když je sklad prázdný, producent přestane vyrábět, když je sklad plný.
#define N 100 int count = 0; Producent/konzument se SLEEP/WAKEUP void producer(void) { int item; void consumer(void) { int item; for (;;) { for (;;) { produce_item(&item); if(count==0) if(count==n) sleep(); sleep(); remove_item(&item); enter_item(item); count--; count++; if(count==n-1) if(count==1) wakeup(producer); wakeup(consumer); consume_item(item); } }... kde je problém? } }
Semafory implementovány OS čítač a fronta uspaných procesů atomické operace DOWN/UP dvě sémantiky chování podle možných hodnot čítače 1. čítač >= 0 2. čítač v rozsahu celých čísel (i záporný) binární semafor (pouze hodnoty 0 a 1)
DOWN Sémantika semaforů -1 pokud dje čítač č > 0, sníží čítač č o 1 a pokračuje č dál pokud dje čítač č = 0, operace DOWN se zablokuje a proces je přidán do fronty čekající na tomto semaforu UP pokud je fronta neprázdná, vybere libovolný proces a ten probudí za DOWN jinak zvětší čítač o 1
DOWN Sémantika semaforů -2 vždy se sníží hodnota čítače č o 1 pokud je čítač >= 0, pokračuje dál jinak se zablokuje UP vždy se zvětší hodnota čítače o 1 pokud je hodnota <= 0 a fronta je neprázdná, vybere libovolný proces a ten odblokuje
Producent/konzument se semafory #define N 100 semaphore mutex = 1; semaphore empty = N; semaphore full = 0; void producer(void) { int item; void consumer(void) { int item; } for(;;) { for(;;) { produce_item(&item); down(&full); down(&empty); down(&mutex); down(&mutex); remove_item(&item); enter_item(item); up(&mutex); up(&mutex); up(&empty); up(&full); consume_item(item); } } }
Monitory implementovány překladačem lze si představit jako třídu C++ všechny proměnné privátní funkce mohou být i veřejné vzájemné vyloučení v jedné instanci zajištěno synchronizací na vstupu a výstupu do/z veřejných funkcí synchronizace implementována blokovacím primitivem OS
Podmíněné proměnné monitoru - 1 pouze uvnitř monitoru slouží k zablokování uvnitř monitoru není to čítač (ale fronta zablokovaných procesů) operace WAIT/SIGNAL podobné jako SLEEP/WAKEUP
Podmíněné proměnné monitoru - 2 WAIT zablokuje proces uvnitř monitoru a umožní vniknout dovnitř jinému procesu SIGNAL vybere libovolný z čekajících procesů a ten probudí problém s probuzením spustit probuzený a druhý uspat probouzející okamžitě opustí monitor
Zprávy zpráva je množina nějakých dat a informací přenášených mezi odesílatelem a příjemcem i zpráva nulové délky nese informaci implementováno OS atomické operace SEND a RECEIVE založeno na principu klient/server vhodné pro distribuované OS
SEND Zprávy - operace odešle dšl zprávu nezablokuje se pokud na zprávu čeká příjemce operací RECEIVE, zprávu obdrží a odblokuje se RECEIVE přijímá zprávu pokud není žádná zpráva dostupná, zablokuje se
Zprávy - adresace pomocí identifikace procesu je možná pouze komunikace mezi idvěma ě procesy schránky há (mailbox) mají svojí velikost a identifikaci operace pracují s identifikací mailboxů pevná velikost modifikuje funkci SEND možností zablokování při plné schránce více procesů spolupracuje p na jedné schránce
Zprávy dostaveníčko (randezvous) schránka nulové velikosti odesílatel čeká na příjemce nebo příjemce čeká na odesílatele po předání zprávy jsou oba procesy najednou odblokovány
Ekvivalence primitiv pomocí jednoho blokovacího primitiva lze implementovat jiné blokovací primitivum, například: implementace monitoru semaforem implementace semaforu zprávami (třetí proces server semaforů)
Problém obědvajících filosofů Pět filosofů sedí okolo kulatého stolu. Každý filosof má před sebou talíř špaget a vedle něj jednu vidličku. Špagety jsou ale bohužel slizké, takže je třeba je jíst dvěma vidličkami. Život filosofa sestává z období jídla a období přemýšlení. Když dostane hlad, pokusí se vzít dvě vidličky, když se mu to podaří, nají se a vidličky odloží.
Obědvající filosofové - 2 #define N 5 void phil (int i) { for(;;) { think(); take_fork(i); take_fork((i+1)%n); eat(); put_fork(i); put_fork((i+1)%n); } }
Obědvající filosofové - 3 funkce take_fork je blokovací všichni i najednou zvednou svoji levou a čekají kjína pravou ikd když nahradíme dvojici itake_fork jednou funkcí take_forks, musí být opatrná pokud nemohu sebrat druhou vidličku, položím první všichni zvednou levou, podívají se doprava, položí levou, atd. filosofové pracují ale nenají se vyhladovění (starvation)
Problém ospalého holiče Holič má ve své oficíně křeslo na holení zákazníka a pevný počet sedaček pro čekající zákazníky. Pokud v oficíně nikdo není, holič se posadí a spí. Pokud přijde první zákazník a holič spí, probudí se a posadí si zákazníka do křesla. Pokud přijde zákazník a holič už střihá a je volné místo v čekárně, posadí se, jinak odejde.
Reálně používaná primitiva Spin-lock Aktivní čekání í Vhodný pro očekávané krátké doby čekání Velmi rychlý vstup do kritické sekce Kritická sekce Blokovací primitivum Vhodný pro očekávané delší doby uvnitř kritické sekce Semafor Spouštění a zastavování vláken, čekání na událost OS závislá!
Synchronizační primitiva vyšší úroveň (1) RWL (Read-Write Lock) Vícenásobně/paralelně READ operace (nemění obsah chráněného prostředku) Exkluzivně WRITE operace (mění obsah) Bariéry Hlavní vlákno vypustí pomocná vlákna a čeká na bariéře na dokončení úkolu všech vláken
Synchronizační primitiva Reentrantní zámky vyšší úroveň (2) Několik funkcí, které používají stejnou kritickou sekci Některé z těchto funkcí volají jiné z těchto funkcí Zdánlivě zjednodušuje použití, reálně přináší spíše potíže Lze odstranit vyrobením blokovacích a neblokovacích variant funkcí Uzamčené (interlocked) operace Atomicky prováděné jednoduché operace (přičtení, inkrement, )
Synchronizační primitiva ve Windows Jednotné funkce pro pasivní čekání Atomické čekání na více primitiv, možnost OR/AND Timeout Každé primitivum má definováno, kdy signalizuje Semafor Signalizuje při hodnotě čítače >0 Event Pouze binární hodnota Nastavení na 1 signalizuje Autoreset/manuální reset Kritická sekce Jednosměrný spojový seznam Uzamčené operace
Synchronizační primitiva na UNIXech OS implementuje semafor Dnes obvykle knihovna PTHREAD Podmíněné proměnné Mutex RWL Spin-lock