
Jak to chodí v jádře aneb napište si vlastní ovladač (4)
I v tomto díle budeme stále věrni minulému tématu a tím jsou "problémy" se
zaváděním a odstraňováním modulů.
Jmenný prostor
Jmenný prostor je v celém jádru pouze jeden a proto je nutné dbát na
rozumné pojmenování funkcí, proměnných a maker (pro všechny tři věci budu
dále používat pojem symbol) použitých ve Vašem ovladači. Veškeré veřejně
přístupné symboly jsou uloženy v tzv. Kernel symbol table. Rozumná doporučení
pro pojmenování můžeme shrnout takto:
- všechny symboly které chcete skrýt (a nechcete pro ně vymýšlet
unikátní názvy) deklarujte jako static
- u symbolů, které je nutné deklarovat jako veřejné použijte rozumné
jméno, které jednoznačně říká co daný symbol znamená
- pro celý ovladač, resp. modul si vyberte unikátní prefix pro všechny
symboly
Není mnoho doporučení jak správně nazvat Vaše symboly, ale pokud se jich
budete držet předejdete mnoha konfliktům a ušetříte si čas.
Symboly
Níže uvedená makra jsou definována v hlavičkoveém souboru
linux/module.h.
Makro EXPORT_NO_SYMBOLS
V případě, že Váš modul nemá žádný symbol, který má být veřejný je dobré
zapsat makro EXPORT_NO_SYMBOLS kdekoli ve zdrojovém
souboru. Z důvodu portability doporučejeme toto makro uvádět ve funkci
init_module() (což je jediné místo kde toto makro pracuje správně
ve starších verzích jádra).
static int __init
init_module(void)
{
...
EXPORT_NO_SYMBOLS;
...
}
Makra EXPORT_SYMBOL(symbol), EXPORT_SYMBOL_NOVERS(symbol)
Pokud máme několik symbolů v modulu, které chceme zpřístupnit celému jádru
(i ostatním modulům) je nutné tento symbol tzv. exportovat. K tomuto účelu nám
slouží dvě výše uvedená makra, která se liší pouze v tom, že první exportuje
symbol s verzí a druhé bez verze. My zatím budeme používat exportování symbolů
bez verzí a k problematice symbolů s verzemi se dostaneme v některém z dalších
dílů.
Aby tato makra mohla fungovat, je nutné definovat makro
EXPORT_SYMTAB ještě před použitím hlavičkového souboru
linux/module.h a nejlépe na úrovni argumentu překladače
-DEXPORT_SYMTAB.
Příklad exportovaného symbolu a jeho použití
První část našeho příkladu bude kratičký modul, který bude exportovat jeden
symbol bez verze. Tím symbolem bude funkce int lz_get_value(),
která vrací obsah proměnné value. Pro větší rozmanitost je tato
proměnná také parametrem modulu a tudíž můžeme její hodnotu ovlivnit při
zavádění. Následující ukázku je taktéž přiložena k článku jako soubor
lzsym.c.
#ifdef MODULE
#include <linux/module.h>
MODULE_DESCRIPTION("Exported symbol example");
MODULE_AUTHOR("Robert V0jta ");
MODULE_LICENSE("GPL");
MODULE_PARM(value, "i");
MODULE_PARM_DESC(value, "Exported symbol value");
#endif /* MODULE */
#include <linux/init.h>
static int value = 0;
static int __init
init_module(void)
{
printk("<1>symbol module started\n");
return 0;
}
static void __exit
cleanup_module(void)
{
printk("<1>Symbol module unloaded\n");
}
int
lz_get_value(void)
{
return value;
}
#ifdef EXPORT_SYMTAB
EXPORT_SYMBOL_NOVERS (lz_get_value);
#endif /* EXPORT_SYMBOL */
Jak sami vidíte není to vůbec nic složitého. Teď potřebujeme hlavičkový
soubor (lzsym.h), který bude deklarovat námi exportovaný symbol
lz_get_value pro použití v jiných modulech. Tento symbol bude
definován v případě existence makra __KERNEL__, protože může
obsahovat i jiné deklarace použitelné v uživatelských aplikacích.
#ifndef LZ_SYMBOL_H
#define LZ_SYMBOL_H
#ifdef __KERNEL__
extern int lz_get_value(void);
#endif /* __KERNEL__ */
#endif /* LZ_SYMBOL_H */
A teď se dostáváme k poslední části našeho příkladu a tím je modul, který
náš exportovaný symbol použije. Není to nic složitého, pouze při zavádění
vypíše vrácenou hodnotu onoho symbolu (lzsymuse.c).
#ifdef MODULE
#include <linux/module.h>
MODULE_DESCRIPTION("LZ Symbol usage");
MODULE_AUTHOR("Robert V0jta ");
MODULE_LICENSE("GPL");
#endif /* MODULE */
#include <linux/init.h>
#include "lzsym.h"
static int __init
init_module(void)
{
printk("<1>LZ symbol value is %d\n", lz_get_value());
return 0;
}
static void __exit
cleanup_module(void)
{
printk("<1>LZ symbol usage modul unloading\n");
}
Jak sami vidíte, jedinou nutností bylo použití hlavičkového souboru
lzsym.h v kterém je deklarace našeho symbolu
lz_get_value.
Ještě nám zbývá uvést ukázkový Makefile pro tento příklad.
MODULES = lzsym.o lzsymuse.o
CC = gcc
CFLAGS = -O2 -DMODULE -D__KERNEL__
all: $(MODULES)
clean:
rm -f $(MODULES)
lzsym.o: lzsym.c
$(CC) $(CFLAGS) -DEXPORT_SYMTAB -o $@ -c $<
lzsymuse.o: lzsymuse.c
$(CC) $(CFLAGS) -o $@ -c $<
Za domácí úlohu si zkuste zrušit definici makra EXPORT_SYMTAB
z argumentu překladače a uvidíte co to udělá.
Nyní si moduly zkompilujeme.
[echelon:~/linuxzone/ldd/4/v0jta-ldd-4]make
gcc -O2 -DMODULE -D__KERNEL__ -DEXPORT_SYMTAB -o lzsym.o \
-c lzsym.c
gcc -O2 -DMODULE -D__KERNEL__ -o lzsymuse.o -c lzsymuse.c
[echelon:~/linuxzone/ldd/4/v0jta-ldd-4]
A postupně zavedeme v pořadí lzsym.o
a lzsymuse.o.
[root@echelon v0jta-ldd-4]# insmod ./lzsym.o value=111
symbol module started
[root@echelon v0jta-ldd-4]# insmod ./lzsymuse.o
LZ symbol value is 111
[root@echelon v0jta-ldd-4]#
Teď si oba moduly zkuste odstranit v opačném pořadí než jste je zaváděli,
tedy lzsymuse a poté lzsym. Pokud máte, zkuste si
zavést modul lzsymuse.o a uvidíte notoricky známou hlášku ...
[root@echelon v0jta-ldd-4]# insmod ./lzsymuse.o
./lzsymuse.o: unresolved symbol lz_get_value
[root@echelon v0jta-ldd-4]#
... o neexistujícím symbolu, protože modul, který tento symbol exportuje
(lzsym) není zaveden. Existuje zde možnost jak automaticky
zavádět potřebné moduly do jádra, ale tu si probereme v některém z dalších
dílů. Teď si zaveďte oba moduly ve správném pořadí a zkuste z jádra odstranit
modul lzsym.
[root@echelon v0jta-ldd-4]# rmmod lzsym
lzsym: Device or resource busy
[root@echelon v0jta-ldd-4]#
100% se Vám to nepodaří z jednoho důvodu - v jádru jsou stále aktivní
moduly, které potřebují ke svému běhu symboly exportované z našeho moduly
a proto ho jádro nemůže odstranit. Tuto informace můžete získat i jinou cestou
a tím je příkaz lsmod, který Vám ukáže závislost modulů.
[root@echelon v0jta-ldd-4]# lsmod | grep lzsym
lzsymuse 896 0 (unused)
lzsym 984 0 [lzsymuse]
[root@echelon v0jta-ldd-4]#
Zde vidíte, že modul lzsym je používán modulem
lzsymuse a proto ho jádro nemůže odstranit.
Počítadlo počtu použití modulu
Zmiňujeme-li problémy s odstraňováním modulu z jádra, musíme se také
zastavit u dalších tří maker, které jsou definována v hlavičkovém souboru
linux/module.h a jsou jimi:
MOD_INC_USE_COUNT
Při použití tohoto makra se počítadlo použití modulu zvýší
o jedničku.
MOD_DEC_USE_COUNT
Při použití tohoto makra se počítadlo použití modulu sníží
o jedničku.
MOD_IN_USE
Toto počítadlo vrátí hodnotu true v případě, že modul je
používán, resp. pokud je počítadlo použití větší jak nula.
Dnešní jádra si sami zjišťují jestli je modul používán či nikoli a proto
není nutné využívat těchto maker. Kdo chce ale psát portabilní kód (pro starší
jádra) musí tyto makra použít, protože starší jádra touto funkcí
nedisponují.
Kdy se to používá?
Představme si modul pro zvukovou kartu, který po zavedení není používán
a proto ho můžeme bez problémů odstranit. Tento modul se začne používat
v případě, že si například pustíme XMMS a začneme si přehrávat MP3. Poté
nám XMMS zavolá funkci open na zařízení
/dev/dsp (či jiné). Dále se nám zavolá funkce open
implementovaná v našem ovladači, která zavolá makro
MOD_INC_USE_COUNT a modul je již používán. Během ukončování XMMS
se zavolá funkce close, kterou také náš modul implementuje a v ní
je zavoláno makro MOD_DEC_USE_COUNT. V tomto případě už není náš
modul používán a můžeme ho bez problémů odstranit.
Mohu zjistit kolikrát je náš modul používán? Ano, můžete a to pomocí
příkazu lsmod, který ve třetím sloupečku (Used) ukazuje počítadlo
použití u daného modulu. Právě teď poslouchám MP3 a když se podívám na
zvukovou kartu, vidím že je opravdu použita a modul neodstraním dokud
neukončím přehrávání MP3, neboli dokud nepřestanu používat dané zařízení.
i810_audio 24712 1 (autoclean)
ac97_codec 13320 0 (autoclean) [i810_audio]
soundcore 6500 2 (autoclean) [i810_audio]
(autoclean)
Tento sloupeček souvisí s automatickým zaváděním modulů, které jsem
zmiňoval výše a bude podrobněji rozebrán v některém z dalších dílů.
Možné problémy
V případě, že budete používat tyto makra je nutná obezřetnost z jednoho
prostého důvodu - jestliže kvůli nějaké akci zvýšíte počítadlo použití modulu
je nutné po ukončení dané akce počítadlo zase snížit. Jinak se Vám počítadlo
nevrátí zpět na nulu a modul z jádra neodstraníte. Podobné případy mohou
vzniknout při ladění modulu kdy Vaše funkce nejsou bezproblémové a nastane-li
havárie Vašeho modulu, počítadlo zůstane větší jako nula a modul zase
neodstraníte. Ladíte-li, doporučuji předefinovat makra
MOD_INC_USE_COUNT a MOD_DEC_USE_COUNT například na
{}.
Závěr
Teď už umíte exportovat symboly, používat je v jiných modulech a také jste
zase o něco chytřejší co se problematiky zavádění a odstraňování modulů týče.
K exportování symbolů, jejich smyslu a použití se vrátíme ještě v příštím díle
a posuneme se o malinko dále.
Deklarace vs Definice
Zjistil jsem, že při psaní článků docela často zaměňuji pojmy
deklarace a definice. Vynasnažím se, aby od
příštího dílu byla vždy použita správná terminologie. Omlouvám se.
Soubory
Použité ukázky v dnešním díle naleznete zde.
Další části seriálu:
|