IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Pár faktů a mýtů o vývoji ovladačů

Pokud se někde v odborněji zaměřené společnosti zmíním, že se zabývám programováním ovladačů pro Windows, obvykle se o této profesi dozvím různé zajímavé, ale často ne příliš pravdivé, postřehy. Na tom obecně není nic špatného – každý si o oboru, který nezná, utvoří nějakou, často ne úplně správnou, představu. Cílem tohoto článku je zmínit některé z mýtů a zvěstí, které kolem vývoje ovladačů panují, a uvést je na pravou míru.

Vývoj ovladačů znamená programování v Assembleru

Tento mýtus patří mezi ty nejrozšířenější a vychází z faktu, že ovladače operují na nižší úrovni než běžné aplikace a knihovny. I přesto však znalost Assembleru nepatří mezi nutné podmínky pro jejich programování. Stejně jako aplikace, i ovladače mají k dispozici celou řadu funkcí a maker poskytovaných jádrem operačního systému, které zapouzdřují vše potřebné.

Pro programování ovladačů se používá zejména jazyk C. S trochou opatrnosti a rozvahy lze využít i mnohé koncepty C++ (OOP, virtuální metody, některé věci z šablon), ač takový postup není Microsoftem oficiálně podporovaný. Teoreticky je možné vyvíjet ovladače v libovolném jazyce, který lze zkompilovat do strojového kódu. V minulosti zde byla například úspěšná snaha použít Object Pascal. Překladač Delphi přeložil zdrojové kódy do souborů OBJ, které byly slinkovány do výsledného ovladače nástrojem od Microsoftu. Hlavní nevýhoda takového řešení spočívala v absenci deklarací funkcí, maker a konstant pro Delphi.

Je pravda, že kód ovladačů je vykonáván v tzv. privilegovaném režimu procesoru (ring 0), což dává vývojáři možnost využít i instrukcí pro běžné aplikace běžící v uživatelském režimu (ring 3) zapovězených. Takové instrukce dovolují přímo komunikovat po sběrnici či měnit důležitá natavení procesoru. I v takovém případě ale obvykle k Assembleru přímo sáhnout nemusíte, jelikož jsou příslušné instrukce poskytovány prostřednictvím speciálních funkcí (např. __debugbreak pro vyvolání breakpointu, __readcr0 pro přečtení hodnoty registru CR0).

Použití Assembleru často značí, že daný ovladač plní nestandardní úkon, který lze také označit slovem neplecha. Mezi v minulosti velmi oblíbená použití Assembleru patřilo vynulování bitu WP v registru CR0, které dovolilo zapisovat i do oblastí paměti s oprávněním jen pro čtení. Takové počínání může mít ale nepředvídané následky, protože se o něm operační systém nedozví (registr CR0 patří přímo procesoru) a na předpokladu, že ochrana proti zápisu funguje správně (tzn. pokus o něj vyvolá výjimku), je založena například implementace optimalizace copy on write. Zapisovat do paměti chráněné proti změnám lze i prostřednictvím dokumentovaného API jádra (v tomto případě OS o všem ví), ale z nějakého důvodu se tato metoda tak nerozšířila. Asi proto, že vyžaduje o několik řádků kódu navíc.

Poznámka: Copy on write je technika sloužící k efektivnímu sdílení prostředků (zejména paměti). Asi nejčastěji se s jejím použitím setkáme u systémových knihoven. Zjednodušeně řečeno, všechny aplikace používající určitou knihovnu mezi sebou sdílí jednu její instanci. Pokud některá z aplikací obsah této instance změní, operační systém jí tiše vytvoří privátní kopii upravené oblasti. Ostatní aplikace dál sdílí původní obsah a změnu neuvidí. Vzhledem k tomu, že změny některých části knihovny (zejména jejího kódu) nepatří mezi obvyklé praktiky, dochází k úspoře paměti. Aby však operační systém mohl pokus o zápis do paměti podléhající copy on write detekovat, musí být taková oblast označena oprávněním jen pro čtení, které musí fungovat. Jinak dochází k přímému přepsání oné jediné instance sdílené mezi všemi aplikacemi, jež takovou změnu "pod rukama" obvykle oslaví vlastním pádem.

Schéma copy on write - Pokročilé postupy pro Windows

Je to těžké, protože Windows nejsou open source

Je pravda, že v případě pochybností se do zdrojových kódů Windows opravdu podívat nemůžeme. Na druhou stranu k tomu málokdy existuje reálný důvod. Jak bylo řečeno výše, jádro poskytuje ovladačům spoustu dokumentovaných funkcí, kterých mohou využít při plnění svých úkolů. Myslím si, že jejich dokumentace patří mezi to nejlepší, na co lze ve světě closed i open source produktů narazit. Samozřejmě, existují i místa, která by si zasloužila vylepšení, ale obecně lze úroveň dokumentace považovat za velmi vysokou.

