přejít na obsah přejít na navigaci

Linux E X P R E S, Vývoj jádra I.

Vývoj jádra I.

Napsat si ovladač pro zařízení, souborový systém nebo něco jiného? V Linuxu 2.6 je to podle Lukáše Jelínka velmi snadné! Začíná jedinečný seriál o programování jádra.


Alespoň pro toho, kdo má zkušenosti s programováním v jazyce C, nechybí mu chuť experimentovat, poznávat nové věci a také trochu schopnosti promýšlet věci v širších souvislostech. To všechno je totiž potřeba k tomu, aby se nová součást stala přirozenou součástí linuxového jádra - minimálně toho vašeho. Pokud budete chtít, a pokud váš příspěvek vyhoví tvrdým kritériím pro přijímání nových modulů do oficiálního jádra, může se nakonec usídlit v milionech počítačů po celém světě.

Je mnoho důvodů, proč se člověk rozhodne, že si napíše nějakou součást (modul) do jádra, případně upraví nějaký už existující. Může to být neexistence ovladače pro nějaké zařízení, touha zavést nebo zlepšit podporu pro méně obvyklý souborový systém, nebo třeba jen dobrý pocit z toho, že se člověk stává součástí elitní skupiny vývojářů jádra. Nápad je to ale vždy dobrý. Modulů (a těch kvalitních zvláště) není nikdy dost, proto každý nový je vždy přínosem, minimálně pro toho, kdo si dá práci s naprogramováním tohoto "malého velkého díla". Každý dobře napsaný modul totiž velkým dílem bezesporu je - bez ohledu na svůj rozsah.

Základní architektura Linuxu

Slasti a strasti vývojáře jádra

Na začátku jsem se snažil navnadit na jednoduchost vývoje, ale skutečnost je trochu složitější. Problém je totiž v tom, že neexistuje pořádná referenční dokumentace k základním funkcím jádra ani k mnohým modulům, které se často používají. Na druhou stranu je k dispozici zdroj informací z největších: jsou to zdrojové kódy jádra a všech obsažených modulů. Sice je to s úrovní jejich "okomentovanosti" různé, v každém případě se z nich lze mnohému naučit. A zrovna v tomto případě se to hodí dvojnásob, je to totiž příležitost poznat, jak je jádro napsáno a jaké techniky se v jeho kódu používají. Dobrým pomocníkem je hypertextová forma zdrojových kódů jádra (k dispozici na lxr.linux.no), kde jsou jednotlivé soubory provázány přes použité symboly (funkce, proměnné, konstanty apod.) tak, že to výrazně usnadňuje orientaci v kódu. Také vřele doporučuji knihu Linux Device Drivers (3. vydání) - to je základní příručka vývojáře jádra a lze z ní načerpat velké množství informací.

Pozor ovšem na starší informace a rady k jádrům před 2.6 - poněkud (většinou k lepšímu) se změnilo rozhraní a řada věcí z dřívějška už tedy neplatí.

Co patří do jádra?

První věcí, kterou musí udělat každý, kdo se rozhodne napsat něco do jádra, je závažné rozhodnutí - patří to tam vůbec? A pokud ano, tak co všechno? Nebylo by lepší to udělat v rámci normálního programu? Že je něco součástí jádra, znamená pro tento kód řadu omezení: Funkce budou z programů přístupné pouze pomocí volání jádra (tzv. syscall), ať už existující či úplně nového. To mj. znamená, že se pro každé volání procesor přepne z uživatelského (user mode) do privilegovaného (kernel mode) režimu a zpět, což znamená určitou časovou ztrátu.

Tzv. syscall znamená, že se pro každé volání procesor přepne z uživatelského (user mode) do privilegovaného (kernel mode) režimu a zpět, což znamená určitou časovou ztrátu.

User mode je uživatelský (neprivilegovaný) režim procesoru, pojem kernel mode značí režim jádra (privilegovaný režim). V režimu jádra lze provádět všechny instrukce, přistupovat kamkoliv do paměti atd., kdežto v uživatelském režimu je sada instrukcí omezena a lze přistupovat pouze do adresního prostoru daného procesu. Přiřazení těchto režimů konkrétním režimům procesoru se na různých platformách liší.

Data z programu nejsou přímo dostupná. Přistupuje se k nim buď přes zvláštní funkce, anebo přes namapovaný virtuální adresní prostor.

Celé jádro sdílí jeden adresní prostor, do kterého lze přistupovat bez omezení. Jakákoli chyba může poškodit data v jiné části jádra, s vážnými dopady na celý systém. Všechno, co v jádře děláme, by mělo trvat co nejkratší dobu. Nadměrně dlouhé pobývání zejména v kritických sekcích má vážné dopady na výkonnost systému. Navíc zde případná nekonečná smyčka způsobí beznadějné zatuhnutí.

Je potřeba počítat s možností, že se systémové volání přeruší a adekvátně na to reagovat (podle toho, o jaké systémové volání se jedná).

Protože jádro 2.6 lze zkompilovat jako preemptivní, musíme počítat s přepnutím kontextu prakticky kdykoli, s výjimkou situací, kdy je to explicitně zakázáno. V jádře je bezpodmínečně nutné po sobě vždy dokonale uklidit. Nikde nesmí zůstat neuvolněná alokovaná paměť ani přidělené prostředky všeho druhu.

Při preemptivním multitaskingu o přidělování a odebírání časových kvant jednotlivým úlohám plně rozhoduje operační systém. To ale neznamená, že o přepnutí kontextu nemohou procesy požádat samy (například při čekání na I/O operaci). Výhodou tohoto řešení je, že nedochází k "zatuhnutí" počítače, neboť i v případě, že úloha zhavaruje, odebere operační systém dané úloze řízení a přidělí časové kvantum ostatním úlohám. (Zdroj Wikipedia.org)

