Produkty Novinky Články Návody Kontakty

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.
obrázek vypis-obsahu-adresare-sys
Obrázek 1.7 Výpis obsahu adresáře /sys/class
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>
45// ——————————————————————-
6#define DEV_NAME               "dskel"
7#define DEV_COUNT             1
8910//——————————————————————-- 
11enum
12{
13     /* ######## prechozi chyby ######## */
14     Failed_SysClass,
15     Failed_SysDev,
16};
1718//——————————————————————-- 
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};
3031static 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};
Výpis zdrojového kódu ovladače - hlavička (doplněk kvůli /sys)
Ú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 ######## */
23// 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}
1112// 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}
Výpis zdrojového kódu ovladače - funkce init() (doplněk kvůli /sys)
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);
910             case Failed_SysDev:
11                     // zrusime tridu
12                     class_destroy(dskel.sys_class_ptr);
1314             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);
1819             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);
2526             case Failed_ChrdevRegion:
27                     // jeste nic se nealokovalo, takze neni co vracet
28                     break;
2930             default:
31                     break;
32     }
33}
Výpis zdrojového kódu ovladače - funkce cleanup() (úprava kvůli /sys)
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ávodInformationhttp://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.