Specifika vývoje ovladačů 2

Windows Pokročilé Specifika vývoje ovladačů 2

ONEbit hosting Unicorn College Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

Tento článek navazuje na Pár faktů a mýtů o vývoji ovladačů a přináší další várku zajímavostí a specifik vývoje ovladačů na platformě Windows. Tentokrát se zaměříme zejména na paměť.

Procesy a vlákna

Při programování běžné aplikace víme, že ta má svůj vlastní virtuální adresový prostor, do něhož jí ostatní aplikace nemohou přímo sahat. V žargonu operačního systému je tento adresový prostor (a další prostředky, jako jsou otevřené soubory) zakomponován do entity s názvem proces. Proces dále obsahuje vlákna (v základu jedno) zodpovědná za vykonávání kódu umístěném v adresovém prostoru. Jako vývojáři aplikace víme, že nám patří jeden proces, ve kterém můžeme mít jedno či více vláken provádějících námi požadované operace.

Jak bylo řečeno na konci minulého článku, ovladače fungují velmi podobně jako DLL knihovny. Nejsou izolovány ve vlastním virtuálním adresovém prostoru, ale všechny sdílejí jeden prostor, který je navíc namapovaný do adresových prostorů všech procesů. Bezpečnostní mechanismy procesoru (konkrétně jednotky zodpovědné za stránkování) zajišťují, že aplikace, ač o ní vědí, nemohou do této oblasti provádět žádné přístupy (čtení, zápis, vykonávání instrukcí). Opačně žádné takové tvrzení neplatí – ovladače mohou přistupovat do celého adresového prostoru procesu, v jehož kontextu se právě nacházejí.

Tím se dostáváme k otázce, v kontextu jakého procesu (popř. jakého vlákna) vlastně ovladače vykonávají svůj kód. Odpověď je šalamounská: záleží na tom, kdo potřebuje jejich služeb. Například ovladač monitorující přístupy do registru je volán vždy v kontextu vlákna, které se snaží danou operaci s registrem provést. Volání funkce, kterou ovladač podstrčí systému, aby jej informoval o práci s registrem, je součástí každé z mnoha druhů registrových operací. Podobná pravidla platí pro ovladače dohlížející na otevírání a vytváření souborů na disku.

Ne vždy je ale situace takto jednoduchá. Ovladače často zpracovávají příchozí požadavky asynchronně – předávají je ke zpracování svým vlastním vláknům, jenž mohou komunikovat s dalšími komponentami jádra. Pro tyto komponenty pak často není možné dopátrat se toho, v kontextu jakého vlákna či procesu byl daný požadavek vytvořen.

Nejasnost kontextu procesu a vlákna vede k programování ovladačů tak, aby na těchto vlastnostech nezáleželo. Vzhledem k tomu, že oblast vyhrazená jádru se v každém adresovém prostoru nachází na stejném místě, ovladače pouze nesmí sahat mimo tuto oblast, pokud si nejsou absolutně jisty, v jakém procesu se právě nacházejí. A i potom je rozumné pracovat pouze s daty, která jim daný proces ukáže (předáním adresy a velikosti příslušného bufferu), protože strukturu celého adresového prostoru neznají (neví například, kde se nachází paměť s alokovanými daty).

Umístění paměti jádra vždy na stejném místě adresového prostoru procesu ukazuje následující obrázek. Adresové prostory Průzkumníka Windows (Explorer.exe), Firefoxu (firefox.exe) a Poznámkového bloku (Notepad.exe) sdílí svoji horní část (adresy 0x80000000-0xffffffff). Dolní část svého prostoru má každý proces sám pro sebe.

Procesy sdílí horní část svých adresových prostorů

Ačkoliv by se tedy mohlo zdát, že přístup do libovolného adresového prostoru (protože ovladače opravdu mohou mezi adresovými prostory přecházet dle svých přání) znamená pro autory ovladačů obrovskou moc, prakticky této výhody příliš nevyužívají. Zejména pokud mluvíme o ovladačích, jejichž cílem je stabilní běh na co největším množství verzí Windows. Obvykle je totiž kontexty procesu a vlákna vůbec nezajímají.

Stránkovaná a nestránkovaná paměť

Podobně jako každá aplikace, i jádro disponuje haldou, ze které mohou ovladače alokovat paměť, případně ji tam vracet, pokud ji již nepotřebují. Hlavní rozdíl spočívá ve faktu, že ovladače mohou alokovat z různých hald (poolů), přičemž zásadní jsou následující dva:

  • Nestránkovaná halda (nonpaged pool) obsahuje bloky, které se vždy budou nacházet ve fyzické paměti, nikdy neodcestují na disk do stránkovacího souboru. Nejdůležitější vlastností takto alokované paměti není to, že její čtení a zápisy budou vždy rychlé (nebude třeba ji hledat na disku), ale její přítomnost i za okolností, kdy se do stránkovacího souboru podívat nelze. Příkladem takových okolností jsou obslužné rutiny přerušení, jenž musí proběhnout vždy co možná nejrychleji a neměly by být příliš složité. Nestránkovaná paměť by měla být využívána pouze v oprávněných případech, protože její množství je omezeno velikostí RAM počítače. Nepodílí se na iluzi budované mechanismy virtuální paměti.
  • Stránkovaná halda (paged pool) disponuje naopak bloky, které mohou do stránkovacího souboru odcestovat kdykoliv. V podstatě se jedná o ekvivalent haldy běžných aplikací spravované funkcemi malloc() a free().