Množina funkcí, které můžeme používat, je poměrně omezená. Nemáme k dispozici rozsáhlé knihovny a aritmetiku pohyblivé řádové čárky.

Uvedený seznam je sice dlouhý, ale nikoli vyčerpávající. Jeho smyslem je, aby si každý několikrát promyslel, co má smysl do jádra dávat (zda to nejde vyřešit jinak).

Běh v kontextu procesu znamená, že se vykonává kód systémového volání, které bylo zavoláno z procesu. Kód v jádře se ale často vykonává i mimo kontext, např. jako obsluha přerušení, odložené zpracování nebo reakce na událost časovače.

V čem psát

Teď nechci hovořit o vývojovém prostředí, kompilaci atd. - o tom bude řeč příště. Nyní jde o něco mnohem závažnějšího, a sice o programovací jazyk. I když to bude leckomu nepříjemné, jediným prakticky použitelným jazykem k vývoji součástí jádra je "obyčejné" C. Proč nejde použít třeba oblíbené C++? Důvodů je několik:

Jednak je to samotný proces kompilace. Zatímco kód psaný v C se do strojového jazyka překládá poměrně přímočaře, u C++ toto rozhodně neplatí - výsledek kompilace není moc predikovatelný. U jádra si takové rozmary nemůžeme dovolit, tam musí být všechno vysoce spolehlivé.

Pak tu máme práci s výjimkami (pokud je chceme v C++ používat). Sice existují postupy, jak používat výjimky v jádře, nicméně nejsou příliš zvládnuté a opět tu hraje roli chování kompilátoru.

A konečně posledním hlavním důvodem je závislost na standardní knihovně. Tu lze vyřešit statickým slinkováním, ale tím si zbytečně zanášíme do jádra mnoho zbytečného kódu.

"Desatero" vývojáře jádra

Nebude to sice zrovna deset pravidel, ale to nic nemění na faktu, že by to měly být železné zásady, které se nevyplácí porušovat. Každé provinění bývá nemilosrdně trestáno. Dobré rozhraní modulu navrhneš. Rozhraní musí být jasné ještě před začátkem implementace. To se týká jak systémových volání, tak symbolů exportovaných pro použití v jiných částech jádra.

Svůj kód čistý a přehledný udržovati budeš. Je to důležité z hlediska prevence chyb, pro ulehčení pozdějších změn a také pro studium chování (pokud se modul stane součástí oficiálního jádra). Také je dobré vyhýbat se příliš komplikovaným konstrukcím. Nové systémové volání nevytvoříš. Pokud není nějaký mimořádně závažný důvod, je nanejvýš vhodné používat pouze existující systémová volání. Přidání nového volání vyžaduje modifikaci tabulky volání a diskvalifikuje tím modul z širšího nasazení.

Na dobrou přenositelnost hleděti budeš. Kromě ovladačů pro jedinou platformu je nejlepší psát vše přenositelně - aby to fungovalo v co nejvíce případech. Je třeba věnovat péči zejména délce datových typů.

Co není tvoje, na pokoji necháš. Modul by měl zásadně přímo přistupovat pouze ke svým datům - k cizím pouze určeným způsobem, obvykle voláním příslušných funkcí.

Pamětí pečlivě šetřiti budeš. Nemá smysl alokovat si zbytečně paměť, která nebude využita. Je lépe nechat vše na správci paměti a jen ve zcela ojedinělých případech si (správným způsobem) připravovat paměť dopředu.

Nikde zdržovati se nebudeš. Jak už to bylo řečeno, každé zdržení může mít těžké následky. Mnohonásobně to platí při obsluze přerušení a podobných situacích. Každou trochu časově náročnější operaci je dobré pořádně zvážit a případně zvolit jiné, rychlejší řešení. Chyby vždy řádně hlásiti budeš. Pokud během vykonávání kódu modulu dojde k chybě, mělo by to být uživatelskému programu řádným způsobem ohlášeno. Způsob se liší podle konkrétního volání, nikdy to však nelze vynechat.

Vždy po sobě řádně uklidíš. Všechna alokovaná paměť a přidělené prostředky systému se musí bezpodmínečně uvolňovat. Posledním okamžikem je uvolnění modulu z paměti, ale co nepotřebujeme ponechávat, je vhodné uklidit ještě před opuštěním daného systémového volání.

O synchronizaci přístupu se postaráš. S každým sdíleným prostředkem se musí pracovat bezpečně z hlediska možného souběhu přístupů. Je však třeba dávat pozor na riziko vzájemného zablokování (deadlock).

Nikomu a ničemu věřiti nebudeš. Co z uživatelského prostoru jest, od ďábla jest. Ke všem datům takto získaným je třeba přistupovat zvlášť opatrně - mohou být nechtěně (nebo i úmyslně) vadná. Jakkoli špatná data nesmějí ohrozit fungování jádra.

Procesor usurpovati si nebudeš. V jádře nelze nikdy spoléhat na to, že plánovač procesu odejme procesor. Naopak, kód musí být napsán tak, aby se procesy vzájemně nebrzdily, a to bez ohledu na počet procesorů a na to, zda bylo jádro zkompilováno jako preemptivní.

Pokračování příště, seriál pokračuje v aktuálním tištěném čísle.

Nahoru

Odkazy

Přidat téma diskuse

Nejsou podporovány žádné značky, komentáře jsou jen čistě textové. Více o diskuzích a pravidlech najdete v nápovědě.
Diskuzi můžete sledovat pomocí RSS kanálu rss



 
 

Top články z OpenOffice.cz