Tento článek je součástí naší aktuální série přehledů UNIXových jader.
V předchozím článku této série jsme diskutovali o přehledu procesů UNIX.
Tento článek vysvětluje na vysoké úrovni o jádrech Reentrant, synchronizaci a kritických částech architektury jádra UNIX.
Reentrantní jádra
Jak název napovídá, reentrantní jádro je takové jádro, které umožňuje spouštění více procesů v režimu jádra v kterémkoli daném okamžiku, a to bez toho, aby způsobovalo problémy s konzistencí mezi datovými strukturami jádra.
Víme, že v systému s jedním procesorem může být v daném okamžiku spuštěn pouze jeden proces, ale v režimu jádra mohou být blokovány další procesy, které čekají na spuštění.
Například v reentrantním jádře se proces čekající na volání ‚read()‘ může rozhodnout uvolnit CPU procesu, který čeká na spuštění v režimu jádra.
Nyní by někdo mohl mít na mysli otázku, proč je jádro znovu vpuštěno? Začněme příkladem, kdy jádro není reentrantní, a uvidíme, co když umožňuje spuštění více procesů v režimu jádra.
Předpokládejme, že proces se spouští v režimu jádra a přistupuje k datové struktuře jádra a některým globálním hodnotám, které jsou s ní spojené.
- Předpokládejme, že název procesu je „A“.
- Nyní „A“ přistupuje ke globální proměnné, aby zjistil, zda je hodnota nenulová (aby mohla provádět nějaké výpočty atd.), a těsně předtím, než se pokusí tuto hodnotu použít v nějaké své logice, přepne kontext na proces „ B' se stane.
- Nyní se tento proces ‚B‘ pokouší získat přístup k hodnotě stejné globální proměnné a snižuje ji.
- Proběhne další přepnutí kontextu a proces „A“ se znovu spustí.
- Protože „A“ neví, že „B“ již hodnotu snížilo, pokusí se tuto hodnotu použít znovu.
- Takže tady je háček, proces „A“ vidí dvě různé hodnoty globální proměnné, protože hodnota byla změněna jiným procesem „B“.
Nyní tedy víme, proč se jádro musí znovu přihlásit. Další otázka, která může vyvstat, je, jak udělat jádro reentrant?
V zásadě lze při vytváření reentrantu jádra zvážit následující body:
- Zapisujte funkce jádra, které upravují pouze lokální proměnné (zásobníku) a nemění globální proměnné ani datové struktury. Takový typ funkcí je také známý jako reentrantní funkce.
- Přísné dodržování používání pouze reentrantních funkcí v jádře není proveditelné řešení. Další používanou technikou jsou tedy „uzamykací mechanismy“, které zajišťují, že pouze jeden proces může v daném čase používat funkci, která se nevrátí.
Z výše uvedených bodů je jasné, že použití reentrantních funkcí a zamykacích mechanismů pro nereentrantní funkce je jádrem tvorby reentrantu jádra. Protože implementace reentrantních funkcí více souvisí s dobrým programováním, uzamykací mechanismy souvisí s konceptem synchronizace.
Synchronizace a kritické sekce
Jak je uvedeno výše v příkladu, reentrantní jádro vyžaduje synchronizovaný přístup ke globálním proměnným jádra a datovým strukturám.
Část kódu, která pracuje s těmito globálními proměnnými a datovými strukturami, je známá jako kritická sekce.
Pokud je řídicí cesta jádra pozastavena (při použití globální hodnoty nebo datové struktury) kvůli přepnutí kontextu, pak by žádná jiná řídicí cesta neměla mít přístup ke stejné globální hodnotě nebo datové struktuře. Jinak by to mohlo mít katastrofální následky.
Když se podíváme zpět a uvidíme, proč potřebujeme synchronizaci? Odpovědí je bezpečné používání globálních proměnných jádra a datových struktur. Toho lze dosáhnout také pomocí atomových operací. Atomická operace je operace, která bude vždy provedena, aniž by jakýkoli jiný proces mohl číst nebo měnit stav, který je během operace přečten nebo změněn. Bohužel atomové operace nelze aplikovat všude. Například odstranění prvku z propojeného seznamu uvnitř jádra nemůže být atomickou operací.
Nyní se zaměříme zpět na to, jak synchronizovat řídicí cesty jádra.
Deaktivace preempce jádra
Preempce jádra je koncept, kde jádro umožňuje násilné pozastavení/přerušení jedné úlohy a spuštění další úlohy s vysokou prioritou, která čekala na zdroje jádra.
Zjednodušeně řečeno jde o přepínání kontextu procesů v režimu jádra, kdy běžící proces je nuceně pozastaven jádrem a druhý proces je spuštěn.
Pokud půjdeme podle definice, uvědomíme si, že právě tato schopnost jádra (zabránit, když jsou procesy v režimu jádra), způsobuje problémy se synchronizací. Řešením problému je zakázat preempci jádra. Tím je zajištěno, že přepnutí kontextu v režimu jádra proběhne pouze tehdy, když proces, který je aktuálně spuštěn v režimu jádra, dobrovolně uvolní CPU a zajistí, že všechny datové struktury jádra a globální proměnné jsou v konzistentním stavu.
Je zřejmé, že zakázání preempce jádra není příliš elegantní řešení a toto řešení selhává, když používáme víceprocesorové systémy, protože dva CPU mohou současně přistupovat ke stejné kritické sekci.
Deaktivace přerušení
Dalším mechanismem, který lze použít k dosažení synchronizace uvnitř jádra, je proces zakázat všechna hardwarová přerušení před vstupem do kritické oblasti a povolit je po opuštění této velmi kritické oblasti. Toto řešení opět není elegantním řešením, protože v případech, kdy je kritická oblast velká, mohou být přerušení deaktivována na velmi dlouhou dobu, čímž se maří vlastní účel přerušení a může způsobit zamrznutí hardwarových aktivit.
Semafory
Toto je nejoblíbenější metoda pro zajištění synchronizace uvnitř jádra.
Je efektivní na jednoprocesorových i víceprocesorových systémech. Podle tohoto konceptu lze semafor chápat jako čítač, který je spojen s každou datovou strukturou a je kontrolován všemi vlákny jádra, když se pokoušejí o přístup k dané datové struktuře.
Semafor obsahuje informace o hodnotě čítače, seznam procesů čekajících na získání semaforu (pro přístup k datové struktuře) a dvě metody pro zvýšení nebo snížení hodnoty čítače spojeného s tímto semaforem.
Pracovní logika je následující:
- Předpokládejme, že proces chce získat přístup ke konkrétní datové struktuře, nejprve zkontroluje počítadlo spojené se semaforem datové struktury.
- Pokud je počítadlo kladné, proces získá Semafor, sníží hodnotu počítadla, provede kritickou oblast a zvýší počítadlo Semaforu.
- Pokud však proces nalezne hodnotu čítače jako nulu, pak je proces přidán do seznamu (spojeného se semaforem) procesů čekajících na získání semaforu.
- Nyní, kdykoli bude počítadlo kladné, všechny procesy čekající na semafor se jej pokusí získat.
- Ten, který znovu získá, sníží počítadlo, provede kritickou oblast a poté zvýší počítadlo zpět, zatímco ostatní procesy přejdou zpět do režimu čekání.
Předcházení zablokování
Práce se synchronizačním schématem, jako je Semafory, má vedlejší efekt „Deadlocks“.
Vezměme si příklad:
- Předpokládejme, že proces A získává Semafor pro určitou datovou strukturu, zatímco proces B získává Semafor pro jinou datovou strukturu.
- Nyní v dalším kroku chtějí oba procesy získat Semafor pro datové struktury, které si navzájem pořídí, tj. Proces A chce získat Semafor, který je již pořízen procesem B a naopak.
- Tento typ situace, kdy proces čeká, až jiný proces uvolní zdroj, zatímco druhý čeká na první, až uvolní zdroj, se nazývá uváznutí.
- Zablokování může způsobit úplné zmrazení řídicích cest jádra.
Tyto typy zablokování jsou častější u návrhů, kde se používá velké množství jaderných zámků. U těchto návrhů je extrémně obtížné určit, že by nikdy nenastal stav uváznutí. V operačních systémech, jako je Linux, se zablokování vyhne jejich získáním v pořadí.