Laboratori 9: Programació de mòduls per al Kernel de Linux

En aquest laboratori aprendrem a programar mòduls per al kernel de Linux. Aquesta és una de les tasques més complexes que es poden fer en el món de la programació de sistemes. Per això, realitzarem uns exemples senzills per a entendre com es programen els mòduls i com es poden integrar al kernel.

Requisits previs

  • Màquina virtual amb Debian.
  • Realitzar una snapshot de la màquina virtual abans de començar el laboratori.
  • Programari necessari:
    • Eines de construcció de programari: build-essential

    • Capçaleres del kernel: linux-headers-$(uname -r)

    • Altres eines: libncurses-dev, bison, flex, kmod

    • Instal·lació de les eines necessàries:

      su -c "apt update && apt upgrade -y"
      su -c "apt install build-essential linux-headers-$(uname -r) libncurses-dev bison flex kmod -y"

Tasques

Explorant el kernel

  1. Obtenir informació sobre la versió del kernel actual:

    uname -r

    En el meu cas, la versió del kernel és 6.1.0-39-arm64.

  2. Per veure els mòduls carregats al kernel, podem fer servir la comanda lsmod:

    su -c "lsmod"

    També podem fer servir la comanda cat per llegir el fitxer /proc/modules:

    su -c "cat /proc/modules"

    Si volem filtrar un mòdul concret, podem fer servir la comanda grep:

    su -c "lsmod | grep fat"

Els moduls del kernel registren la informació de log en una consola, però per defecte no la podreu veure per la sortida estàndard (sdtout) o la sortida d’error (stderr). Per veure aquesta informació, necessitarem fer servir la comanda dmesg.

Per exemple, si volem veure els últims missatges del kernel, podem fer servir la comanda:

su -c "dmesg | tail -n 10"

Aquest missatge provenen dels mòduls del kernel que utilitzen la funció printk per imprimir informació de log. Aquesta funció permet especificar el nivell de log i el mòdul que genera el missatge. Per canviar el nivell de log, podem fer servir la comanda dmesg amb l’opció -n:

su -c "dmesg -n 4"

En aquest cas, el nivell de log és 4, que correspon a WARNING. Això significa que només es mostraran els missatges de log amb nivell WARNING o superior.

Nivells de log disponibles:

  • 0: KERN_EMERG: Missatges d’emergència.
  • 1: KERN_ALERT: Missatges d’alerta.
  • 2: KERN_CRIT: Missatges crítics.
  • 3: KERN_ERR: Missatges d’error.
  • 4: KERN_WARNING: Missatges d’avís.
  • 5: KERN_NOTICE: Missatges de notificació.
  • 6: KERN_INFO: Missatges d’informació.
  • 7: KERN_DEBUG: Missatges de depuració.
  • 8: KERN_DEFAULT: Nivell per defecte.

Programant un mòdul

En aquesta secció, programarem un mòdul senzill per al kernel de Linux. Aquest mòdul imprimirà un missatge d’inici i un missatge de finalització quan es carregui i descarregui al kernel.

  1. Creem un directori per al nostre mòdul:

    mkdir -p $HOME/kernel
    cd $HOME/kernel
  2. Creem un fitxer anomenat hello.c amb el següent contingut:

    #include <linux/kernel.h>
    #include <linux/module.h> 
    
    MODULE_LICENSE("GPL");
    
    int init_module(void) {
        printk(KERN_INFO "Hello, world!\n");
        return 0;
    }
    
    void cleanup_module(void) {
        printk(KERN_INFO "Goodbye, world!\n");
    }
  3. Crearem un fitxer Makefile per compilar el nostre mòdul amb el següent contingut:

    CONFIG_MODULE_SIG=n
    obj-m += hello.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

    Nota:

    • El fitxer Makefile és sensible a la indentació. Assegureu-vos que utilitzeu tabuladors en lloc d’espais.
    • La variable obj-m indica quin és el mòdul que volem compilar.
    • La variable PWD conté la ruta del directori actual.
    • $(shell uname -r) retorna la versió del kernel actual.
    • CONFIG_MODULE_SIG=n desactiva la verificació de la signatura del mòdul.
  4. Compil·lem el nostre mòdul amb la comanda make:

    make

    Troubleshooting:

    • Si obteniu un error de missing separator, assegureu-vos que utilitzeu tabuladors en lloc d’espais.
    • Si obteniu un error de /lib/modules/, assegureu-vos que teniu instal·lat el paquet linux-headers.
    • Si obteniu un error de missing MODULE_LICENSE(), podeu afegir la següent línia al vostre fitxer hello.c:
  5. Carreguem el nostre mòdul amb la comanda insmod:

    su -
    insmod /home/jordi/laboratoris/lab2-kernel/kernel-modules/hello.ko

    Nota: Si teniu errors assegureu-vos que esteu executant la comanda com a root. Quan canviem a l’usuari root, la variable $HOME canvia a /root. Per tant, assegureu-vos d’apuntar a la ruta correcta.

  6. Comprovem que el mòdul s’ha carregat correctament amb la comanda lsmod:

    lsmod | grep hello

    Si el mòdul s’ha carregat correctament, veureu una sortida similar a aquesta:

    hello                 16384  0
  7. Comprovem els missatges del kernel amb la comanda dmesg:

    su -c "dmesg"

    Si tot ha anat bé, veureu els missatges Hello, world! al final de la sortida.

  8. Descarreguem el mòdul amb la comanda rmmod:

    rmmod hello
  9. Comprovem que el mòdul s’ha descarregat correctament amb la comanda lsmod:

    lsmod | grep hello

    Si el mòdul s’ha descarregat correctament, hauríeu de veure el missatge Goodbye, world! al final de la sortida de dmesg.

Es poden fer diferents millores i moduls més complexos, però aquest és un exemple senzill per a començar a programar mòduls per al kernel de Linux. Per exemple, podeu afegir més informació al mòdul com llicència, autor, descripció i versió. També podeu utilitzar les macros __init i __exit per optimitzar el mòdul i reduir la memòria utilitzada.

Per exemple, aquí teniu un exemple millorat del mòdul hello.c:

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

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jordi Mateo");
MODULE_DESCRIPTION("Hello World OS Kernel Module");
MODULE_VERSION("0.1");

static int __init hello_init(void) {
    printk(KERN_INFO "WOW I AM A KERNEL HACKER!!!\n");
    return 0;
}

static void __exit hello_cleanup(void) {
    printk(KERN_INFO "I am dead.\n");
}

module_init(hello_init);
module_exit(hello_cleanup);