🍺 Buy me a beer
🐧

Il Kernel Linux

Cosa succede davvero tra premi il tasto di accensione e la tua shell risponde al prompt. Boot, moduli, scheduler, memoria virtuale, VFS, namespace e cgroup: lo strato che tutti usano e quasi nessuno guarda.

"Linux è solo il kernel" — frase vera, ripetuta da almeno un pedante in ogni thread su Internet dal 1991, e comunque il punto di partenza giusto per capire cosa stai davvero eseguendo.

01 / 13

Cos'è davvero un kernel

Non è il "sistema operativo" nel senso in cui lo intende chi installa Ubuntu. È il programma che parla direttamente con l'hardware e decide chi può fare cosa.

💡 L'analogia della portineria

Immagina un palazzo di uffici (l'hardware: CPU, RAM, dischi, rete). Gli inquilini (i tuoi programmi: Firefox, bash, nginx) non possono girare liberamente per i piani tecnici, staccare i cavi o riconfigurare l'ascensore. Tutto passa dalla portineria: se vuoi un pacco (leggere un file), usare il telefono (aprire un socket) o cambiare stanza (allocare memoria), fai una richiesta al portiere. Il portiere è il kernel: l'unico che ha le chiavi di tutto, e ogni richiesta che riceve si chiama system call. Gli inquilini vivono in user space, il portiere lavora in kernel space, e il confine tra i due è sorvegliato dalla CPU stessa (i "ring" o "exception level" del processore).

🔗 Lo strato che sta sotto a tutto

Ogni volta che un programma stampa a schermo, legge un file o apre una connessione di rete, in un modo o nell'altro finisce per chiedere il permesso al kernel.

APPLICAZIONI
bash · firefox · nginx · python3
user space, girano come processi normali
LIBRERIE
glibc · musl · libc
wrappano le syscall in funzioni C comode
SYSCALLconfine ring 3 → ring 0
open() · read() · write() · fork() · mmap()
l'unica porta d'ingresso ufficiale al kernel
SOTTOSISTEMIkernel space
scheduler · VFS · memory manager · net stack
il cuore del kernel, cap. 5-9 di questa guida
DRIVER
ext4 · e1000e · nvme · i915
traducono i sottosistemi in comandi hardware
HARDWARE
CPU · RAM · dischi · scheda di rete
i piani tecnici del palazzo
ModelloIdeaEsempiPro / contro
MonoliticoTutto (scheduler, filesystem, driver, rete) gira in un unico spazio kernel, un'unica "immagine".Linux, i BSDVeloce (nessun context switch tra sottosistemi), ma un bug in un driver può far cadere tutto il kernel
MicrokernelIl kernel fa solo lo stretto indispensabile (IPC, scheduling base); driver e filesystem girano come processi user space separati.Minix 3, QNX, seL4Un driver che crash non porta giù il sistema, ma ogni operazione passa da più hop di messaggi → più overhead
IbridoKernel piccolo "puro" ma con moduli che girano comunque in kernel space per performance.Windows NT, macOS/XNUCompromesso: prova a prendere il meglio di entrambi
💡 Linux è monolitico, ma modulare Il compromesso di Linux: il kernel è un unico grosso binario (monolitico, tutto in kernel space), ma i sottosistemi che non ti servono si possono caricare e scaricare a runtime come moduli (.ko, cap. 4) invece di essere sempre presenti. Non è un microkernel — niente processi separati per i driver — ma non è nemmeno il monolite rigido degli anni '70. È per questo che la stessa immagine di kernel gira su un Raspberry Pi e su un mainframe con 512 core: i moduli giusti si caricano solo dove servono.

🧠 Kernel space vs user space

La CPU stessa impone il confine: su x86 esistono 4 "ring" (0=massimo privilegio, 3=minimo), Linux usa solo ring 0 (kernel) e ring 3 (user, ARM chiama gli stessi concetti EL1/EL0). Un processo user space non può leggere memoria di un altro processo, non può parlare direttamente all'hardware, non può disabilitare gli interrupt. Se ci prova, la CPU genera un'eccezione e il kernel decide cosa fare (di solito: SIGSEGV, "Segmentation fault").

🔐 Perché questo confine esiste

Senza isolamento, un bug (o un malware) in un browser potrebbe leggere la password che stai digitando in un terminale accanto, o scrivere direttamente sul disco bypassando i permessi del filesystem. Il kernel space è l'unico posto dove "tutto è permesso": per questo ogni riga di codice che ci gira dentro è potenzialmente un bug di sicurezza globale, e ogni driver che carichi (anche closed-source, es. NVIDIA) gira con la stessa fiducia del resto del kernel.

02 / 13

Il boot: dal firmware a PID 1

Tra "premi il pulsante" e "vedi il prompt" succedono cinque o sei passaggi distinti, ognuno con un colpevole diverso quando qualcosa non parte.

1. FIRMWARE
BIOS legacy · UEFI
POST hardware, poi cerca un bootloader (MBR o ESP)
2. BOOTLOADER
GRUB2 · systemd-boot · syslinux
carica in RAM vmlinuz + initramfs, passa i parametri
3. KERNELvmlinuz
decompressione, init hardware, mount initramfs
qui il kernel prende il controllo della macchina
4. INITRAMFSroot temporanea in RAM
carica driver LVM/RAID/LUKS/NVMe, trova la vera root
poi fa switch_root verso il disco reale
5. INITPID 1
systemd (quasi tutti) · OpenRC · SysV init
avvia servizi, target, la userland che conosci
💡 Perché serve una "root temporanea" (initramfs)

Per montare il vero filesystem di root, il kernel potrebbe avere bisogno di driver che lui stesso non ha ancora caricato: il modulo per il controller NVMe, quello per l'array RAID software, quello per sbloccare un volume LUKS cifrato con una passphrase. È un problema di uovo e gallina: serve un driver per montare il disco, ma il driver di solito sta... sul disco. La soluzione è l'initramfs: un mini-filesystem compresso, caricato tutto in RAM dal bootloader insieme al kernel, che contiene giusto i moduli e gli strumenti (mdadm, lvm2, cryptsetup) necessari per assemblare la root reale. Una volta trovata e montata, il kernel fa switch_root e getta via l'initramfs: da quel momento vivi sul disco vero.

🔍 Guardare il boot dopo il fatto

i log del kernel
# Il ring buffer del kernel: tutti i messaggi dal boot in poi
dmesg -T | less
dmesg -T | grep -iE "error|fail|warn"

# Stessa cosa via systemd, filtrata per il boot corrente (-b) o uno precedente (-b -1)
journalctl -k -b
journalctl -k -b -1          # il boot PRIMA di questo (utile dopo un crash)

# Quanto ci ha messo ogni fase, e chi ha rallentato l'avvio
systemd-analyze
systemd-analyze blame
systemd-analyze critical-chain
💡 dmesg legge lo stesso ring buffer del kernel che journalctl -k mostra via systemd-journald, con la differenza che il journal lo persiste su disco tra un boot e l'altro (se Storage=persistent in journald.conf). Se il sistema è crashato ieri notte, journalctl -k -b -1 è spesso l'unico modo per vedere cosa è successo.

💾 I file coinvolti

  • /boot/vmlinuz-$(uname -r) — l'immagine del kernel, compressa
  • /boot/initrd.img-$(uname -r) — l'initramfs, generato con mkinitramfs/dracut
  • /boot/grub/grub.cfg — la config generata di GRUB (non toccarla a mano, si rigenera)
  • /etc/default/grub — qui si personalizzano i parametri kernel, poi update-grub
  • /proc/cmdline — i parametri effettivamente passati al kernel in questo boot

⚙️ Parametri kernel comuni

cmdline utili
cat /proc/cmdline

# esempi che troverai spesso in GRUB_CMDLINE_LINUX_DEFAULT
quiet splash                    # boot silenzioso con logo
nomodeset                       # disabilita KMS, utile con GPU rotte
systemd.unit=rescue.target       # boot in modalità rescue
init=/bin/bash                  # bypassa init: recovery estremo
⚠️ "Kernel panic - not syncing" al boot Se il kernel non trova o non riesce a montare la root, va in panic e si ferma li’: è una morte controllata, non un crash silenzioso. Le cause più comuni: initramfs senza il modulo giusto (un nuovo controller NVMe, un array mdadm non assemblato), UUID sbagliato in root=, o un aggiornamento del kernel che non ha rigenerato l'initramfs. Il rimedio quasi sempre è avviare da un kernel precedente nel menu GRUB e rigenerare l'initramfs (update-initramfs -u su Debian/Ubuntu, dracut -f su Fedora/RHEL).
03 / 13

Sorgenti, versioni e come si compila

Il kernel è un progetto open source enorme, sviluppato su git, con un ritmo di rilascio incredibilmente regolare per essere gestito da migliaia di contributor sparsi nel mondo.

🌐 kernel.org e il ciclo di rilascio

Il kernel usa versioning X.Y (es. 6.11), senza un vero significato semantico stile semver: un bump di X non implica breaking change. Ogni ciclo dura circa 9-10 settimane: due settimane di "merge window" (si accettano nuove feature), poi 7-8 "release candidate" (rc1...rc7) solo per stabilizzare e correggere bug, poi il rilascio finale.

RamoCosa contieneQuanto dura
MainlineL'ultima versione, sviluppo attivo di Linus Torvalds~10 settimane per release
StableBackport di fix e sicurezza su una release mainlineFino alla prossima mainline
Longterm (LTS)Stessa idea ma mantenuta per anni, usata dalle distro enterpriseTipicamente 2-6 anni (es. 6.1, 6.6, 6.12 LTS)
Distro kernelUn LTS "congelato" + patch backportate a mano dalla distroIl ciclo di vita della distro (es. Ubuntu HWE, RHEL)
💡 Il kernel che gira sulla tua distro non è quasi mai l'ultima mainline: è un LTS con centinaia di patch di sicurezza e driver backportate a mano dai manutentori della distro. Per questo uname -r mostra spesso un numero tipo 6.8.0-45-generic: 6.8.0 è la base LTS, 45 è il numero della build della distro.

📁 L'albero dei sorgenti (quello che conta davvero)

DirectoryCosa c'è dentro
arch/Codice specifico per CPU: arch/x86, arch/arm64, arch/riscv...
kernel/Il cuore: scheduler, gestione processi, timer, moduli
mm/Memory management: paging, allocatori, swap, OOM killer
fs/VFS e i filesystem: fs/ext4, fs/xfs, fs/proc...
net/Lo stack di rete: net/ipv4, net/netfilter, socket
drivers/La fetta più grande in assoluto: driver per ogni hardware esistente
include/Header condivisi, incluse le definizioni delle syscall
security/I Linux Security Module: SELinux, AppArmor, Smack (cap. 12)
Documentation/Documentazione in testo semplice, sorprendentemente buona
📊 drivers/ da sola è oltre la metà delle righe di codice dell'intero kernel. La ragione per cui l'immagine che scarichi è "solo" qualche decina di MB compressi è che al build entra solo il codice per il tuo hardware: il resto sta lì per compilare kernel diversi da immagini diverse.

🔧 Compilarne uno (per curiosità, non per produzione)

build minimale
git clone --depth 1 https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux

# Parti dalla config del kernel che gia' gira (comodo, eredita i driver giusti)
cp /boot/config-$(uname -r) .config
make olddefconfig          # aggiorna la config alle nuove opzioni
make menuconfig            # o nconfig: interfaccia a menu per toccare le opzioni

# Build vera e propria: usa tutti i core disponibili
make -j$(nproc)

# Installa moduli e immagine (poi serve rigenerare initramfs e grub.cfg)
sudo make modules_install
sudo make install
💥 Un kernel custom non è un giocattolo Compilare va benissimo per capire come funziona o testare una patch, ma su un sistema che devi mantenere nel tempo un kernel fatto in casa perde gli aggiornamenti di sicurezza automatici della distro. Tieni sempre una voce di boot per il kernel della distro come rete di salvataggio: se il tuo custom non parte, quello è il piano B.
04 / 13

Moduli del kernel (LKM)

Il motivo per cui non serve ricompilare il kernel ogni volta che colleghi una webcam nuova: i Loadable Kernel Module.

💡 App installabili, ma per il kernel

Un modulo (.ko, "kernel object") è codice compilato apposta per essere caricato e scaricato dal kernel a runtime, senza riavviare. È come installare un'app sul telefono invece di dover flashare tutto il firmware: il driver per la tua webcam USB non serve finché non la colleghi, quindi resta su disco come modulo e si carica solo quando udev se ne accorge.

🔧 Gestirli dalla riga di comando

lsmod, modprobe, insmod
# Cosa e' caricato ADESSO (letto da /proc/modules)
lsmod

# Info su un modulo: autore, parametri, dipendenze, licenza
modinfo nvidia
modinfo e1000e

# Carica un modulo (modprobe risolve le dipendenze da solo, meglio di insmod)
sudo modprobe vfat
sudo insmod /lib/modules/$(uname -r)/kernel/fs/fat/vfat.ko   # basso livello, no dipendenze automatiche

# Scaricalo (fallisce se qualcosa lo sta ancora usando)
sudo modprobe -r vfat
sudo rmmod vfat

# Ricostruisci l'indice delle dipendenze dopo aver aggiunto moduli
sudo depmod -a
💡 modprobe è quasi sempre lo strumento giusto: legge /lib/modules/$(uname -r)/modules.dep (generato da depmod) e carica in automatico anche le dipendenze. insmod è il livello sotto, usato principalmente da modprobe stesso o per test mirati.

🔄 Caricamento automatico

  • udev carica i moduli in automatico quando rileva hardware nuovo (in base a vendor/device ID)
  • /etc/modules-load.d/*.conf — moduli da caricare sempre al boot
  • /etc/modprobe.d/blacklist.conf — moduli da non caricare mai (es. driver conflittuali, come nouveau vs nvidia)
  • /etc/modprobe.d/*.conf — opzioni da passare a un modulo al caricamento

🏷️ Il "tainted kernel"

Alcuni moduli (tipicamente driver proprietari come quello NVIDIA) non sono sotto licenza GPL: caricarli marca il kernel come tainted ("contaminato"). Non è un errore fatale, ma i manutentori del kernel possono rifiutarsi di aiutarti a debuggare un crash se il kernel è tainted — ragionevole: non hanno il sorgente del modulo che potrebbe essere il colpevole.

verificare
cat /proc/sys/kernel/tainted   # 0 = pulito
dmesg | grep -i taint
05 / 13

Processi e scheduler

Come il kernel decide, migliaia di volte al secondo, quale dei tanti processi in coda ottiene la CPU adesso.

📋 task_struct: cos'è un processo per il kernel

Ogni processo (e ogni thread) è rappresentato nel kernel da una struttura dati C chiamata task_struct: PID, stato, priorità, spazio di indirizzamento, file aperti, credenziali, e molto altro. Un thread per il kernel Linux è semplicemente un task_struct che condivide lo spazio di memoria con altri (creato con clone() e i flag giusti, es. CLONE_VM): non esiste una distinzione "processo vs thread" a basso livello come in altri sistemi, esiste solo "quanto condividono due task tra loro".

Stato (ps/top)SiglaSignificato
Running / RunnableRSta girando sulla CPU o è pronto e in coda
Sleeping (interruptible)SIn attesa di un evento (I/O, timer); può essere svegliato da un segnale
Uninterruptible sleepDIn attesa di I/O che non può essere interrotta — se troppi processi restano qui, spesso è un disco/NFS malato
StoppedTFermato da SIGSTOP o da un debugger
ZombieZTerminato ma il genitore non ha ancora letto il suo exit status con wait()
⚠️ Uno zombie non si "uccide" Un processo in stato Z è già morto: non consuma CPU né memoria, resta solo la sua entry nella process table finché il genitore non fa wait(). kill -9 su uno zombie non fa nulla (non c'è più nessun processo da uccidere). Se ne hai a centinaia, il colpevole è il genitore che non raccoglie mai i figli: va riavviato o corretto lui.

CFS → EEVDF: la storia recente

Dal 2007 al 2023 Linux ha usato il Completely Fair Scheduler: modella il tempo CPU come una risorsa da dividere "equamente" usando un red-black tree ordinato per "virtual runtime" (chi ha girato meno, va prima). Dal kernel 6.6 lo scheduler di default è EEVDF (Earliest Eligible Virtual Deadline First): stessa filosofia di equità, ma modella meglio la latenza per i task interattivi, dando priorità a chi "ha diritto" di girare prima secondo una scadenza virtuale.

🎯 Nice, priorità e real-time

Il nice value (-20 a +19) è un suggerimento di priorità per lo scheduling "normale" (CFS/EEVDF): più basso = più CPU relativa. Sopra a tutto questo esistono le classi real-time (SCHED_FIFO, SCHED_RR): un task RT gira sempre prima di qualsiasi task normale, a costo di poter "affamare" il resto del sistema se scritto male — per questo servono privilegi speciali per usarle.

🔍 Ispezionare processi e priorità

ps, nice, chrt
# Vista con priorita' e nice value
ps -eo pid,ppid,ni,pri,stat,comm --sort=-pri | head

# Avvia un processo con priorita' bassa (buono citizen, non rallenta gli altri)
nice -n 15 ./backup-notturno.sh

# Cambia il nice di un processo gia' in corsa
sudo renice -n -5 -p 4821

# Real-time scheduling (richiede CAP_SYS_NICE): usalo solo se sai cosa fai
sudo chrt -f -p 50 4821       # SCHED_FIFO, priorita' 50
chrt -p 4821                  # mostra la policy attuale

# Dettagli grezzi di un processo, letti da procfs
cat /proc/4821/status | grep -E "State|Threads"
06 / 13

Gestione della memoria

Ogni processo crede di avere tutta la RAM per sé. È una bugia elaborata, e il kernel è quello che la mantiene credibile.

💡 L'illusione della memoria virtuale

Ogni processo vede uno spazio di indirizzi tutto suo, tipicamente da 0 a qualche exabyte teorico, come se fosse solo. In realtà quello spazio è virtuale: la MMU (Memory Management Unit, hardware nella CPU) traduce ogni indirizzo virtuale in un indirizzo fisico reale usando delle tabelle delle pagine che il kernel mantiene per ogni processo. Due processi possono usare lo stesso indirizzo virtuale 0x400000 senza mai pestarsi i piedi, perché puntano a pagine fisiche diverse. È anche così che l'isolamento tra processi è possibile: nessuno può nemmeno "vedere" la memoria fisica di un altro, semplicemente perché non ha le tabelle per tradurla.

📊 Paging

La memoria è divisa in pagine (tipicamente 4 KiB, o "huge page" da 2 MiB/1 GiB per ridurre l'overhead di traduzione su carichi pesanti). Ogni pagina virtuale mappa una pagina fisica, oppure è marcata "non presente": se un processo ci accede, scatta un page fault e il kernel decide cosa fare (caricarla da disco, allocarla on-demand, o terminare il processo se l'accesso è illegale).

💾 Swap

Quando la RAM fisica scarseggia, il kernel può spostare pagine usate raramente su disco (swap), liberando RAM per chi ne ha bisogno adesso. Su hardware moderno con tanta RAM lo swap serve più come "valvola di sicurezza" che come uso quotidiano; su sistemi con poca RAM (o desktop con hibernation) resta fondamentale. Il parametro vm.swappiness (0-200) regola quanto aggressivamente il kernel preferisce swappare rispetto a liberare cache.

🚫 L'OOM killer

Linux di default fa overcommit della memoria: permette di allocare più memoria virtuale di quanta RAM+swap esista fisicamente, scommettendo che non tutti i processi useranno davvero tutto quello che hanno chiesto. Quando la scommessa va male e la memoria finisce per davvero, entra in scena l'OOM killer (Out-Of-Memory killer): sceglie un processo "colpevole" (in base a memoria usata, tempo di vita, e uno score regolabile) e lo termina con SIGKILL per salvare il resto del sistema.

capire chi è stato ucciso, e proteggere un processo
# Cerca "Out of memory: Killed process" nei log
dmesg | grep -i "killed process"
journalctl -k | grep -i oom

# oom_score: piu' alto = piu' probabile candidato alla morte (0-1000)
cat /proc/4821/oom_score

# oom_score_adj: -1000 = mai ucciso, +1000 = prima scelta
echo -500 | sudo tee /proc/4821/oom_score_adj
⚠️ Il database che "si spegne da solo" nei log di sistema senza un errore proprio, spesso non è crashato: è stato ucciso dall'OOM killer. Prima di dare la colpa all'applicazione, controlla sempre dmesg.

🔎 Guardare l'uso della memoria

free, vmstat, /proc/meminfo
free -h                       # totale, usata, cache/buffer, disponibile
vmstat 1 5                     # snapshot ogni secondo, 5 volte: si/so = swap in/out
cat /proc/meminfo | head -20    # il dettaglio grezzo che free riassume
sysctl vm.swappiness           # quanto e' propenso a swappare (0-200)
💡 La colonna available di free -h è quella che conta davvero, non "free": Linux usa la RAM libera come cache del filesystem in modo aggressivo (buona cosa!), quindi "free" bassa non significa "stai per finire la memoria". "Available" già considera quanta cache può essere liberata all'istante se serve.
07 / 13

VFS: "tutto è un file", davvero

Come lo stesso comando read() funziona identico su un file ext4, un socket di rete e uno pseudo-file in /proc.

💡 L'adattatore universale

Il Virtual File System è uno strato di astrazione tra le syscall generiche (open(), read(), write(), close()) e i filesystem reali che implementano quelle operazioni ognuno a modo suo. Come una presa multipla universale che accetta la spina di qualsiasi paese: il programma chiama sempre read(fd, buf, size), e il VFS instrada la chiamata verso ext4, XFS, NFS, o un file virtuale in /proc a seconda di dove sta quel file descriptor. L'applicazione non sa (e non le importa) quale filesystem c'è sotto.

📁 I tre mattoncini del VFS

  • superblock — i metadati di un intero filesystem montato (tipo, dimensione, stato)
  • inode — i metadati di un singolo file: permessi, proprietario, dimensione, timestamp, puntatori ai blocchi dati (non il nome!)
  • dentry ("directory entry") — la coppia nome↔inode, cachata aggressivamente per rendere veloce la risoluzione dei path

🔗 Perché il nome non è nell'inode

È il motivo per cui un file può avere più nomi (hard link): sono dentry diverse che puntano allo stesso inode. Cancellare un nome (unlink(), quello che fa rm) decrementa solo un contatore di riferimenti nell'inode; i dati spariscono davvero solo quando l'ultimo link sparisce e nessun processo tiene ancora il file aperto — per questo su Linux puoi cancellare un file di log mentre un servizio ci sta ancora scrivendo, senza errori, e lo spazio si libera solo quando il servizio lo chiude o riavvia.

Filesystem virtualeDove montaCosa espone
procfs/procProcessi e stato del kernel come file (cap. 11)
sysfs/sysIl device model: hardware, driver, moduli (cap. 8)
tmpfs/tmp, /runFilesystem che vive solo in RAM, sparisce al reboot
devtmpfs/devNodi dei device, popolati automaticamente dal kernel + udev
overlayfsusato da DockerUnisce più directory in una vista sola (layer immutabili + uno scrivibile)
cgroupfs/sys/fs/cgroupControllo risorse per gruppi di processi (cap. 10)
💾 I filesystem "reali" (ext4, XFS, Btrfs...) e la loro scelta sono territorio della guida Gestione Dischi: qui interessa solo come il kernel li rende tutti intercambiabili dal punto di vista di chi programma, non quale sia meglio per il tuo caso d'uso.
08 / 13

Device model e driver

Come un pezzo di hardware collegato diventa un file in /dev che qualsiasi programma può usare con open() e read().

📄

Char device

Flusso di byte sequenziale: seriali, tastiere, /dev/null, /dev/urandom

💾

Block device

Accesso ad blocchi indicizzati: dischi, partizioni, /dev/sda

🌐

Network device

Niente file in /dev: gestiti via socket, es. eth0, wlan0

🔢 Major e minor number

Ogni device char o block ha un numero major (identifica il driver) e minor (identifica l'istanza specifica). Quando apri /dev/sda1, il kernel guarda major/minor associati a quel nodo, trova il driver giusto tramite major, e passa il minor al driver per sapere "quale" disco/partizione specifica.

vederli
ls -l /dev/sda /dev/null
# brw-rw---- 1 root disk 8, 0 ... /dev/sda      ← major 8, minor 0
# crw-rw-rw- 1 root root 1, 3 ... /dev/null     ← major 1, minor 3

cat /proc/devices             # tutti i major registrati, char e block

🔌 udev: chi popola /dev

Il kernel notifica gli eventi hardware (collegato/scollegato) via uevent; udevd li ascolta e crea/rimuove i nodi in /dev con nomi persistenti e simbolici, oltre a caricare i moduli giusti e far girare regole custom (permessi, alias).

ispezionare
udevadm info --query=all --name=/dev/sda
udevadm monitor              # eventi live: collega una chiavetta
lsblk -o NAME,MAJ:MIN,MODEL

🏷️ sysfs: l'albero dei device

/sys/devices rappresenta la topologia fisica reale del sistema (bus PCI, USB, piattaforma), con /sys/class e /sys/block come "viste" più comode organizzate per tipo. Sono link simbolici che puntano tutti alla stessa struttura interna del kernel: il device model unificato introdotto con il kernel 2.6.

esplorare
ls /sys/class/net/            # interfacce di rete
cat /sys/block/sda/queue/rotational   # 0=SSD, 1=HDD
lspci -k                     # device PCI + driver in uso
09 / 13

Lo stack di rete

Dal socket che apre il tuo programma fino al pacchetto che esce dalla scheda di rete, passando per i punti in cui firewall e NAT si infilano nel percorso.

🔌 I socket: l'API che vede l'applicazione

Un'applicazione non "parla TCP" direttamente: chiama socket(), bind(), listen(), accept(), send()/recv(), tutte syscall che il VFS tratta quasi come file (i socket hanno persino un file descriptor). Sotto, il kernel gestisce l'intero stack: segmentazione TCP, routing IP, ARP, il driver della scheda fisica.

💡 I 5 varchi di netfilter

Il framework netfilter inserisce dei "punti di aggancio" (hook) lungo il percorso che un pacchetto fa dentro il kernel, dove le regole di firewall/NAT possono ispezionare, modificare o scartare il traffico. Sono la base su cui sono costruiti sia il vecchio iptables che il moderno nftables (la guida Networking copre in dettaglio come scriverle).

PREROUTING
appena il pacchetto entra, prima del routing
tipico posto per il DNAT (port forwarding)
INPUT
destinato a un processo locale
il firewall "classico" per la macchina stessa
↓ / →
FORWARD
pacchetto di passaggio (routing/NAT)
qui vive un firewall che fa da router
OUTPUT
generato da un processo locale
traffico in uscita dalla macchina stessa
POSTROUTING
appena prima di uscire fisicamente
tipico posto per SNAT/masquerade

🔍 Guardare lo stack in azione

socket, routing, sysctl di rete
# Socket in ascolto/connessi, con processo
ss -tulpn

# Tabella di routing del kernel
ip route show

# Parametri kernel per il networking: forwarding, buffer, tuning TCP
sysctl net.ipv4.ip_forward     # 0/1: la macchina fa da router?
sysctl net.core.somaxconn      # backlog massimo per listen()

# Regole netfilter attive (nftables moderno)
sudo nft list ruleset
💡 La scrittura delle regole di firewall, il NAT, VPN e DNS sono territorio della guida Networking: qui l'obiettivo era solo capire dove, dentro il kernel, quelle regole si agganciano.
10 / 13

Namespace e cgroup: la base dei container

Docker, Podman e Kubernetes non hanno inventato un modo nuovo di isolare processi: hanno impacchettato due feature del kernel che esistono da anni.

💡 Due problemi diversi, due meccanismi diversi

Un container ha bisogno di due cose: non vedere quello che non gli compete (altri processi, altra rete, altri mount) e non consumare più risorse di quelle assegnate. Il primo problema lo risolvono i namespace (isolamento della vista), il secondo i cgroup (limitazione delle risorse). Un container Docker è, in sostanza, un processo Linux normale lanciato dentro namespace dedicati e sotto un cgroup con dei limiti — non c'è nessuna virtualizzazione hardware coinvolta, per questo i container partono in millisecondi e le VM in secondi.

NamespaceIsola
PIDNumerazione dei processi: dentro il container, il primo processo è visto come PID 1
NETInterfacce di rete, tabelle di routing, porte: ogni container ha il suo stack di rete
MNTIl punto di vista sui mount: la root del container è una directory qualsiasi dell'host
UTSHostname e nome di dominio
IPCCoda messaggi, semafori e memoria condivisa System V
USERMappatura UID/GID: root dentro il namespace può essere un utente non privilegiato fuori
CGROUPLa vista sulla propria gerarchia di cgroup
TIMEOffset dell'orologio monotonico/boot, per far credere a un container di essere avviato in un altro momento

⚖️ cgroup v1 vs v2

I control group raggruppano processi e impongono limiti su CPU, memoria, I/O e altro. La v1 aveva una gerarchia separata per ogni tipo di risorsa (confusionario); la v2 (ormai lo standard, usata da systemd e Docker moderni) usa un'unica gerarchia unificata sotto /sys/fs/cgroup, più semplice da ragionare e da assegnare.

giocare con namespace e cgroup a mano
# Crea un nuovo namespace PID+mount ed esegui una shell dentro (un "container" primitivo)
sudo unshare --pid --mount --fork --mount-proc bash

# Elenca i namespace attivi sul sistema
lsns

# La vista ad albero dei cgroup che systemd mantiene
systemd-cgls

# Limite di memoria di un servizio (cgroup v2, via systemd)
systemctl set-property nginx.service MemoryMax=512M
cat /sys/fs/cgroup/system.slice/nginx.service/memory.max
🐋 Approfondire come Docker/Kubernetes usano questi meccanismi (immagini, orchestrazione, rete overlay) è il territorio delle guide Docker e Kubernetes. Qui interessava solo il pavimento su cui sono costruiti.
11 / 13

Introspezione e tuning

Il kernel espone quasi tutto il suo stato interno come semplici file di testo. Non serve un debugger per capire cosa sta facendo: serve sapere dove guardare.

📁 /proc: processi e stato del kernel

  • /proc/[pid]/ — tutto su un processo: status, cmdline, fd/, maps, environ
  • /proc/cpuinfo, /proc/meminfo — hardware e memoria
  • /proc/sys/ — gli stessi parametri regolabili con sysctl, esposti come file
  • /proc/version, /proc/cmdline — con cosa è stato avviato il kernel

⚙️ sysctl: il tuning a runtime

leggere e scrivere parametri kernel
sysctl -a | grep swappiness
sudo sysctl -w vm.swappiness=10       # applica subito, non persiste
# persistente: /etc/sysctl.d/99-custom.conf
sudo sysctl --system                 # ricarica tutti i file sysctl.d

🔎 Debug e profiling più avanzati

StrumentoA cosa serve
straceTraccia le syscall di un processo: il primo strumento quando "non si capisce cosa fa"
ltraceCome strace ma per le chiamate a librerie condivise (user space)
perfProfiler basato sui contatori hardware della CPU: dove va davvero il tempo
ftraceTracer integrato nel kernel: latenza, funzioni chiamate, scheduling
eBPF / bpftraceProgrammi sandboxed iniettati nel kernel a runtime: tracing e networking programmabili senza moduli custom
un assaggio
strace -f -e trace=open,openat -p 4821   # quali file apre un processo in corsa
sudo perf top                             # cosa consuma CPU, in tempo reale
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve { printf("%s\n", comm); }'
💡 eBPF è probabilmente lo sviluppo più importante nel kernel Linux dell'ultimo decennio: permette di eseguire piccoli programmi dentro il kernel in modo sicuro (verificati staticamente prima del caricamento), senza scrivere un modulo. È la base di strumenti come Cilium (networking Kubernetes), Falco (security runtime) e gran parte dell'osservabilità moderna.
12 / 13

Sicurezza del kernel

Il kernel gira con la fiducia massima possibile sul sistema. Ridurre cosa "root" può davvero fare è una battaglia continua, non un interruttore.

🔑 Capabilities: root diviso a fette

Storicamente su Unix esisteva solo "root" (può tutto) e "non-root" (può poco). Le capabilities spezzano i privilegi di root in ~40 permessi granulari: CAP_NET_BIND_SERVICE (aprire porte <1024), CAP_SYS_ADMIN (il "quasi tutto" rimasto, da evitare), CAP_NET_RAW (socket raw, es. ping). Un processo può avere solo la capability che gli serve, senza essere root vero.

vedere e assegnare
getcap /usr/bin/ping
sudo setcap cap_net_bind_service=+ep /usr/bin/miod

🛡️ LSM: Linux Security Modules

Un framework di hook sparsi in tutto il kernel dove moduli di sicurezza possono decidere se permettere o negare un'operazione, oltre ai normali permessi Unix. SELinux (label-based, RHEL/Fedora) e AppArmor (path-based, Debian/Ubuntu/SUSE) sono i due più diffusi: la guida AppArmor copre in dettaglio come si scrivono i profili.

🔒 seccomp: ridurre le syscall disponibili

Seccomp-bpf permette a un processo di dichiarare "da ora in poi posso chiamare solo queste 30 syscall, tutte le altre uccidimi o rifiutale": un runtime di container come Docker applica un profilo seccomp di default che blocca decine di syscall pericolose o inutili (es. caricare moduli kernel, montare filesystem) senza che l'applicazione dentro se ne accorga.

✓ Hardening che vale la pena

  • Kernel LTS aggiornato, non "quello che c'era all'installazione"
  • AppArmor/SELinux attivi e in modalità enforcing, non solo "logging"
  • kernel.dmesg_restrict=1 — nasconde il ring buffer a utenti non privilegiati
  • kernel.kptr_restrict=2 — nasconde indirizzi di memoria del kernel nei log
  • KASLR attivo (default oggi): randomizza dove il kernel si carica in memoria
  • kernel.unprivileged_bpf_disabled=1 se non ti serve eBPF non privilegiato

✗ Errori comuni

  • Disabilitare AppArmor/SELinux "perché dava fastidio" invece di scrivere un profilo
  • Caricare moduli non firmati con Secure Boot attivo, poi non capire perché non parte
  • Ignorare i CVE del kernel pensando "tanto non sono esposto"
  • Dare CAP_SYS_ADMIN quando serve una capability specifica più ristretta
  • Girare container privilegiati (--privileged) per pigrizia invece di capire quale capability manca davvero
13 / 13

Cheat Sheet & Glossario

Tutto quello che serve, su una pagina. Bookmark questa sezione e dimentica il resto.

🐧 Info sul kernel

versione & boot
uname -a
cat /proc/version
cat /proc/cmdline
dmesg -T | less
journalctl -k -b
systemd-analyze blame

🔧 Moduli

lsmod, modprobe
lsmod
modinfo nome_modulo
sudo modprobe nome_modulo
sudo modprobe -r nome_modulo
sudo depmod -a

Processi & memoria

ps, nice, free
ps -eo pid,ni,pri,stat,comm
sudo renice -n -5 -p PID
free -h
cat /proc/PID/status
dmesg | grep -i "killed process"

🔌 Device & rete

udev, sysfs, socket
udevadm info --query=all --name=/dev/sda
lspci -k
ss -tulpn
sudo nft list ruleset

📈 Namespace, cgroup & tuning

unshare, sysctl
lsns
systemd-cgls
sysctl -a | grep swappiness
sudo sysctl -w vm.swappiness=10

🔎 Debug

strace, perf
strace -f -p PID
sudo perf top
cat /proc/sys/kernel/tainted
getcap /usr/bin/ping

📚 Glossario essenziale

  • Syscall — l'unica porta ufficiale tra user space e kernel space
  • Kernel space / user space — i due livelli di privilegio imposti dalla CPU
  • task_struct — la struttura dati che rappresenta un processo/thread
  • Scheduler (EEVDF/CFS) — decide chi gira sulla CPU e per quanto
  • Page fault — eccezione generata accedendo a memoria virtuale non mappata
  • OOM killer — termina processi quando la memoria finisce davvero
  • VFS — lo strato che astrae tutti i filesystem dietro le stesse syscall
  • inode / dentry — metadati di un file e la sua voce nome↔inode
  • initramfs — filesystem temporaneo in RAM usato solo per montare la root reale
  • Modulo (.ko) — codice kernel caricabile/scaricabile a runtime
  • Major/minor number — identificano driver e istanza di un device
  • udev — il demone che popola /dev in base agli eventi hardware
  • sysfs / procfs — filesystem virtuali che espongono lo stato del kernel
  • netfilter — i punti di aggancio kernel usati da iptables/nftables
  • Namespace — isola la vista di un processo su PID/rete/mount/etc.
  • cgroup — limita quante risorse un gruppo di processi può usare
  • Capability — privilegio granulare, frazione dei poteri storici di root
  • LSM — framework su cui girano SELinux/AppArmor
  • seccomp — filtro che restringe quali syscall un processo può chiamare
  • eBPF — programmi sandboxed eseguiti dentro il kernel senza un modulo custom
  • Tainted kernel — marcato dopo il caricamento di moduli non-GPL o non firmati

📚 Risorse

  • kernel.org — sorgenti ufficiali, changelog, release corrente
  • Documentation/ nel sorgente — sorprendentemente leggibile, parte da lì
  • Linux Kernel Development di Robert Love — il libro di riferimento classico
  • LWN.net — il meglio del giornalismo tecnico sullo sviluppo kernel
  • man7.org — man page di syscall e strumenti, di gran lunga le migliori online

🔗 Guide collegate

  • Gestione Dischi — i filesystem reali che il VFS astrae
  • macOS — XNU, il cugino ibrido Mach+BSD di questo stesso kernel
  • Android — lo stesso kernel Linux, con Binder e Zygote sopra
  • Linux Admin — systemd, SSH, il mestiere quotidiano sopra il kernel
  • Networking — nftables, VPN, DNS: cosa costruire sopra netfilter
  • AppArmor — profili LSM, capabilities e seccomp in pratica
  • Docker — namespace e cgroup impacchettati in container
🐧
Regola finale dello svogliato — non ti serve conoscere ogni riga di drivers/ per amministrare Linux bene. Ti serve sapere che dmesg è il primo posto dove guardare quando qualcosa si comporta in modo strano, che l'OOM killer esiste e lascia tracce, e che namespace/cgroup sono la ragione per cui i container non sono magia. Il resto lo cerchi quando ti serve davvero, con questa guida come mappa di dove guardare.