Specifika vývoje ovladačů 3
V minulém dílu Specifika vývoje ovladačů 2 jsme se zaměřili na paměť. V tomto díle miniseriálu o různých aspektech vývoje ovladačů se budeme zabývat pouze jedním tématem, které prostupuje celým jádrem a každý vývojář ovladačů by s ním měl být dobře seznámen. Jedná se o mechanismus hardwarových priorit.
Na rozdíl od plánovací priority vláken (a potažmo procesů), hardwarová priorita vlákna neudává, jak často mu bude přidělen procesor, ale jaké úkony může vykonávat. Přitom platí, že kód běžící s hardwarovou prioritou X může být přerušen (přeplánován) pouze kódem s hardwarovou prioritou Y > X. Čím vyšší hardwarovou prioritou vlákno disponuje, tím méně věcí může provádět, ale tím menší množství událostí může přerušit jeho běh. Termín hardwarová priorita je mnou vymyšlený český ekvivalent sousloví interrupt request level (IRQL), jež se používá v dokumentaci.
Existuje 32 různých hardwarových priorit pro 32bitová a 16 pro 64bitová
Windows. Nejdůležitější hodnoty jsou 0 (PASSIVE_LEVEL
), 1
(APC_LEVEL
), 2 (DISPATCH_LEVEL
) a 31 (resp. 15)
(HIGH_LEVEL
). Tabulka 1 popisuje jaká omezení tyto priority
kladou na vykonávaný kód.
Tabulka 1: Přehled nejdůležitějších IRQL a jejich omezení
PASSIVE_LEVEL | APC_LEVEL | DISPATCH_LEVEL | HIGH_LEVEL | |
---|---|---|---|---|
Pasivní čekání | ANO | ANO | NE | NE |
Aktivní čekání | ANO | ANO | ANO | ANO |
Stránkovaná paměť | ANO | ANO | NE | NE |
Alokace paměti | ANO | ANO | ANO* | NE |
API | ANO | NE | NE | NE |
Vlákno pasivně čeká, pokud není naplánováno na
procesoru a zároveň pro jeho možný běh je třeba splnit nějakou další
podmínku, například dokončit čtení dat z disku. Pasivní čekání je
nutnou podmínkou pro téměř všechny nevýpočetní činnosti a samozřejmě
pro samotný plánovač vláken. Jeho nedostupnost od
DISPATCH_LEVEL
výše velmi omezuje možnosti, které vývojář
ovladače na dané hardwarové prioritě má.
Při Aktivním čekání vlákno běží na procesoru a neustále testuje, zda je určitá podmínka splněna či stále ještě ne. Jelikož tento druh čekání nevyžaduje žádnou podporu od operačního systému, je možné jej provádět na libovolné IRQL.
Jak bylo řečeno v minulém dílu, stránkovaná paměť může být
systémem odložena na disk a odtud načtena až v okamžiku potřeby. Jelikož
pro takový úkon je potřeba na načtení dat (pasivně) počkat, ke
stránkované paměti nelze přistupovat a ani ji (de)alokovat na IRQL >=
DISPATCH_LEVEL
.
Do oblasti nestránkované paměti (případně stránkované paměti, které
bylo zakázáno odebrat se na disk) je možné přistupovat na libovolné HW
prioritě. Jediné omezení platí pro její (de)alokaci, tyto činnosti je
nutné provádět maximálně na APC_LEVEL
(DISPATCH_LEVEL
pro nestránkovanou).
Na úrovni DISPATCH_LEVEL
je vykonávána většina rutin
plánovače. Díky tomu je možné například přeplánovat vlákno běžící
na PASSIVE_LEVEL
jiným vláknem na stejné hardwarové prioritě,
ač to poučka uvedená výše zdánlivě zakazuje. Trik spočívá v tom, že
plánovač IRQL zvýší na DISPATCH_LEVEL
, provede vlastní
přeplánování a následně ji sníží na úroveň nově naplánovaného
vlákna. Dočasné zvýšení IRQL patří mezi legitimní kroky, nedochází
tedy k porušení žádného jaderného zákona.
Obrázek níže ilustruje situaci popsanou v předchozím odstavci. Na
procesoru nejprve běží vlákno A
na IRQL APC_LEVEL
.
Po určité době plánovač usoudí, že nastal čas na změnu; IRQL se
zvýší na DISPATCH_LEVEL
a dojde k naplánování vlákna
B
. Plánovač pak svoji intervenci ukončí a nechá běžet
vlákno B
. IRQL klesne na poslední známou hodnotu pro vlákno
B
, což je v tomto případě PASSIVE_LEVEL
. Díky
dočasnému zvýšení na DISPATCH_LEVEL
nedošlo k porušení
žádného jaderného pravidla.
Z předchozího odstavce také vyplývá, že pokud vlákno zvedne HW
prioritu nad APC_LEVEL
, nemůže jej z aktuálního procesoru nikdo
"vyštípat". Může být krátkodobě přerušeno jinými událostmi
nastávajícími na vyšších IRQL (hardwarová přerušení, časovač,
meziprocesorová komunikace...), ty jej ale nemohou přeplánovat. Z tohoto
důvodu na vyšších IRQL nedává příliš smysl mluvit o vláknech, ale jen
o procesorech.
Vzhledem k výše popsaným omezením je cílem každého ovladače
vykonávat maximum kódu na IRQL PASSIVE_LEVEL
(přinejhorším na
APC_LEVEL
). Pro sestoupení z vyšších IRQL na nižší existují
dokumentované postupy, jenž spočívají v tom, že ovladač naplánuje
operaci, kterou potřebuje vykonat na nízké IRQL, na pozdější dobu a
vrátí řízení jádru systému. Na provedení operace nemůže počkat, neb
služby plánovače nejsou na vyšších IRQL dostupné. Tyto mechanismy se
nazývají odložené volání procedury a pracovní vlákna.
Odložené volání procedury (Deferred Procedure Call –
DPC) slouží k snížení hodnoty IRQL na DISPATCH_LEVEL
.
Nejčastější použití nalezneme v obslužných rutinách přerušení.
Například stisknutí klávesy na notebookové klávesnici vyvolá hardwarové
přerušení, které zpracovává ovladač i8042prt.sys
. Jeho
obslužná rutina je, jak to u přerušení bývá, volána na IRQL >
DISPATCH_LEVEL
, a tak jsou její možnosti velmi omezené. Ovladač
tedy pouze zjistí, k jaké události klávesnice došlo (včetně toho, jaká
klávesa byla stisknuta) a požádá systém, aby, až IRQL dostatečně klesne,
zavolal určitou funkci a předal jí zjištěné informace v parametrech. Tím
vlastně odloží zpracování informací na příhodnější dobu.
Bezesporu není bez zajímavosti, že si ovladač může vybrat procesor, na
kterém má být odložená procedura vykonána.
Pokud při zpracování informací ovladač usoudí, že úroveň
DISPATCH_LEVEL
je pro něj stále příliš vysoká, může
požádat o pomoc některé z tzv. pracovních vláken (worker
threads) – množiny vláken běžících na IRQL PASSIVE_LEVEL
a
vykonávajících práci, kterou jim zadá libovolná komponenta jádra. Práce
v tomto případě znamená volání funkce se zadaným parametrem. Mechanismus
je tedy navenek značně podobný DPC. Jakmile se ovladač dostane na
PASSIVE_LEVEL
, může například provádět zápisy do registru
či do souboru, což jsou operace na vyšších IRQL zakázané.
Systém se ovladače pro vykonávání maxima kódu na nízkých IRQL snaží donutit také tím, že omezuje časový interval, který lze na vyšších úrovních strávit. Pokud jej ovladač překročí, dochází k modré obrazovce smrti. Vzhledem k tomu, že ovladače jsou vlastně DLL knihovny, které systém zavolá, když je potřeba, splnění tohoto požadavku obvykle nepředstavuje velký problém – ovladač udělá, co musí, a v případě potřeby použije mechanismus pro snížení IRQL a předá řízení zpět volajícímu.
Časový limit pro běh kódu na IRQL >=
DISPATCH_LEVEL
má svůj význam, protože na procesorech s tímto
IRQL nefunguje plánovač, což narušuje iluzi současného vykonávání mnoha
úkolů.