Salta al contenuto
Note per Studenti Esercizio - Modalità utente e modalità kernel con un modulo del kernel (laboratorio 1)

Esercizio - Modalità utente e modalità kernel con un modulo del kernel (laboratorio 1)

In questa pagina 5

Testo (laboratorio 1 del corso di Sistemi Operativi, Ing. Informatica UniPD). Su una macchina virtuale Linux (Debian su QEMU o VirtualBox):

  1. scrivere uprog.c, un programma che legge il registro di segmento CS con un po' di assembly inline, ne estrae i 2 bit meno significativi e stampa se sta girando in modalità utente o kernel;
  2. compilare e caricare un primo modulo del kernel (kprog.c), che stampa un messaggio al caricamento e alla rimozione;
  3. esercizio: modificare kprog.c perché al caricamento (insmod) stampi anche il livello di privilegio corrente; verificare con dmesg che il messaggio sia "Running in Kernel Mode".

Teoria: Sistema operativo - ruolo, struttura e system callIl sistema operativo (operating system, OS) è il software che fa da intermediario tra utenti, programmi e hardware: alloca le risorse (CPU, memoria, dischi, dispositivi), esegue i programmi e li protegge l'uno dall'altro; la sua parte sempre in esecuzione è il kernel; l'hardware offre la modalità doppia (utente/kernel, un bit di modo) e gli interrupt, e il programma chiede un servizio al kernel con una system call, una trap che passa in modalità kernel; di solito si usa un'API (libc, POSIX, Windows API) che a sua volta invoca la system call (printf chiama write); la struttura può essere monolitica (UNIX/Linux, con moduli caricabili), a microkernel (Mach, QNX), ibrida (Windows, Darwin, Android sopra un kernel Linux modificato).Sistema operativo - ruolo, struttura e system call → (modalità doppia, bit di modo, strutture a moduli).

Il registro CS e il livello di privilegio

Sui processori x86 il registro di segmento CS (code segment) contiene un selettore; i suoi due bit meno significativi sono il CPL (current privilege level): 0 = anello 0 = modalità kernel, 3 = anello 3 = modalità utente (gli anelli 1 e 2 non sono usati da Linux). Basta quindi leggere CS e mascherare con 0x03 per sapere in quale modalità sta girando il codice.

1. Il programma utente uprog.c

uprog.cc
#include <stdio.h>

int main(void)
{
    unsigned int cs_register_value;
    unsigned int privilege_level;

    /* legge il registro CS con assembly inline (sintassi AT&T di gcc, solo x86) */
    __asm__("mov %%cs, %0" : "=r"(cs_register_value));
    /* i 2 bit meno significativi sono il livello di privilegio */
    privilege_level = cs_register_value & 0x03;

    if (privilege_level == 0)
        printf("Running in Kernel Mode\n");
    else
        printf("Running in User Mode\n");
    return 0;
}

Compilazione ed esecuzione: gcc uprog.c -o uprog e ./uprog (il ./ serve perché il programma è nella directory corrente). Risultato: Running in User Mode (CPL = 3): un normale programma gira in modalità utente. Il codice è stato compilato con zig cc -Wall -Wextra -Wpedantic per Linux x86-64 senza avvisi; non è stato eseguito qui (binario Linux).

2. Il primo modulo del kernel

Un modulo del kernel (loadable kernel module, LKM) è codice che si carica nel kernel in esecuzione e gira in modalità kernel: un errore può bloccare tutto il sistema (nella VM non ci sono danni veri). Struttura minima:

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

/* chiamata quando il modulo viene caricato: deve restituire 0 se va bene */
int simple_init(void)
{
    printk(KERN_INFO "Loading Kernel Module\n");
    return 0;
}

/* chiamata quando il modulo viene rimosso */
void simple_exit(void)
{
    printk(KERN_INFO "Removing Kernel Module\n");
}

module_init(simple_init);       /* registra il punto d'ingresso */
module_exit(simple_exit);       /* registra il punto d'uscita */
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Simple Module");
  • simple_init (punto d'ingresso) restituisce un intero: 0 successo, altro fallimento; simple_exit restituisce void; nessuna delle due riceve parametri.
  • module_init e module_exit registrano le due funzioni nel kernel; le macro MODULE_* sono convenzioni.
  • printk è il printf del kernel: il testo va nel buffer dei messaggi del kernel, che si legge con sudo dmesg; KERN_INFO è la priorità (informativa). sudo dmesg -c svuota il buffer.

Il file Makefile (le righe di comando iniziano con Tab, non con spazi):

obj-m += kprog.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

Comandi:

comando effetto
make compila e produce kprog.ko (il modulo)
lsmod elenca i moduli (nome, dimensione, chi lo usa)
sudo insmod kprog.ko inserisce il modulo nel kernel: parte simple_init
sudo dmesg mostra il messaggio "Loading Kernel Module"
sudo rmmod kprog rimuove il modulo (senza .ko): parte simple_exit

3. Soluzione dell'esercizio

Basta portare nel modulo le tre righe di uprog.c, sostituendo printf con printk:

int simple_init(void)
{
    unsigned int cs_register_value;
    unsigned int privilege_level;

    printk(KERN_INFO "Loading Kernel Module\n");

    __asm__("mov %%cs, %0" : "=r"(cs_register_value));
    privilege_level = cs_register_value & 0x03;

    if (privilege_level == 0)
        printk(KERN_INFO "Running in Kernel Mode\n");
    else
        printk(KERN_INFO "Running in User Mode\n");
    return 0;
}

Dopo sudo insmod kprog.ko, dmesg mostra Loading Kernel Module e Running in Kernel Mode: il codice del modulo è eseguito con CPL =0= 0, lo stesso livello del kernel, mentre uprog stampava "User Mode" (CPL =3= 3). È la dimostrazione pratica del bit di modo.

Nota. Il modulo richiede i sorgenti/header del kernel della VM (/lib/modules/$(uname -r)/build) e non è stato compilato qui: il blocco di codice del modulo non è dunque verificato con un compilatore. Consigli del laboratorio: compilare spesso (gli errori del compilatore sui moduli non sono molto utili), caricare e rimuovere il modulo e controllare dmesg a ogni modifica.

Domande d'esame

Come si distingue da un programma se sta girando in modalità kernel o utente su x86? Traccia. Si legge il registro CS: i due bit meno significativi (CPL) valgono 0 in modalità kernel e 3 in modalità utente. Un modulo del kernel risulta in modalità kernel, un normale programma in modalità utente.

Versione ripasso

Teoria collegata