Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
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 faktov a mýtov o vývoji ovládačov

Ak sa niekde v odbornejšie zamerané spoločnosti zmienim, že sa zaoberám programovaním ovládačov pre Windows, zvyčajne sa o tejto profesii dozviem rôzne zaujímavé, ale často nie príliš pravdivé, postrehy. Na tom všeobecne nie je nič zlé - každý si o odbore, ktorý nepozná, utvorí nejakú, často nie úplne správnu, predstavu. Cieľom tohto článku je spomenúť niektoré z mýtov a zvestí, ktoré okolo vývoja ovládačov panujú, a uviesť ich na pravú mieru.

Vývoj ovládačov znamená programovanie v Assemblera

Tento mýtus patrí medzi tie najrozšírenejšie a vychádza z faktu, že ovládače operujú na nižšej úrovni než bežné aplikácie a knižnice. Aj napriek tomu však znalosť Assemblera nepatrí medzi nutné podmienky pre ich programovanie. Rovnako ako aplikácie, aj ovládače majú k dispozícii celý rad funkcií a makier poskytovaných jadrom operačného systému, ktoré zakukľujú všetko potrebné.

Pre programovanie ovládačov sa používa najmä jazyk C. S trochou opatrnosti a rozvahy možno využiť aj mnohé koncepty C ++ (OOP, virtuálne metódy, niektoré veci z šablón), hoci takýto postup nie je Microsoftom oficiálne podporovaný. Teoreticky je možné vyvíjať ovládača v ľubovoľnom jazyku, ktorý možno skompilovať do strojového kódu. V minulosti tu bola napríklad úspešná snaha použiť Object Pascal. Prekladač Delphi preložil zdrojové kódy do súborov OBJ, ktoré boli slinkovány do výsledného ovládača nástrojom od Microsoftu. Hlavná nevýhoda takéhoto riešenia spočívala v absencii deklaráciou funkcií, makier a konštánt pre Delphi.

Je pravda, že kód ovládačov je vykonávaný v tzv. Privilegovanom režime procesoru (ring 0), čo dáva vývojári možnosť využiť aj inštrukcií pre bežné aplikácie bežiace v užívateľskom režime (ring 3) zakázaných. Také inštrukcie dovoľujú priamo komunikovať po zbernici či meniť dôležitá natavenie procesora. Aj v takom prípade ale zvyčajne k Assemblera priamo siahnuť nemusíte, pretože sú príslušné inštrukcie poskytované prostredníctvom špeciálnych funkcií (napr. __debugbreak pre vyvolanie Breakpoint, __readcr0 pre prečítanie hodnoty registra CR0).

Použitie Assemblera často značí, že daný ovládač plní neštandardný úkon, ktorý možno tiež označiť slovom neplecha. Medzi v minulosti veľmi obľúbená použitie Assemblera patrilo vynulovanie bitu WP v registri CR0, ktoré dovolilo zapisovať aj do oblastí pamäte s oprávnením len na čítanie. Takéto počínanie môže mať ale nepredvídané následky, pretože sa o ňom operačný systém nedozvie (register CR0 patrí priamo procesora) a na predpoklade, že ochrana proti zápisu funguje správne (tzn. Pokus o neho vyvolá výnimku), je založená napríklad implementácia optimalizácia copy on write. Zapisovať do pamäte chránenej proti zmenám možno aj prostredníctvom zdokumentovaného API jadra (v tomto prípade OS o všetkom vie), ale z nejakého dôvodu sa táto metóda tak nerozšírila. Asi preto, že vyžaduje o niekoľko riadkov kódu navyše.

Poznámka: Copy on write je technika slúžiaca k efektívnemu získavaniu zdrojov (najmä pamäte). Asi najčastejšie sa s jej použitím stretneme u systémových knižníc. Zjednodušene povedané, všetky aplikácie používajúce určitú knižnicu medzi sebou zdieľa jednu jej inštanciu. Ak niektorá z aplikácií obsah tejto inštancie zmení, operačný systém jej ticho vytvorí privátnej kópiu upravené oblasti. Ostatné aplikácie ďalej zdieľa pôvodný obsah a zmenu neuvidí. Vzhľadom k tomu, že zmeny niektorých časti knižnice (najmä jej kódu) nepatrí medzi zvyčajné praktiky, dochádza k úspore pamäti. Aby však operačný systém mohol pokus o zápis do pamäti podliehajúce copy on write detekovať, musí byť taká oblasť označená oprávnením len na čítanie, ktoré musia fungovať. Inak dochádza k priamemu prepísanie onej jedinej inštancie zdieľané medzi všetkými aplikáciami, ktoré takúto zmenu "pod rukami" zvyčajne oslávi vlastným pádom.

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

Je to ťažké, pretože Windows nie sú open source

Je pravda, že v prípade pochybností sa do zdrojových kódov Windows naozaj pozrieť nemôžeme. Na druhej strane k tomu málokedy existuje reálny dôvod. Ako bolo povedané vyššie, jadro poskytuje ovládačom veľa zdokumentovaných funkcií, ktorých môžu využiť pri plnení svojich úloh. Myslím si, že ich dokumentácia patrí medzi to najlepšie, na čo sa dá vo svete closed aj open source produktov naraziť. Samozrejme, existujú aj miesta, ktoré by si zaslúžila vylepšenia, ale všeobecne sa dá úroveň dokumentácie považovať za veľmi vysokú.

