Co je potřeba ke kompilaci modulů
V první řadě potřebujeme samozřejmě kompilátor. Není třeba žádný speciální – náš důvěrně známý GCC plně vyhovuje. Předpokládám, že už ho každý má ve svém systému nainstalovaný (což je ostatně nutná podmínka pro kompilaci běžných programů); pokud tomu tak není, stačí doinstalovat z distribuce a nejlépe také aktualizovat na poslední stabilní verzi (ať už 3.x.x, nebo 4.x.x).
Další podmínkou je přítomnost zdrojových souborů jádra. Ty lze získat z oficiálního serveru (ftp.kernel.org), bývají také v distribucích. Některé distribuce obsahují kromě plných zdrojáků také „odlehčenou“ verzi – ta plně postačuje ke kompilaci modulů pro jádro, ke kterému přísluší, ale celé jádro z nich zkompilovat nelze. Pro experimenty stačí i tato odlehčená verze, v pozdějších fázích vývoje je lépe se uchýlit k plným zdrojovým textům. Programový kód musíme mít samozřejmě v čem napsat. Lze použít libovolný editor (vim, Emacs, Kate, jEdit apod.) nebo vývojové prostředí (Eclipse + CDT, KDevelop, Anjuta, Sun Studio atd.) – je to čistě záležitost vaší volby a chuti.
První kroky
Pro kompilaci nejjednoduššího modulu budeme potřebovat dva soubory. Samotný zdroják (prostě normální céčkovský soubor) a soubor Makefile. Postupně se na ně podíváme. Než k tomu ale dojde, bude dobré si připomenout, jak se s moduly jádra pracuje.
Uvažujme modul jako samostatný objekt, který máme někde na disku (typické místo pro nainstalované moduly je pod adresářem /lib/modules), odkud ho můžeme načíst do paměti. Moduly lze načítat prakticky kdykoliv, lze je i uvolňovat (odstraňovat) za běhu jádra. Zda a jak to probíhá, záleží na konfiguraci jádra. Obvykle můžeme moduly načítat v libovolnou chvíli a uvolňovat je také kdykoliv (kromě doby, kdy je někdo používá).
Pro manipulaci s moduly máme dvě skupiny programů: nízkoúrovňové (insmod, rmmod) a „inteligentní“ (modprobe). Ty doplňují ještě programy s informativní funkcí (lsmod, modinfo). Nízkoúrovňové programy pouze tupě vykonávají svou činnost – insmod zavede modul do paměti, rmmod ho uvolní. Program modprobe toho umí mnohem víc (a je také konfigurovatelný), v pravý čas se na něj podíváme podrobněji.
Stručné schéma práce programu insmod
Dobře napsaný modul (typicky funkcí printk()) informuje o všem, co je z hlediska správce systému důležité. Tyto informace si lze přečíst např. příkazem dmesg. Důkladně se na to podíváme v příštím dílu seriálu.
Funkce printk() je obdoba známé knihovní funkce printf(), ovšem místo na standardní výstup vypisuje do logovacího bufferu jádra. Odtud lze tyto informace získat programem dmesg a navíc se některé ze zpráv (podle úrovně závažnosti) dostanou i tam, kam směřuje logování jádra (bývají např. v souboru /var/log/messages).
Nejjednodušší modul
Vrhněme se tedy na napsání prvního jaderného modulu. Tento modul nebude dělat nic jiného, než že se bude umět zavést a uvolnit. Do souboru mymodule.c napíšeme text, který vidíte v rámci.
#include #include static int __init mymodule_init(void) { printk(KERN_INFO "mymodule: modul inicializovan\n"); return 0; } static void __exit mymodule_cleanup(void) { printk(KERN_INFO "mymodule: modul uvolnen\n"); } module_init(mymodule_init); module_exit(mymodule_cleanup); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Prvni zkusebni modul"); MODULE_AUTHOR("Lukas Jelinek ");
Kód modulu obsahuje dvě funkce – někomu možná připomenou konstruktor a destruktor dynamické sdílené knihovny. První z nich (inicializační) se volá při načítání modulu a její úspěšné dokončení je nutnou podmínkou k tomu, aby byl modul plně zaveden – to dá funkce najevo návratovou hodnotou 0. Pokud by vrátila zápornou hodnotu, znamená to neúspěch při zavádění – a program insmod vypíše zprávu příslušející danému kódu chyby (používají se záporné hodnoty chybových kódu, jak je známe z běžných knihovních funkcí – tedy např. EINVAL nebo EBUSY).
Druhá funkce zajišťuje úklid – volá se při uvolňování modulu a musí být napsána tak, aby v ní nikdy nic neselhalo. Pozor – pokud se modul zakompiluje přímo do jádra (nikoli samostatně), tato funkce se nikdy nezavolá.
Funkce je zvykem deklarovat jako static (i když to není nutné, je to z historických důvodů) a používají se u nich symboly __init a __exit. Tyto nepovinné symboly říkají, že se příslušná funkce volá pouze při inicializaci, resp. ukončení. Je to optimalizační záležitost (inicializační funkce se po načtení modulu odstraní z paměti; úklidová funkce se zase nezakompiluje do monolitického jádra, protože je tam zbytečná).
Pomocí maker module_init a module_exit určujeme, které funkce se používají pro inicializaci a úklid. Mohou být totiž pojmenovány libovolně. Vždy se doporučuje všechny funkce (tedy i ty, které se neexportují do jmenného prostoru jádra) pojmenovávat tak, aby název začínal (případně i zkráceným) názvem modulu – jak je ostatně v příkladu vidět. Předejdeme tím zmatkům a případným kolizím.
Na konci příkladu jsou tři speciální makra. Opět nejsou povinná, ale minimálně to licenční (MODULE_LICENSE) by se mělo vždy používat. Pro licenci GPL se použije uvedená forma zápisu, ale možností je poněkud více.
Modul bez zvolené licence (stejně jako ten s proprietární licencí) je jádrem považován za odpadlíka a nejenže bude uživateli hlásit, že je v jádře „cosi shnilého“, ale hlavně bude modulu odepřen přístup k některým symbolům. Pouze GPL-kompatibilní licence mají plnou podporu, proto by se mělo dávat jádru najevo, že modul takovou licenci má (bližší informace najdete v dokumentaci jádra).
Uvedení autora a popisu modulu je záležitostí ryze informativní – tyto údaje vypisuje např. utilita modinfo. Jsou užitečné, proto vřele doporučuji je vždy uvádět (u jména autora se obvykle uvádí i e-mailová adresa). Existují ještě další podobná makra, ale ta se používají méně.
Kompilujeme
Modul tedy máme napsaný, nastal proto čas ho zkompilovat. V adresáři s modulem si proto vytvoříme soubor Makefile. Protože využíváme systém KBUILD, bude obsah souboru vypadat trochu jinak, než jsme zvyklí z normálních programů. Lze postupovat různě, pro náš jednoduchý modul můžeme použít zápis, který vidíte v druhém rámci.
KDIR = /lib/modules/`uname -r`/build obj-m := mymodule.o all: make -C $(KDIR) M=`pwd` clean: make -C $(KDIR) M=`pwd` clean
První řádek (definice proměnné KDIR) říká, kde se nachází zdrojový strom jádra. Uvedený způsob použije strom aktivního jádra (toho, které se právě používá), a to v rámci konvencí distribuce Fedora Core. Jiné distribuce mohou mít soubory uloženy jinde, proto se může příslušné cesta lišit.
Na druhém řádku určíme, jaký modul se bude kompilovat. Uvádí se v podobě objektového souboru. U jediného zdrojového (*.c) souboru si s tím vystačíme, při více souborech bude nutný trochu složitější zápis.
Pak tu máme ještě dvě pravidla – jsou velmi jednoduchá. Pro cíl all (a spuštění make bez parametrů) a pro cíl clean (odstranění souborů). Kompilovat se bude do aktuálního adresáře.
Máme-li soubor Makefile, můžeme začít kompilovat. Stačí v příslušném adresáři spustit make a vše potřebné by mělo proběhnout. Jsou-li zdroj i Makefile bezchybné, KBUILD pouze ohlásí, co kompiluje a již by mělo být hotovo. Po úspěšné kompilaci by v adresáři měl být (kromě meziproduktů) soubor s modulem připraveným k použití. Soubor má příponu .ko (kernel object), tedy v našem případě mymodule.ko.
Funguje to?
Zkusíme modul načíst příkazem insmod mymodule.ko. Pokud je vše v pořádku, měl by proces skončit bez vypsání jakékoli zprávy. Při chybě by sdělil, co je špatně. Pak můžeme příkazem dmesg zkontrolovat, zda modul vypsal, co měl (viz zdrojový kód). Modul lze uvolnit příkazem rmmod mymodule – i zde platí to, co jsem uvedl pro insmod.
Co dělat, když modul nefunguje, jak hledat chyby a hlavně jak jim předcházet, to bude náplní příštího dílu. Ladění a testování jádra je totiž (v porovnání s normálními programy) poněkud komplikovanější. Právě proto je potřeba využít všech prostředků k tomu, aby chyby vůbec nevznikaly.
Ještě bych rád upozornil, že obě tyto operace jsou (podobně jako další pokusy s načteným modulem) obecně nebezpečné. Pokud je v modulu chyba, může při jeho načítání nebo uvolňování počítač zatuhnout nebo se mohou poškodit data kdekoliv v paměti. Proto je důležité před zkoušením modulu ukončit všechny nepotřebné programy, zapsat data z paměti na disk (příkazem sync) a po skončení experimentů počítač restartovat.