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):
- scrivere
uprog.c, un programma che legge il registro di segmentoCScon un po' di assembly inline, ne estrae i 2 bit meno significativi e stampa se sta girando in modalità utente o kernel; - compilare e caricare un primo modulo del kernel (
kprog.c), che stampa un messaggio al caricamento e alla rimozione; - esercizio: modificare
kprog.cperché al caricamento (insmod) stampi anche il livello di privilegio corrente; verificare condmesgche il messaggio sia "Running in Kernel Mode".
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
#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:0successo, altro fallimento;simple_exitrestituiscevoid; nessuna delle due riceve parametri.module_initemodule_exitregistrano le due funzioni nel kernel; le macroMODULE_*sono convenzioni.printkè ilprintfdel kernel: il testo va nel buffer dei messaggi del kernel, che si legge consudo dmesg;KERN_INFOè la priorità (informativa).sudo dmesg -csvuota 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) cleanComandi:
| 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 , lo stesso livello del kernel, mentre uprog stampava "User Mode" (CPL ). È 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
- CPL: i 2 bit meno significativi del registro
CS(x86): 0 = kernel (anello 0), 3 = utente (anello 3). 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 →. uprog.c:__asm__("mov %%cs, %0" : "=r"(cs));poics & 0x03; stampa "Running in User Mode" (CPL 3).gcc uprog.c -o uprog,./uprog.- Modulo:
simple_init(ritorna int, 0 = ok) esimple_exit(void) registrati conmodule_init/module_exit;printk(KERN_INFO ...)scrive nel buffer letto dasudo dmesg;Makefileconobj-m += kprog.o(comandi con Tab). - Comandi:
make,lsmod,sudo insmod kprog.ko,sudo dmesg,sudo rmmod kprog. - Esercizio: aggiungere in
simple_initla lettura diCSeprintk: dmesg mostra "Running in Kernel Mode". - Perché funziona:
CScontiene il selettore del segmento di codice corrente e il CPL, che indica il livello di privilegio con cui gira il codice: ring 0 per il kernel (e per un modulo, che è codice del kernel), ring 3 per i programmi utente. Gli anelli 1 e 2 non sono usati da Linux. - Il modulo: gira in modalità kernel, un suo errore può bloccare il sistema (nella VM nessun danno); richiede i sorgenti/header del kernel della VM (
/lib/modules/$(uname -r)/build) e non è stato compilato qui;uprog.cinvece è compilato conzig ccsenza avvisi (non eseguito). - Consigli del corso: compilare spesso (gli errori sono poco chiari), caricare/rimuovere e controllare
dmesga ogni modifica,sudo dmesg -cper pulire il buffer. - Errori tipici:
printfal posto diprintk; Makefile con spazi invece di Tab;rmmod kprog.ko(va senza.ko); dimenticaresudo.