Inicializace a ukončení - II
V předchozím textu, kde jsme probírali inicializaci a ukončení modulu během jeho zavádění/vyjímání z jádra, jsme záměrně vynechali jednu důležitou část. A sice přidání informací o našem modulu do adresáře /sys.
K čemu je to dobré? No k tomu, abychom donutili Linux vytvořit pro náš modul dskel soubor zařízení v adreáří /dev a to pokud možno zcela automaticky. Určitě si vzpomínáte, že během inicializace náš modul žádá jádro o přidělení volného major-minor páru. A že kvůli tomu nelze vytvořit soubor zařízení dopředu, protože nevíme jaký major-minor pár náš modul zrovna dostane přidělen. Ano? Dobrá, takže teď je už jasné, že musíme najít nějakou cestu, jak správci zařízení udev nebo jeho menšímu bratrovi mdev sdělit, že pro náš zavedený modul mají vytvořit soubor zařízení. Nalezení této cesty bude předmětem následujících odstavců.
Adresář/sys a třídy zařízení
Adresář /sys je přípojným bodem speciálního souborového systému sysfs. Tento virtuální souborový systém používá jádra pro export informací o zařízeních, modulech a dalších informací do uživatelského prostoru. Adresář /sys není ekvivalentem adresáře /proc. Oba jsou sice virtuální souborové systémy, ale každý je určen pro zpřístupnění jiných informací. Systém Linux může díky těmto speciálním souborovým systémům dodržet unixovou konvenci, že vše je soubor. A to i v případě zpřístupnění detailních informací o zařízení či systému.
Když si otevřeme adresář /sys zjistíme, že je v něm několik podadresářů s povědomými názvy: bus/ (sběrnice), devices/ (zařízení), firmware/ apod. Nás budou zajímat dva z nich: block/ a class/. V nich jsou uloženy informace o třídách zařízení. V adresáři block/ jsou informace o blokových zařízeních, v adresáři class/ pak o všech ostatních zařízeních. A tušíte správně - adresář class/ je adresářem, kterému mu budeme věnovat další pozornost, protože náš modul se týká znakových zařízení.
Na obrázku 1.7 je výpis obsahu adresáře /sys/class. Každý podadresář v tomto adresáři představuje třídu zařízení, v které jsou umístěny adresáře se samotnými moduly, resp. informacemi o nich. Například adresář input/ obsahuje všechna vstupní zařízení (kromě blokových zařízení).
Vyexportováním informací o modulu během jeho zavádění, tj. přidáním třídy (podadresáře do adresáře /sys/class) a přidáním zařízení (adresáře s informacemi o modulu) dojde automaticky i k vygenerování události, kterou umí odchytit správce zařízení udev, resp. mdev. To je ten první krok, který musíme udělat, aby pro náš modul dskel mohl správce zařízení vytvořit soubor zařízení.
Zvídavější z vás se asi budou ptát, jak se správce zařízení udev dozví, který major-minor pár byl našemu modulu jádrem přidělen. Odpověď je jednoduchá - v adresáři, kde jsou informace o modulu (například /sys/class/input/mice), je soubor uevent. Když si prohlédneme jeho obsah, zjistíme, že obsahuje vždy nejméně dvě položky MAJOR a MINOR. Jejich hodnoty jsou přiděleným major-minor párem. Elegantní, že?
Pojďme si ukázat, jak upravit náš modul dskel, resp. jeho inicializační a ukončovací funkce, tak aby o sobě exportoval informace do adresáře /sys/class, a tím vygeneroval událost pro správce zařízení.
Nejprve upravíme hlavičku našeho modulu (viz výpis 13). Přidáme si dvě možné chyby do výčtu chyb, které mohou nastat během inicializace modulu. Také musíme přidat hlavičkový soubor <linux/device.h> (definice struktury třídy a zařízení a deklarace potřebných funkcí) a soubor <linux/err.h> (definice maker IS_ERR() a PTR_ERR()). Mimo to potřebujme ještě dva nové členy struktury dskel_t, v nichž budou uloženy ukazatel na třídu zařízení (člen sys_class_ptr) a ukazatel na samotnou reprezentaci modulu v adresáři /sys/class (člen sys_dev_ptr).
1/* ######## prechozi include ######## */ 2#include <linux/device.h> 3#include <linux/err.h> 4 5// ——————————————————————- 6#define DEV_NAME "dskel" 7#define DEV_COUNT 1 8 9 10//——————————————————————-- 11enum 12{ 13 /* ######## prechozi chyby ######## */ 14 Failed_SysClass, 15 Failed_SysDev, 16}; 17 18//——————————————————————-- 19// parametry ovladace 20struct dskel_t 21{ 22 // major-minor par 23 dev_t dev_num; 24 // ukazatel na strukturu cdev reprezentujici znakove zarizeni 25 struct cdev * cdev_ptr; 26 // ukazatel na tridu zarizeni v adresari /sys/class 27 struct class * sys_class_ptr; 28 struct device * sys_dev_ptr; 29}; 30 31static struct dskel_t dskel = 32{ 33 .dev_num = MKDEV(0, 0), 34 .cdev_ptr = NULL, 35 .sys_class_ptr = NULL, 36 .sys_dev_ptr = NULL, 37};
Úpravu hlavičky máme za sebou. Jako další rozšíříme inicializační funkci. Za kód zajišťující začlenění modulu do jádra, přidáme kód (viz výpis 13), který nejprve vytvoří třídu zařízení, tj. adresář dskel v adresáři /sys/class a pak samotný adresář modulu dskel. Třída zařízení i adresář modulu se oba budou jmenovat stejně jako název modulu. Je to nejjednodušší způsob jak splnit požadavek, aby třída zařízení měla unikátní název. Nemá proto smysl dávat třídě zařízení název jako user či local, protože by mohlo dojít ke kolizi s jinými pokusnými ovladači.
Za zmínku stojí dvě skutečnosti. První je ta, že informace o modulu přidáváme do adresáře /sys/class až po úspěšném začlenění modulu do jádra. Druhá, že návratová hodnota obou funkcí class_create() a device_create() je sice ukazatel, ale v případě chyby nemusí ještě nutně mít hodnotu NULL, může totiž reprezentovat kód chyby. Proto se jeho hodnota musí kontrolovat speciálním makrem IS_ERR(), které nabývá nenulové hodnoty v případě, že je ukazatel neplatný. Kód chyby z neplatného ukazatele získáme pomocí makra PTR_ERR(), které převede neplatný ukazatel na kód chyby. Ten pak použijeme jako návratovou hodnotu.
1/* ######## pridani ovladace do jadra ######## */ 2 3// vytvorime si tridu v /sys/class 4dskel.sys_class_ptr = class_create(THIS_MODULE, DEV_NAME); 5if ( IS_ERR(dskel.sys_class_ptr) ) 6{ 7 cleanup(Failed_SysClass); 8 result = PTR_ERR(dskel.sys_class_ptr); 9 goto End; 10} 11 12// pridame nas modul do /sys/class/dskel 13dskel.sys_dev_ptr = device_create(dskel.sys_class_ptr, 14 NULL, dskel.dev_num, NULL, DEV_NAME); 15if ( IS_ERR(dskel.sys_dev_ptr) ) 16{ 17 cleanup(Failed_SysDev); 18 result = PTR_ERR(dskel.sys_dev_ptr); 19 goto End; 20}
Protože v inicializační funkci vytváříme třídu zařízení a adresář modulu, musíme po vyjmutí ovladače z jádra oba adresáře zrušit. Takže nás nemine ani úprava úklidové funkce. Pro jistotu uvádíme její celý upravený kód (výpis 13).
1static void cleanup(int error) 2{ 3 switch ( error ) 4 { 5 // kompletni uklid pri exitu 6 case ModuleExit: 7 // zrusime adresar modulu v /sys 8 device_destroy(dskel.sys_class_ptr, dskel.dev_num); 9 10 case Failed_SysDev: 11 // zrusime tridu 12 class_destroy(dskel.sys_class_ptr); 13 14 case Failed_SysClass: 15 // vyjmeme modul z jadra 16 // pozn.: cdev_del() se vola jen po uspesnem cdev_add() 17 cdev_del(dskel.cdev_ptr); 18 19 case Failed_CdevAdd: 20 // pozn.: neexistuje opak fce cdev_alloc(), uvolneni pameti resi 21 // jadro samo 22 case Failed_CdevAlloc: 23 // uvolnime nami zabrany major-minor par 24 unregister_chrdev_region(dskel.dev_num, DEV_COUNT); 25 26 case Failed_ChrdevRegion: 27 // jeste nic se nealokovalo, takze neni co vracet 28 break; 29 30 default: 31 break; 32 } 33}
Zkompilujeme modul a zkusíme se jej zavést. První dobré znamení je, když zavádění proběhne bez chyby. Další, pokud se v adresáři /sys/class objeví adresář dskel s podadresářem dskel. Ověříme si, že zde existuje soubor uevent s údaji o přiděleném major-minor páru. Po vyjmutí modulu z jádra musí oba adresáře zmizet (jestli nezmizí, tak je chyba v úklidové funkci).
Udev a mdev prakticky
K dokončení celé záležitosti s automatickým vytvářením souboru zařízení potřebujeme udělat ještě druhý krok - definovat správci zařízení udev nebo mdev jaký soubor zařízení má pro náš modul vytvořit. Udev k tomuto účelu používá soubory s pravidly, mdev záznam v konfiguračním souboru mdev.conf.
Pravidla správce zařízení udev jsou uložena v adresáři /etc/udev/rules.d v jednotlivých souborech s příponou .rules. Udev je prochází v abecedním pořadí. Jako uživatel root si zde vytvoříme nový textový soubor 10-local.rules. Díky tomu bude naše pravidlo zkontrolováno jako první před všemi ostatními pravidly, které v systému již existují. Do souboru s pravidlem vložíme tento řádek:
KERNEL=="dskel", NAME="dskel", MODE="0666", OWNER="root", GROUP="root"
Klíčové slovo KERNEL s operátorem == slouží pro určení shody. Pokud se náš module jmenuje dskel, tak nastane shoda a udev vytvoří soubor zařízení s názvem daným hodnotou proměnné NAME a nastaví mu práva přístupu dle proměnných MODE, OWNER a GROUP.
Klíčových slov je samozřejmě více, zájemce o hlubší porozumění funkce udev a jeho pravidlům proto odkážeme na stručný návodhttp://www.abclinuxu.cz/blog/vejsplechty/2008/1/mini-howto-psani-udev-pravidel.
Zkouška funkčnosti našeho pravidla je jednoduchá - zavedeme náš modul. V adresáři /dev se musí objevit zařízení dskel s určenými právy přístupu. Pokud něco nefunguje, zkontrolujme pořádně pravidlo - často je chyba v názvu zařízení, kdy pravidlo jakoby funguje, ale přiřazená práva jsou špatně apod.
Mdev má svá pravidla uložena v jednom jediném souboru /etc/mdev.conf. Kamkoliv do tohoto souboru vložíme tento řádek:
dskel root:root 0660
Jen dodávám, že pořadí řádku v rámci konfiguračního souboru definuje jeho pořadí při prohledávání. Zkouška funkčnosti je stejná jako v případě udev.