Rašome kernelio modulį

Linux kernelio modulius galima rašyti dviem būdais: in-tree ir out-of-tree, o kompiliuoti dar dviem - kaip build-in (įkimpiluojamas į patį kernelį) ir kaip loadable-module (sukompiliuojamas kaip atskiras modulis, kurį galima užkrauti prireikus ar panorėjus).
Pats rašymas nelabai ir skiriasi, skiriasi tik kur laikomi failai. Paprastai rekomenduojama modulį rašyti in-tree ir prašyti, kad tavo modulį priimtų Linusas. Tada atseit kažkas pasirūpins, kad tavo modulis kompiliuotųsi keičiantis kernelio versijoms. Tačiau būkime realistai - niekam tavo modulis neįdomus, niekas jo nepriims ir niekas neprižiūrės.
Jeigu jau apėmė tokios niūrios nuotaikos arba dar tik mokaisi rašyti kernelio modulius, geriau rinktis out-of-tree metodą - bus lengviau susirasti savus failus ir sekti pakeitimus.

Rašome modulį

Pradėkime nuo paprasto “Sveikas pasauli!” moduliuko. Susikuriame naują failą:

labukas.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

static int __init inicializuoti_moduli(void)
{
printk(KERN_INFO "Sveikas, pasauli!\n");

/*
* Reikia gražinti 0, antraip kernelis galvos, kad inicializavimas nulūžo.
* Jeigu gražinsime >0 kernelis labai supyks, bet vistiek paleis modulį.
* Jeigu gražinsime <0 kernelis nepaleis modulio ir atspausdins klaidos žinutę pagal gražinamą klaidos kodą.
*/
return 0;
}

static void __exit atlaisvinti_moduli(void)
{
printk(KERN_INFO "Sudie, pasauli!\n");
}

module_init(inicializuoti_moduli);
module_exit(atlaisvinti_moduli);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Piktas Zuikis");
MODULE_DESCRIPTION("Paprastas 'Sveikas, pasauli!' modulis.");

Kas čia parašyta? Tuojau paaiškinsiu:
Sukuriame dvi statines funkcijas - inicializavimo ir apsivalymo. Kernelis suranda šias funkcijas pagal module_init() ir module_exit() makrosus.
__init ir __exit makrosai skirti atminties valdymui. Įkompiliuotuose moduliuose __init makrosu pažymėta funkcija po iškvietimo bus atlaisvinta, o __exit funkcija bus ignoruojama. Dinamiškai užkraunamuose moduliuose (su modprobe/rmmod) šie makrosai nieko nereiškia. Mes rašysime dinamiškai užkraunamą modulį.
Makrosas printk yra skirtas loginimui į syslogą.

Kompiliuojame modulį

out-of-tree

Kompiluojant out-of-tree metodu mums net nereikia kernelio kodo, užtenka linux-headers, esančių: /lib/modules/$(uname -r)/build

Paruošiame Makefile:

Makefile
1
2
3
4
5
obj-m += labukas.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Kompiliuojame:

1
make

Peržiūrime modulio informaciją:

1
modinfo labukas.ko 

Prijungiame modulį prie veikiančio kernelio ir pažiūrime ar suveikė:

1
2
sudo insmod ./labukas.ko
dmesg | tail

Išjungiame modulį ir pažiūrime ar suveikė:

1
2
sudo rmmod labukas
dmesg | tail

Viskas, šiandienos darbas baigtas.
http://www.tldp.org/LDP/lkmpg/2.6/html/

in-tree

Kompiliuojant modulį in-tree iš pradžių reikės to tree - tai yra kernelio kodo. Jį parsisiunčiame iš https://www.kernel.org arba iš distribucijos repozitorijos, arba (jei esame visiškai pašėlę) iš github’o.

Kai jau gavome kodą galima į jį pridergti. Tarkime, kad mūsų modulis “labukas” yra draiveris. Todėl einame į aplanką drivers ir jame susikuriame naują aplanką - labukas. Į jį įmetame aukščiau nagrinėtą labukas.c ir sukuriame dar du papildomus failus:

drivers/labukas/Makefile
1
obj-$(CONFIG_LABAS_PASAULI_PVZ) += labukas.o
drivers/labukas/Kconfig
1
2
3
4
5
6
config LABAS_PASAULI_PVZ
tristate "Labas pasauli pavyzdys"
default m
help
Modulis, kurį aktyvavus į logą išspausdinama žinutė "Labas pasauli!".
Modulio pavadinimas bus "labukas".

Makefile įsirašo sąlyga kada kompiliuoti mūsų modulį - tik kai pasirinktas CONFIG_LABAS_PASAULI_PVZ. KConfig faile - informacija apie modulį. Ši informacija bus matoma konfigūruojant kernelį pvz. per menuconfig. tristate reiškia, kad mūsų kodas gali būti ir kaip modulis ir įkompiliuotas. Jeigu norime leisti tik įkompiliuoti, turėtumėme vietoj tristate įrašyti bool.

Palauk, palauk, neskubėk! Dar ne viskas. Šiuos du papildomus failus irgi reikia užregistruoti.

Atsidarome drivers/Kconfig ir pridedame eilutę source "drivers/labukas/Kconfig". Tada atsidarome drivers/Makefile ir pridedame obj-y += labukas/.

Na dabar jau galime bandyti kompiliuoti: sugrįžtame į kernelio kodo šakninį aplanką ir paleidžiame make menuconfig, įeiname į Device Drivers ir štai - matosi mūsų modulis:

Klaviatūroje paspaudžiame klaustuką ir gauname išsamesnę informaciją ką šis konfigūracijos punktas reiškia:

Išeiname iš konfigūracijos. Išeinant paklaus ar norime išsaugoti pakeitimus - mes norime. Paleidžiame make ir kada nors kernelis, o kartu ir mūsų modulis, bus sukompiliuoti. Kaip jį pasileisti, tikiu, kuo puikiausiai visi žinote.

Kodą galima rasti githube