Velkou výhodu rozhraní poskytovaného jádrem Windows představuje jeho stabilita. Nové verze operačního systému obvykle přináší pár nových funkcí, ale staré zachovávají, takže i ovladače psané pro Windows XP poběží i na Windows 10. Samozřejmě za předpokladu, že nevyužívají nedokumentovaného chování, nebo netrpí problémem, který se díky specifické implementaci jádra na Windows XP neprojevoval (a tak nemohl být odstraněn).

Výše uvedené tvrzení má své limity. Velká část rozhraní tu s námi opravdu je již od Windows XP, existují ale i oblasti, jež doznaly velkých změn. Například pokud se náš ovladač podílel ve Windows XP na síťové komunikaci (ať už posíláním/při­jímáním paketů, nebo monitorováním aktivity běžných aplikací), mohl pro tyto účely využívat rozhraní Transport Driver Interface (TDI). To ale bylo ve Windows Vista označeno jako zastaralé (deprecated) a nahradilo jej daleko mocnější Windows Filtering Platform (WFP). Následek není ten, že by ve Windows Vista a novějších TDI neexistovalo a nefungovalo, jen není oficiálně podporováno, takže některé jeho pokročilejší vlastnosti již nemusí platit. Pravda je taková, že TDI bylo reimplementováno pomocí WFP a například pro odesílání/přijímání paketů fungovalo dobře i na Windows 8.1 (Windows 10 jsem netestoval).

Fakt, že implementace velké části jádra dokumentována není, nutí autory ovladačů používat pouze dokumentovaná rozhraní, pokud to jen trochu jde. Díky tomu pak ovladače mohou fungovat i v případě, že se implementace určitého mechanismu jádra úplně změní. Myslím si, že kdyby jádro bylo celé open source, závislost ovladačů na konkrétní implementaci by byla vyšší. I bez zdrojového kódu lze zjistit, jak jádro funguje "pod pokličkou", ale takový postup je dost náročný na to, aby autor ovladače pečlivě uvážil, zda mu riziko nekompatibility s novými verzemi Windows vyváží výhody spojené s využitím určitého nedokumentovaného chování.

Navíc se úplně nedá říci, že by Windows byly naprosto closed source, a to i dlouho předtím než Microsoft začal publikovat na GitHubu. Součástí balíku pro vývoj ovladačů Windows Driver Kit (WDK) je i sada ukázkových ovladačů (včetně zdrojových kódů). Některé z nich jsou použity i v každé běžící instanci Windows. Jedná se například o:

  • ovladače souborových systémů FAT a CDFS (fastfat.sys, cdfs.sys),
  • obecné ovladače klávesnice, myši, disku a CD-ROM (kbdclass.sys, mouclass.sys, disk.sys, cdrom.sys)
  • Windows Driver Fremework (WDF) zjednodušující vývoj Plug&Play ovladačů (zveřejněno na GitHubu).

Pokud tedy píšeme ovladač, obvykle je možnost podívat se do kódu produktu podobného zaměření.

Funkce main

Z programování běžných aplikací jsme zvyklí, že celý program vlastně začíná a končí ve funkci main(). Ta obdrží parametry příkazové řádky, případně hodnoty proměnných prostředí, provede to, co od aplikace požadujeme a skončí. Její konec znamená v podstatě ukončení celé aplikace.

Programování ovladačů z tohoto pohledu odpovídá tvorbě dynamických knihoven. Žádná funkce main() neexistuje. Podobné postavení zaujímá funkce DriverEntry(), kterou jádro zavolá těsně po načtení ovladače do paměti. Jejím úkolem není provést to, co od ovladače očekáváme, ale pouze jej inicializovat a skončit. Návratová hodnotou ovladač informuje operační systém, zda se mu inicializace podařila. V záporném případě je odstraněn z paměti.

Pokud chceme, aby ovladač fungoval v podobném režimu, jako funkce main() v běžné aplikaci, musíme v rámci DriverEntry() (či kdykoliv později) vytvořit samostatné vlákno, jež začne provádět námi požadovanou činnost. Jeho ukončení však na běh ovladače nemá žádný vliv – jádro jej z paměti neodstraní. Ovladač totiž z paměti nikdo neodstraní, pokud on nechce, a i pokud chce, nemůže tak učinit vlastními silami.

V rámci DriverEntry() obvykle ovladač sdělí operačnímu systému, za jakých podmínek mu má předat řízení. Z pohledu OOP vlastně systému předává delegáty (v C se jedná o zpětně volané funkce). Operační systém dovoluje ovladačům reagovat zejména na následující události:

  • souborové a registrové operace,
  • síťová komunikace,
  • přístup k procesům a vláknům,
  • detekce připojení nového zařízení,
  • komunikace s určitým typem zařízení,
  • změna systémového času, napájení...

Podrobněji o těchto možnostech a dalších zajímavostech kolem vývoje ovladačů si ale povíme zase někdy příště.


 

Všechny články v sekci
Pokročilé postupy pro Windows
Článek pro vás napsal Martin Dráb
Avatar
Uživatelské hodnocení:
13 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í
Aktivity