Veľkú výhodu rozhrania poskytovaného jadrom Windows predstavuje jeho stabilita. Nové verzie operačného systému obvykle prináša pár nových funkcií, ale staré zachovávajú, takže aj ovládače písané pre Windows XP pobeží aj na Windows 10. Samozrejme za predpokladu, že nevyužívajú nedokumentovaného správanie, alebo netrpí problémom, ktorý sa vďaka špecifickej implementácii jadra na Windows XP neprejavoval (a tak nemohol byť odstránený).

Uvedené tvrdenie má svoje limity. Veľká časť rozhranie tu s nami naozaj je už od Windows XP, existujú ale aj oblasti, ktoré doznali veľkých zmien. Napríklad, ak sa náš ovládač podieľal vo Windows XP na sieťovej komunikácii (či už posielaním / prijímaním paketov, alebo monitorovaním aktivity bežných aplikácií), mohol na tieto účely využívať rozhranie Transport Driver Interface (TDI). To ale bolo vo Windows Vista označené ako zastarané (deprecated) a nahradilo ho ďaleko mocnejší Windows Filtering Platform (WFP). Následok nie je ten, že by vo Windows Vista a novších TDI neexistovalo a nefungovalo, len nie je oficiálne podporované, takže niektoré jeho pokročilejšie vlastnosti už nemusí platiť. Pravda je taká, že TDI bolo reimplementováno pomocou WFP a napríklad pre odosielanie / prijímanie paketov fungovalo dobre aj na Windows 8.1 (Windows 10 som netestoval).

Fakt, že implementácia veľkej časti jadra dokumentovaná nie je, núti autormi ovládačov používať iba dokumentovaná rozhranie, pokiaľ to len trochu ide. Vďaka tomu potom ovládače môžu fungovať aj v prípade, že sa implementácia určitého mechanizmu jadra úplne zmení. Myslím si, že keby jadro bolo celé open source, závislosť ovládačov na konkrétnu implementáciu by bola vyššia. Aj bez zdrojového kódu možno zistiť, ako jadro funguje "pod pokrievkou", ale taký postup je dosť náročný na to, aby autor ovládače starostlivo zvážil, či mu riziko nekompatibility s novými verziami Windows vyváža výhody spojené s využitím určitého nedokumentovaného správania.

Naviac sa úplne nedá povedať, že by Windows boli úplne closed source, a to aj dlho predtým než Microsoft začal publikovať na GitHub. Súčasťou balíka pre vývoj ovládačov Windows Driver Kit (WDK) je aj sada ukážkových ovládačov (vrátane zdrojových kódov). Niektoré z nich sú použité aj v každej bežiaci inštanciu Windows. Jedná sa napríklad o:

  • ovládače súborových systémov FAT a CDFS (fastfat.sys, cdfs.sys),
  • všeobecné ovládače klávesnice, myši, disku a CD-ROM (kbdclass.sys, mouclass.sys, disk.sys, cdrom.sys)
  • Windows Driver Fremework (WDF) zjednodušujúce vývoj Plug & Play ovládačov (zverejnené na GitHub).

Ak teda píšeme ovládač, obvykle je možnosť pozrieť sa do kódu výrobku podobného zamerania.

Funkcie main

Z programovanie bežných aplikácií sme zvyknutí, že celý program vlastne začína a končí vo funkcii main(). Tá obdrží parametre príkazového riadku, prípadne hodnoty premenných prostredia, vykoná to, čo od aplikácie požadujeme a skončí. Jej koniec znamená v podstate ukončenie celej aplikácie.

Programovanie ovládačov z tohto pohľadu zodpovedá tvorbe dynamických knižníc. Žiadna funkcia main() neexistuje. Podobné postavenie zaujíma funkcie DriverEntry(), ktorú jadro zavolá tesne po načítaní ovládača do pamäte. Jej úlohou nie je vykonať to, čo od ovládača očakávame, ale iba ho inicializovať a skončiť. Návratová hodnotou ovládač informuje operačný systém, či sa mu inicializácia podarila. V zápornom prípade je odstránený z pamäte.

Ak chceme, aby ovládač fungoval v podobnom režime, ako funkcia main() v bežnej aplikácii, musíme v rámci DriverEntry() (alebo kedykoľvek neskôr) vytvoriť samostatné vlákno, ktoré začne vykonávať nami požadovanú činnosť. Jeho ukončenie však na beh ovládače nemá žiadny vplyv - jadro ho z pamäte neodstráni. Ovládač totiž z pamäte nikto neodstráni, ak on nechce, a aj ak chce, nemôže tak urobiť vlastnými silami.

V rámci DriverEntry() zvyčajne ovládač oznámi operačnému systému, za akých podmienok mu má odovzdať riadenie. Z pohľadu OOP vlastne systému odovzdáva delegátmi (v C sa jedná o spätne volané funkcie). Operačný systém dovoľuje ovládačom reagovať najmä na nasledujúce udalosti:

  • súborové a registrové operácie,
  • sieťová komunikácia,
  • prístup k procesom a vláknam,
  • detekcia pripojenie nového zariadenia,
  • komunikácia s určitým typom zariadení,
  • zmena systémového času, napájanie ...

Podrobnejšie o týchto možnostiach a ďalších zaujímavostiach okolo vývoja ovládačov si ale povieme zase niekedy nabudúce.


 

Všetky články v sekcii
Pokročilé postupy pre Windows
Článok pre vás napísal Martin Dráb
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
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