I aplikace může jádro systému instruovat, aby s určitou částí jejího adresového prostoru zacházelo jako z nestránkovanou haldou (tzn. neodkládalo její obsah na disk). K tomuto účelu slouží funkce VirtualLock().

Obsluha výjimek

Vyvolávání a ošetřování výjimek jistě patří mezi oblíbené postupy nejen v C++, ale i ve vyšších programovacích jazycích (Java, C#, Python ...). Pro příznivce tohoto stylu programování představuje prostředí jádra Windows velké zklamání – mechanismus výjimek není příliš podporován, rozhodně ne dostatečně pro C++, ve kterém jinak ovladače programovat lze.

Jak demonstruje následující kód, podpora výjimek přecejen není nulová. Místo slov try a except se používají alternativy __try a __except. V případě, že chceme výjimku vyhodit, zavoláme funkci ExRaisestatus().

__try {
        // . . . Normální běh . . .
__except (EXCEPTION_EXECUTE_HANDLER) {
        // . . . Obsluha výjimky . . .
}

Až na několik funkcí je API jádra založeno čistě na chybových kódech – zda určitá funkce uspěla, se ovladač dozví z její návratové hodnoty. Snad jedinou výjimku tvoří případ, kdy ovladač reaguje na požadavek aplikace a potřebuje přenést jí specifikovaná data do paměti jádra.

Jelikož aplikace nemůže číst ani zapisovat do paměti jádra, předává potřebná data (například jméno souboru, který má být otevřen) tak, že jádru sdělí jejich adresu a délku. Jádro (nebo příslušný ovladač) pak musí z této adresy data překopírovat mimo dosah aplikace, případně je zabezpečit jiným způsobem. Aplikace totiž může v době, kdy jádro s jejími daty pracuje:

  • měnit jejich obsah,
  • měnit oprávnění příslušných paměťových stránek (a tak zamezit zápisu),
  • paměť uvolnit, takže adresa předaná jádru přestane být platná.

Naštěstí všechny tyto ošklivé případy je možné řešit obsluhou výjimek, která v tomto případě funguje, jak má. Pokud se během kopírování do paměti jádra daný buffer stane neplatným, je vyvolána výjimka STATUS_ACCESS_VIOLATION, kterou jádro (či ovladač) zachytí a obslouží. Pokud se však ovladač pokusí přistoupit na neplatnou adresu v paměti jádra, ani sebelepší obsluha výjimek jej nezachrání před modrou obrazovkou smrti.

Neexistuje žádný způsob, jak ovladač může zjistit, zda je určitá adresa v paměti jádra platná. Může se maximálně dozvědět, zda přístup na takovou adresu vyvolá výpadek stránky (funkce MmIsAddressValid()). Výpadek stránky ale nemusí znamenat, že je daná adresa neplatná; příslušná oblast paměti se může nacházet ve stránkovacím souboru na disku. Zde uvedená funkce navíc nezaručuje, zda daná adresa nevyvolá výpadek stránky hned poté, co vrátí řízení volajícímu.


 

 

Článek pro vás napsal Martin Dráb
Avatar
Jak se ti líbí článek?
2 hlasů
Autor se věnuje studiu obecné teorie operačních systémů, vnitřnímu uspořádání jádra OS Windows, trochu také matematice a šifrování
Miniatura
Všechny články v sekci
Pokročilé postupy pro Windows
Miniatura
Následující článek
Specifika vývoje ovladačů 3
Aktivity (2)

 

 

Komentáře

Avatar
jiri.sada
Redaktor
Avatar
jiri.sada:5. června 22:08

úžasná série, doufám v další článek! xD

 
Odpovědět  +1 5. června 22:08
Avatar
Martin Dráb
Redaktor
Avatar
Odpovídá na jiri.sada
Martin Dráb:5. června 22:50

úžasná série, doufám v další článek! xD

Již je v procesu schvalování. Tématicky není tak rozmanitý, neb pokrývá jen jedno téma, které je ale prostupuje +- všemi oblastmi vývoje ovladačů (navíc se mi jeho popis hodí v připravovaném seriálu, ve kterém si krůček po krůčku naprogramujeme jednodušší ovladač). Původně bylo toto téma zahrnuto do tohoto článku, ale tradičně jsem se nevešel do limitu na počet znaků. :-)

Odpovědět  +1 5. června 22:50
2 + 2 = 5 for extremely large values of 2
Avatar
jirisalek
Člen
Avatar
jirisalek:13. června 22:21

Dobrá práce ;-) Už netrpělivě vyhlížím vydání dalšího článku :-)

 
Odpovědět 13. června 22:21
Avatar
Martin Dráb
Redaktor
Avatar
Odpovídá na jirisalek
Martin Dráb:13. června 22:27

Pokud myslíš díl následující po tomto, tak
https://www.itnetwork.cz/…e-ovladacu-3

Odpovědět 13. června 22:27
2 + 2 = 5 for extremely large values of 2
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 4 zpráv z 4.