Procesy, vlákna, synchronizace a deadlock
Proces vs. vlákno, implementace, přepínání kontextu, stavy vláken, synchronizační nástroje, klasické synchronizační úlohy, uváznutí a strategie jeho řešení.
📦 Program, Proces, Vlákno – základní pojmy
Program je spustitelný binární soubor uložený na disku. Formát závisí na OS: ELF (Linux/Unix), PE/PE32+ (Windows). Obsahuje sekce .text (kód) a .data (data).
Co si OS udržuje pro každý proces (PCB – Process Control Block):
- Identifikace: PID (Process ID), PPID (Parent PID), SID (Session ID)
- Bezpečnost: identita vlastníka (EUID, RUID), skupiny (GID), přístupová práva
- Správa paměti: informace pro překlad virtuálních adres (page table –
struct mm_struct) - Správa souborů: tabulka deskriptorů souborů
- IPC prostředky: semafory, roury, signály
Co si OS udržuje pro každé vlákno (TCB – Thread Control Block):
- TID (Thread ID)
- Hodnoty registrů CPU, čítač instrukcí (PC) – nutné pro přepínání kontextu
- Zásobník (stack) – lokální proměnné a history volání
- Priorita, stav vlákna, čas strávený na CPU
Procesy mají oddělené adresní prostory (paměť). Vlákna v rámci jednoho procesu sdílejí adresní prostor – to umožňuje efektivní komunikaci, ale přináší riziko race conditions.
🔄 Vytvoření a ukončení procesu
Nový proces vznikne, když existující proces zavolá příslušné systémové volání:
- Unix/Linux:
fork(2),execve(2) - Windows:
CreateProcessA()
fork() – vytvoří kopii (klon) aktuálního procesu:
- Vrací číslo potomka v rodiči, v potomkovi vrací 0, při chybě -1
- Jádro alokuje nové datové struktury; adresový prostor se „zkopíruje" od rodiče (v praxi pomocí Copy-on-Write)
- Čítač instrukcí ukazuje na instrukci za
fork()– rodič i potomek pokračují dál na sobě nezávisle
execve(filename, argv, envp) – nahradí adresový prostor novým programem:
- Adresový prostor se přepíše obsahem spustitelného souboru
- Nový PC ukazuje na první instrukci nového programu
wait(int *status) – rodič zablokuje, dokud potomek neskončí; potomkova exit hodnota se uloží do status.
- Zavolání
exit()nebo návrat z main → systémové voláníexit - Nastaví se
exit_state = zombie, zápis návratového kódu doexit_code - Rodič se notifikuje signálem → přečte návratový kód pomocí
wait()→ strukturatask_structpotomka se uvolní - Pokud rodič skončí dříve → OS přenastaví ukazatel rodiče na
init(init adoptuje sirotky)
⚙️ Plánování vláken a přepínání kontextu
Jádro OS a vlákna sdílejí omezený počet jader CPU. Jedno vlákno může být v jednom okamžiku zpracováváno jedním logickým jádrem. Vlákna se na jádrech střídají pomocí preemptivního plánování.
Preemptivní plánování:
- Vláknu je přiděleno jádro CPU na základě plánovacích kritérií (priorita, ...)
- Přidělí se časové kvantum
- CPU je vláknu odebráno, pokud: uplyne časové kvantum (přerušení od časovače), vlákno provede systémové volání (např. V/V), nastane přerušení
Postup přepnutí kontextu:
- Uloží se kontext původního vlákna (registry, PC, zásobníkový ukazatel)
- Jádro OS naplánuje další vlákno
- Nastaví se kontext nového vlákna
Stavy vlákna:
| Stav | Popis |
|---|---|
Idle | Vznik nového vlákna |
Ready | Vlákno čeká, až mu bude přiděleno jádro CPU |
Running | Vlákno je zpracováváno jádrem CPU |
Blocked | Vlákno čeká na událost (V/V, signál, mutex...) |
Zombie | Vlákno se ukončuje, ale ještě ne vše dokončeno |
Free | Vlákno bylo kompletně zrušeno (teoretický stav) |
API pro vlákna: pthread_create(&tid, NULL, &start_routine, NULL) – vytvoří vlákno vykonávající start_routine. pthread_join(tid, &result) – čeká na dokončení vlákna.
⚠️ Časově závislé chyby (Race Conditions) a kritické sekce
Klasický příklad: Dvě vlákna inkrementují sdílený čítač.
// Vlákno 1: // Vlákno 2: LOAD R, [A] LOAD R, [A] INC R INC R STORE [A], R STORE [A], R // Pokud se proloží → výsledek je 1 místo 2!
Pravidla korektního paralelního programu:
- Žádné předpoklady nesmí být kladeny na rychlost vláken a počet jader
- Zajistit výlučný přístup ke sdíleným prostředkům (žádné vlákno nesmí čekat nekonečně dlouho)
- Mimo kritickou sekci nesmí být vlákno blokováno ostatními vlákny
🔒 Synchronizační nástroje
Základní přístupy k synchronizaci kritické sekce:
1. Aktivní čekání (Busy Waiting / Spinning)
Sdílená proměnná indikuje obsazenost sekce. Vlákno čekající na vstup opakovaně testuje stav proměnné ve smyčce. Implementace vyžaduje atomickou instrukci:
xchg (x86), cas (SPARC), amoswap (RISC-V).
- ✅ Výhoda: minimální režie při krátkém čekání
- ❌ Nevýhoda: čekání zatíží CPU na 100%; může způsobit inverzní prioritní problém
Nastane, pokud: vlákno A (nízká priorita) je v kritické sekci; vlákno B (vyšší priorita) čeká pomocí busy waiting; všechna jádra obsazena vlákny s vyšší prioritou → vlákno A nikdy nedostane CPU → uváznutí!
2. Blokující volání (Mutex / Zámek)
mutex_lock() – zamkne nebo zablokuje vlákno; mutex_unlock() – odemkne nebo probudí čekající vlákno.
POSIX: pthread_mutex_t, funkce pthread_mutex_lock(), pthread_mutex_unlock(). C++: std::mutex, std::lock_guard.
- ✅ Výhoda: čekání nezatěžuje CPU
- ❌ Nevýhoda: větší režie (změna stavu vlákna v jádře OS) → vyplatí se při delším čekání
3. Podmíněné proměnné (Condition Variables)
cond_wait(cond, mutex) – atomicky uvolní mutex a zablokuje vlákno (musí být voláno s uzamčeným mutexem; po probuzení mutex opět zamkne). cond_signal(cond) – probudí aspoň jedno blokované vlákno.
Spurious wakeup (falešné probuzení) – POSIX umožňuje probuzení vlákna bez zavolání cond_signal(). Také při více čekajících vláknech může podmínka přestat platit. Proto vždy: while(podminka) cond_wait(...);
4. Semafor
sem_wait() – sníží čítač o 1 (pokud 0, zablokuje vlákno); sem_post() – zvýší čítač o 1 (nebo probudí čekající vlákno). Binární semafor (hodnoty 0/1) ≈ mutex.
POSIX: sem_t, sem_init(), sem_wait(), sem_post().
5. Bariéra
barrier_wait() – zablokuje vlákno, dokud se na bariéře nesejde přesný počet vláken; pak jsou všechna najednou uvolněna a čítač se resetuje.
POSIX: pthread_barrier_t, pthread_barrier_wait(). Použití: maticové výpočty, paralelní simulace.
🏭 Klasické synchronizační úlohy
Producent–Konzument (Bounded Buffer Problem)
Producent vkládá data do sdílené fronty s omezenou velikostí N. Konzument data vybírá. Musíme řešit:
- Výlučný přístup při vkládání/vybírání
- Fronta prázdná → zablokuj konzumenta
- Fronta plná → zablokuj producenta
Řešení pomocí semaforů:
sem_t empty; // sem_init(&empty, N); // počítá prázdná místa sem_t full; // sem_init(&full, 0); // počítá plná místa mutex_t mtx; // mutex pro ochranu fronty // Producent: // Konzument: sem_wait(&empty); sem_wait(&full); mutex_lock(&mtx); mutex_lock(&mtx); vlo_item(); vyber_item(); mutex_unlock(&mtx); mutex_unlock(&mtx); sem_post(&full); sem_post(&empty);
Řešení pomocí podmíněných proměnných:
// Producent: mutex_lock(&mtx); while(count == N) cond_wait(&cv_full, &mtx); insert_item(); count++; cond_signal(&cv_empty); mutex_unlock(&mtx);
| Nástroj | Typ problému | POSIX API |
|---|---|---|
| Mutex | Kritická sekce | pthread_mutex_t |
| Podmíněná proměnná | Čekání na podmínku | pthread_cond_t |
| Semafor | Počítání zdrojů, KS | sem_t |
| Bariéra | Synchronizace iterací | pthread_barrier_t |
| Spinlock | Krátká KS, vícejadrové sys. | HW instrukce (TSL) |
💀 Uváznutí (Deadlock) – definice a příčiny
Výpočetní prostředky mohou být:
- Fyzické: fyzická paměť, tiskárna, ...
- Logické: proměnná, soubor, mutex, semafor, ...
- Odnímatelné (preemptable): lze odebrat bez rizika (např. CPU)
- Neodnímatelné (nonpreemptable): nelze bezpečně odebrat (většina prostředků)
Alokační graf znázorňuje alokaci prostředků vlákny (orientovaný graf). Hrana od prostředku k vláknu = alokovaný prostředek. Hrana od vlákna k prostředku = vlákno čeká. Každá smyčka v grafu = uváznutí.
📋 Coffmanovy podmínky
Uváznutí nastane pouze pokud jsou splněny všechny čtyři podmínky současně:
| # | Podmínka | Vysvětlení |
|---|---|---|
| 1 | Vzájemné vyloučení | Každý prostředek je přidělen právě jednomu vláknu nebo je volný (nelze sdílet) |
| 2 | Neodnímatelnost | Přidělený prostředek nemůže být vláknu násilím odebrán – musí být dobrovolně uvolněn |
| 3 | Drž a čekej | Vlákno, které má přiděleny prostředky, může žádat o další (postupná alokace) |
| 4 | Kruhové čekání | Existuje smyčka vláken, kde každé čeká na prostředek přidělený dalšímu vláknu ve smyčce |
Podmínky 1–3 jsou nutné, ale ne dostačující pro vznik uváznutí. Podmínka 4 (kruhové čekání) je samotné uváznutí. Pokud je alespoň jedna podmínka porušena, uváznutí nemůže nastat.
🛡️ Strategie pro řešení uváznutí
1. Pštrosí strategie (ignorování problému)
- Problém se neřeší – pokud uváznutí nastane, vyžaduje se zásah uživatele/administrátora
- Vhodná, pokud: systém obsahuje velký počet různě se chovajících vláken, pravděpodobnost uváznutí je malá, řešení by bylo příliš drahé
- Praktické řešení většiny univerzálních OS (Linux, Windows) – jádro je navrženo jako „deadlock free", na úrovni procesů se řeší částečně (limity ulimit)
2. Prevence uváznutí (porušení jedné z Coffmanových podmínek)
- Porušení vzájemného vyloučení: v praxi téměř nerealizovatelné bez race conditions
- Porušení neodnímatelnosti: vhodné, kde lze uložit stav (např. přepínání kontextu CPU)
- Porušení „drž a čekej": alokovat všechny prostředky najednou před použitím (worse utilization)
- Porušení kruhového čekání: číslování prostředků + alokace jen ve vzestupném pořadí → smyčka nemůže vzniknout. Příklad: večeřící filozofové – filosof musí brát vidličky ve vzrůstajícím pořadí čísel
3. Předcházení vzniku uváznutí (Banker's Algorithm)
Předpoklady: Předem známé požadavky vláken na prostředky. Koncept bezpečného stavu:
Používané matice/vektory: E (existence vector), F (free vector), Q (request matrix – celkové požadavky), A (allocation matrix – aktuálně alokované), M = Q - A (missing matrix).
Algoritmus detekce bezpečného stavu:
- Existuje vlákno, které lze uspokojit volnými prostředky? → označ je, přidej jeho prostředky k volným, opakuj
- Byla označena všechna vlákna? → ANO = bezpečný stav, NE = nebezpečný stav
Bankéřův algoritmus vyžaduje znát předem všechny požadavky vláken a je výpočetně náročný. V praxi se pro obecné OS nepoužívá.
4. Detekce a zotavení
Uváznutí se detekuje periodickými kontrolami (algoritmus podobný bankéřovu, ale pro aktuální požadavky Qc). Zotavení:
- Ukončení všech uvázlých vláken – typické v OS
- Postupné ukončování – dokud uváznutí přetrvává
- Rollback a restart – vyžaduje checkpoint mechanismus; pravděpodobnost opakovaného uváznutí je nízká díky nedeterminismu
| Strategie | Princip | Použití v praxi |
|---|---|---|
| Pštrosí | Ignoruj problém | Většina univerzálních OS |
| Prevence | Porušení Coffmanovy podmínky | Návrh protokolů, RT systémy |
| Předcházení | Bankéřův algoritmus | Speciální systémy s předvídatelnými požadavky |
| Detekce + zotavení | Detekce smyček, oběť | DB systémy, transakční systémy |
📌 Shrnutí okruhu 1
- Proces = entita s prostředky (pamětí, soubory...); vlákno = entita s CPU časem. Vlákna jednoho procesu sdílejí paměť.
- Přepínání kontextu = uložení + obnovení stavu (registrů, PC) vlákna. Probíhá transparentně – vlákno „nevnímá" přerušení.
- Race conditions vznikají nechráněným přístupem ke sdíleným datům. Mutex, semafor a podmíněné proměnné zajišťují vzájemné vyloučení.
- Deadlock nastane, pokud jsou splněny všechny 4 Coffmanovy podmínky najednou. Nejběžnější přístup: pštrosí strategie + omezování prostředků.
- Bankéřův algoritmus umožňuje předcházet uváznutí za cenu znalosti požadavků vláken dopředu a výpočetní režie.
❓ Kontrolní otázky ke státnicím
Proces je entita vlastnící prostředky (paměť, soubory, zámky). Vlákno je výpočetní entita v rámci procesu, které je přidělováno CPU. Vlákna jednoho procesu sdílejí VAS a prostředky procesu, ale každé má vlastní zásobník a kontext (registry, PC). Přepínání kontextu mezi vlákny téhož procesu je rychlejší, protože nevyžaduje změnu adresního prostoru.
Jsou to 4 nutné podmínky pro vznik deadlocku: (1) vzájemné vyloučení, (2) neodnímatelnost prostředků, (3) „drž a čekej", (4) kruhové čekání. Jsou důležité, protože k uváznutí může dojít pouze pokud jsou splněny všechny čtyři najednou. Porušením libovolné jedné podmínky zabráníme uváznutí.
Race condition nastane, když výsledek programu závisí na pořadí (načasování) přístupu vláken ke sdíleným datům. Předchází se ochranou kritických sekcí synchronizačními nástroji: mutex zajistí, že do kritické sekce vstupuje vždy jen jedno vlákno. Podmíněné proměnné a semafory koordinují přístup k prostředkům s podmínkami (plná/prázdná fronta).
TSL (Test-and-Set-Lock) je atomická hardwarová instrukce, která najednou přečte hodnotu z paměti do registru a nastaví slovo v paměti na nenulovou hodnotu. Atomičnost zaručuje, že žádné jiné vlákno nemůže přistoupit ke slovu během provádění instrukce. Bez ní by samotná sdílená proměnná nestačila (kontrola a nastavení by nebyly atomické → race condition).
Přepínání kontextu = uložení stavu aktuálního vlákna (registry, PC, SP) do TCB, výběr nového vlákna plánovačem, načtení stavu nového vlákna. Dochází k němu při: (1) vypršení časového kvanta (přerušení od časovače), (2) systémovém volání (vlákno se může zablokovat), (3) přerušení od V/V zařízení, (4) dobrovolném vzdání se CPU.
Bankéřův algoritmus brání uváznutí tím, že prostředek přidělí, jen pokud systém zůstane v bezpečném stavu. Bezpečný stav = existuje pořadí uspokojení všech vláken. Algoritmus: hledá vlákno, jehož zbývající požadavky (M = Q - A) lze uspokojit volnými prostředky (F). Takovéto vlákno „uspokojíme" a jeho prostředky přičteme k volným. Opakujeme. Pokud uspokojíme všechna vlákna → stav bezpečný. Vyžaduje znát předem všechny požadavky.
Virtualizace hlavní paměti stránkováním
Virtuální adresní prostor (VAS), princip stránkování, překlad virtuálních adres na fyzické, tabulky stránek (jednoúrovňová, víceúrovňová, invertovaná), TLB, algoritmy pro nahrazování stránek.
🗺️ Virtuální adresní prostor (VAS) – motivace a princip
Proč VAS?
- Ochrana paměti: nelze číst/přepisovat data jiného procesu → bezpečnost (hesla, PIN), stabilita (pád procesu neovlivní ostatní)
- Jednodušší zavádění programu: lze zavést do libovolné volné fyzické paměti bez přepočítávání adres
- Větší adresní prostor než fyzická paměť: 32-bit OS → každý proces může adresovat 4 GB; fyzicky může být méně
Segmenty VAS: .text (kód), .data (globální proměnné), heap (halda, roste nahoru), libraries (sdílené knihovny), stack (zásobník, roste dolů).
Překlad adres vyžaduje podporu v HW (MMU) a OS → složitost. Překlad adres chvíli trvá → zpomalení (řeší TLB).
📄 Stránkování – základní princip
Proč stránkování?
- Elegantně řeší fragmentaci fyzické paměti – každé stránce lze přiřadit libovolný rámec
- Celý VAS nemusí být v paměti – stačí pouze aktuálně používané stránky
- Nepoužívané stránky mohou být odloženy na disk (swap)
Struktura virtuální adresy:
[číslo stránky (page number) | offset (byte uvnitř stránky)]
Příklad: 32-bit CPU, 4 KB stránky → offset = 12 bitů, číslo stránky = 20 bitů → 2²⁰ = 1M stránek.
🔧 MMU a jednoúrovňová stránkovací tabulka
Jednoúrovňová stránkovací tabulka obsahuje pro každou stránku VAS jeden řádek:
- Číslo rámce – kam je stránka namapována
- Řídicí bity:
P(Present) – stránka je v hlavní pamětiA(Accessed) – ke stránce se přistupovaloD(Dirty) – obsah byl modifikován (před odložením nutno zapsat na disk)W(Write) – lze stránku modifikovat?X(Execute) – stránka obsahuje spustitelný kód?U(User/Supervisor) – přístupná v uživatelském modu?G(Global) – platná ve všech procesech (sdílené jádro OS)C(Cache) – lze používat cache? (zakázat pro registry periferií)
Překlad adresy (jednoúrovňová ST):
- Rozděl virtuální adresu na číslo stránky (index do ST) a offset
- Přečti řádek ST[číslo stránky] → zkontroluj bit P
- Bit P = 1: fyzická adresa = číslo rámce + offset → přístup do paměti
- Bit P = 0: výpadek stránky → OS obsluha
Pro 32-bit CPU s 4 KB stránkami: ST má 2²⁰ = 1M řádků × 4 B = 4 MB na jeden proces. Pro 128 procesů = 512 MB jen pro ST! Přitom většina ST je prázdná (VAS využit z malé části).
Řešení: víceúrovňová stránkovací tabulka.
🌲 Víceúrovňová stránkovací tabulka
Princip (n-úrovňová ST):
- V paměti je vždy pouze top-level tabulka (úroveň 1)
- Tabulky úrovní 2 ... n existují jen pro používané části VAS → úspora paměti
- Každá tabulka úrovně 1 ... n-1 obsahuje Present bit + číslo rámce s tabulkou další úrovně
- Tabulka úrovně n obsahuje Present bit + číslo rámce s daty
Příklad – dvouúrovňová ST (x86 32-bit):
Virtuální adresa: [10 bitů L1 index | 10 bitů L2 index | 12 bitů offset]
Pro 128 procesů stejné zátěže jako dříve: 128 × [1 × (1024 × 4B) + 3 × (1024 × 4B)] = 128 × 16 KB = 2 MB (oproti 512 MB u jednoúrovňové!).
Reálné architektury:
| Architektura | Úrovně ST | Virtuální adresa |
|---|---|---|
| x86 (32-bit) | 2 úrovně | 32 bit: 10+10+12 |
| x86-64 (64-bit) | 4 úrovně (CR3) | 48 bit: 9+9+9+9+12 |
| SPARC v9 | Invertovaná ST (TSB) | – |
Překlad adresy vyžaduje více přístupů do paměti (pro n úrovní = n+1 přístupů). Řeší TLB – cache přeložených adres.
🔀 Invertovaná stránkovací tabulka
Překlad adresy:
- Hašovací funkce z (PID, číslo stránky) → index do invertované ST
- Porovnej záznam v tabulce s (PID, číslo stránky)
- Shoda → číslo řádku = číslo rámce → fyzická adresa = rámec + offset
- Neshoda → procházej zřetězení (chain) → nebo výpadek stránky
| Typ ST | Velikost | Rychlost překladu | Použití |
|---|---|---|---|
| Jednoúrovňová | Velká (na proces) | Rychlá (1 přístup) | Jednoduché systémy |
| Víceúrovňová | Malá (pro sparse VAS) | Pomalejší (n přístupů) | x86, ARM, RISC-V |
| Invertovaná | Malá (jedna pro systém) | Pomalá (hash + chain) | PowerPC, UltraSPARC |
⚡ Translation Lookaside Buffer (TLB)
Obsah jedné položky TLB:
valid bit– platná položka?- Číslo stránky (page number)
- Číslo rámce (frame number)
ASID– Address Space ID (identifikátor adresního prostoru – aby TLB mohl být sdílený více procesy)- Kopie řídicích bitů ze stránkovací tabulky (W, X, U...)
Průběh překladu s TLB:
- MMU hledá číslo stránky v TLB → TLB Hit: fyzická adresa = rámec z TLB + offset → velmi rychlé (~1 cyklus)
- TLB Miss: MMU prohledá stránkovací tabulky v paměti, aktualizuje TLB, přeloží adresu
- Při výpadku stránky: OS obslouží, aktualizuje ST, TLB, restartuje instrukci
Díky principu lokality je TLB hit ratio obvykle >99%. Průměrný čas přístupu: t_avg = hit_ratio × t_TLB + (1 - hit_ratio) × t_pamet. Příklad: hit_ratio = 0.99, t_TLB = 1 ns, t_pamet = 50 ns → t_avg ≈ 1.5 ns.
Při přepnutí procesu musí OS zneplatnit položky TLB patřící předchozímu procesu (flush TLB) nebo použít ASID k rozlišení procesů. Flush TLB je drahá operace – ASID umožňuje zachovat platné záznamy i po přepnutí kontextu.
🔁 Obsluha výpadku stránky (Page Fault Handler)
OS obsluha výpadku stránky (krok za krokem):
- Ověření platnosti virtuální adresy (leží ve VAS?) a oprávněnosti přístupu (zápis do R/O stránky?) → pokud ne: Segmentation Fault
- Nalezení volného rámce (Get Free Frame):
- Pokud existuje volný rámec → použij jej
- Pokud ne → vyber oběť pomocí algoritmu pro náhradu stránek (PRA)
- Je oběť dirty (D bit)? → zapiš ji nejprve na disk (swap)
- Zneplatni záznamy ST a TLB pro odložený rámec ve všech dotčených procesech
- Načtení stránky do rámce:
- Ze souboru (file-backed VMA, např. .text, .data programu, mmap)
- Ze swapu (anonymní stránka dříve odložená)
- Inicializace nulou (nová anonymní stránka – heap, stack)
- Aktualizace stránkovacích tabulek (nastavení P bitu, čísla rámce)
- Aktualizace TLB
- Restart přerušené instrukce
Linux spravuje VAS pomocí struktur vm_area_struct. Každá VMA je spojitý blok virtuální paměti s přístupovými právy. Může být file-backed (propojená se souborem, např. spustitelný soubor, knihovna) nebo anonymní (halda, zásobník, .bss). Platná virtuální adresa musí ležet v některé VMA.
🧮 Algoritmy pro náhradu stránek (Page Replacement Algorithms)
Cíl: minimalizovat počet výpadků stránek. Vychází z principu časové a prostorové lokality.
1. Optimální algoritmus (OPT)
- Nahradí stránku, ke které se bude přistupovat nejpozději v budoucnosti
- ✅ Generuje minimální počet výpadků (dolní mez)
- ❌ Nerealizovatelný (neznáme budoucnost) – slouží jako benchmark
2. NRU algoritmus (Not Recently Used)
- Třídy stránek podle R (referenced) a D (dirty) bitů: Třída 0 (R=0,D=0), 1 (R=0,D=1), 2 (R=1,D=0), 3 (R=1,D=1)
- Nahradí stránku z neprázdné třídy s nejnižším číslem
- OS periodicky resetuje R bit
- ✅ Jednoduchý, rozumný počet výpadků
3. FIFO algoritmus (First-In First-Out)
- Nahradí stránku, která je v paměti nejdéle (hlava fronty)
- ✅ Jednoduchý na implementaci
- ❌ Nezohledňuje frekvenci přístupu → velký počet výpadků; trpí Bélády anomálií (více rámců → více výpadků)
4. Clock algoritmus (modifikovaný FIFO)
- Kruhová fronta + ručička (ukazatel). Každá stránka má R bit.
- Při hledání oběti: pokud R=1 → vynuluj R, posuň ručičku. Pokud R=0 → tato stránka je oběť.
- ✅ Lepší než FIFO, rozumná implementace. Varianta se dvěma ručičkami (two-handed clock) – používá se ve SVR4 Unix a nástupcích.
5. LRU algoritmus (Least Recently Used)
- Nahradí stránku, ke které se přistupovalo nejdéle (nejstarší přístup)
- Dobrá aproximace optimálního algoritmu (využívá časovou lokalitu)
- ✅ Nízký počet výpadků
- ❌ Implementace: nutný speciální HW čítač nebo matice přístupů → v čisté podobě příliš drahé
6. Aging algoritmus (softwarová simulace LRU)
- Pro každou stránku n-bitový čítač C (inicializován na 1 při načtení)
- Periodicky: posun C doprava o 1 bit, nastavení MSB na hodnotu R bitu, reset R bitu
- Oběť = stránka s nejmenší hodnotou C
- ✅ Menší režie než LRU, rozumná aproximace
- ❌ Pamatuje jen omezenou historii (n bitů); nerozliší přesný čas přístupu v jedné periodě
| Algoritmus | Výpadků (příklad) | Složitost | Praxe |
|---|---|---|---|
| OPT | 6 (minimum) | Nerealizovatelný | Benchmark |
| LRU | 7 | HW podpora | Aproximace (Aging) |
| NRU | 7 | Snadná | Ano |
| Clock | 8 | Středně složitá | Unix SVR4, Linux |
| Aging | 9 | Středně složitá | Softwarové OS |
| FIFO | 9 | Jednoduchá | Méně vhodný |
Linux používá variantu LRU se dvěma oddělenými LRU seznamy pro každý typ stránek: aktivní (active_anon, active_file) a neaktivní (inactive_anon, inactive_file). Anonymní stránky jsou odděleny od file-backed (odlišné vzory přístupu). Fyzická paměť se spravuje pomocí Buddy alokátoru (alokace v mocninách dvou rámců) a Slab alokátoru (malé objekty jádra).
🏗️ Návrh stránkovacích systémů – klíčové otázky
Kdy nahrát stránku?
- Demand paging – stránka se nahraje až při prvním přístupu (výpadek stránky)
- Pre-paging – při výpadku se nahraje stránka + několik sousedních (prostorová lokalita) → méně přenosů z disku
Kolik rámců přidělit procesu?
- Málo rámců → mnoho výpadků stránek → thrashing (zahlcení výměnou stránek)
- Od určitého počtu se počet výpadků výrazně nemění (= Working Set)
- Variable-allocation: OS přiděluje rámce podle aktuálních potřeb procesu
Sdílení paměti:
- .text segment sdílených knihoven → sdílení v paměti (MAP_SHARED), jen jeden rámec na systém
- .data sdílených knihoven → MAP_PRIVATE + Copy-on-Write: kopie se vytvoří až při zápisu
Kernel space a KPTI (Meltdown):
Jádro OS je tradičně namapováno do VAS každého procesu (chráněno U bitem = 0). Po útoku Meltdown (2018): KPTI (Kernel Page Table Isolation) – do user space se namapuje pouze nutné minimum jádra; jádro má vlastní oddělené ST. Při systémovém volání se přepíná PTBR.
📌 Shrnutí okruhu 2
- Stránkování dělí VAS i fyzickou paměť na bloky stejné velikosti (stránky/rámce). Řeší fragmentaci paměti a umožňuje VAS větší než fyzická paměť.
- Překlad adresy: číslo stránky → index do ST → číslo rámce; fyzická adresa = rámec + offset. MMU provádí překlad hardwarově za pomoci ST uložených v paměti.
- Víceúrovňová ST šetří paměť tím, že tabulky nižší úrovně existují jen pro používané části VAS. TLB cache akceleruje překlad (typicky >99% hit rate).
- Výpadek stránky: OS vybere oběť (PRA), odloží ji (pokud dirty), načte požadovanou stránku, aktualizuje ST + TLB.
- Algoritmy nahrazování: OPT (optimum, nerealizovatelný), LRU (nejlepší aproximace), Clock (praktický), Aging (softwarová aproximace LRU). Linux: varianta LRU s aktivními/neaktivními seznamy.
❓ Kontrolní otázky ke státnicím
Virtuální adresa se rozdělí na číslo stránky a offset. MMU nejprve hledá v TLB (TLB hit → přímý výsledek). TLB miss → prochází stránkovací tabulku v paměti (jednoúrovňová: jeden přístup; víceúrovňová: n přístupů). Pokud P bit = 0 → výpadek stránky → OS obsluha. Fyzická adresa = číslo rámce (z ST) + offset (nezměněn). Překlad se provádí pro každý přístup do paměti transparentně procesem.
Jednoúrovňová ST musí mít řádek pro každou stránku VAS, i když je většina VAS nevyužitá – pro 32-bit proces = 4 MB na ST. Víceúrovňová ST existuje v paměti jen pro skutečně používané části VAS (tabulky nižší úrovně se alokují až při potřebě). Pro procesy s řídkým VAS to výrazně šetří paměť. Nevýhoda: pomalejší překlad (více přístupů do paměti → kompenzováno TLB).
TLB (Translation Lookaside Buffer) je rychlá asociativní cache překladů virtuálních na fyzické adresy. Bez TLB by každý přístup do paměti vyžadoval n přístupů do ST (n = počet úrovní). Díky principu lokality je TLB hit ratio typicky >99%, čímž se průměrná latence překladu blíží latenci TLB (~1 ns) namísto přístupu do RAM (~50 ns). Při přepnutí procesu musí OS zneplatnit TLB záznamy (flush) nebo používat ASID.
1) Ověření platnosti a oprávněnosti přístupu (segfault pokud ne). 2) Nalezení volného rámce – pokud žádný není volný, algoritmus PRA vybere oběť; pokud je oběť dirty (D=1), zapíše se na disk. 3) Zneplatnění ST a TLB záznamu obětovaného rámce ve všech dotčených procesech. 4) Načtení požadované stránky do rámce – ze souboru (file-backed), ze swap souboru, nebo inicializace nulou (anon). 5) Aktualizace ST a TLB. 6) Restart přerušené instrukce.
LRU (Least Recently Used) nahradí stránku, ke které se přistupovalo nejdéle. Je optimální aproximací (využívá časovou lokalitu) ale vyžaduje hardware counter nebo matici přístupů → nepraktické. Clock (hodinový algoritmus) je modifikovaný FIFO s R bitem: ručička prochází kruhovou frontou; stránky s R=1 dostávají „druhou šanci" (R se vynuluje); stránka s R=0 je odstraněna. Prakticky realizovatelný, generuje méně výpadků než FIFO. Aging je softwarová aproximace LRU pomocí n-bitového čítače, který se periodicky posouvá doprava.
Jednoúrovňová ST: každý proces má vlastní tabulku, jeden řádek pro každou stránku VAS → velká paměťová náročnost (škáluje s počtem procesů × velikostí VAS). Invertovaná ST: celý systém má jednu tabulku, jeden řádek pro každý fyzický rámec → konstantní velikost (škáluje jen s fyzickou pamětí). Nevýhoda: překlad pomocí hashování + zřetězení je pomalejší → nutnost TLB. Používána v PowerPC, UltraSPARC.
Thrashing nastane, když procesu je přiděleno příliš málo fyzických rámců – neustále dochází k výpadkům stránek a OS stráví více času výměnou stránek než samotným výpočtem. Prevence: přidělovat procesu Working Set počet rámců (množina stránek nedávno používaných). Pokud součet WS všech procesů překročí fyzickou paměť, je nutné snížit počet aktivních procesů (swap celých procesů na disk).