Driver Linux e dispositivi: come il kernel comunica con l’hardware
Scopri come Linux comunica con dischi, tastiere, schede di rete e GPU attraverso driver, firmware, interrupt, DMA, power management e dispositivi presenti in /dev.
Quando premi un tasto sulla tastiera, salvi un documento su un SSD, apri un sito Internet o riproduci un video, Linux deve comunicare con componenti hardware molto diversi tra loro.
Una tastiera invia eventi. Un disco legge e scrive blocchi di dati. Una scheda di rete trasmette pacchetti. Una GPU elabora immagini, video e grafica tridimensionale.
Dal punto di vista dell’utente, tutto questo sembra avvenire in modo immediato. Accendiamo il computer, colleghiamo una periferica e ci aspettiamo che funzioni. Dietro questa apparente semplicità, però, esiste una struttura composta da più livelli.
Le applicazioni non comunicano direttamente con l’hardware. Un browser non controlla autonomamente la scheda di rete e un editor di testo non conosce il funzionamento interno del disco sul quale salva un documento.
Tra i programmi e i componenti fisici della macchina si trova il kernel Linux. Il kernel gestisce le risorse del sistema e utilizza componenti specializzati, chiamati device driver, per controllare i dispositivi.
In questo articolo vedremo che cos’è un driver Linux, come comunica con l’hardware, quale differenza esiste tra driver e firmware e che ruolo svolgono interrupt, DMA, power management e file dispositivo presenti nella directory /dev.
Come Linux comunica con l’hardware
Nei precedenti episodi della serie Capire Linux abbiamo rappresentato Linux come un sistema organizzato a livelli.
In alto troviamo l’utente e le applicazioni. Sotto le applicazioni troviamo librerie e utility. Quando un programma deve effettuare un’operazione controllata, utilizza una system call per chiedere un servizio al kernel.
Il kernel riceve la richiesta, verifica che l’operazione sia consentita e la inoltra al sottosistema corretto. Può trattarsi del filesystem, della rete, della memoria, del sistema grafico oppure del sottosistema di input.
Se l’operazione deve raggiungere un componente fisico, entra in gioco il driver.
Il percorso generale può essere rappresentato in questo modo:
Utente
↓
Applicazione
↓
Librerie
↓
System call
↓
Kernel
↓
Driver
↓
Dispositivo hardware
Immaginiamo di voler salvare un documento.
L’applicazione prepara i dati e utilizza una libreria per richiedere una scrittura. La richiesta arriva al kernel attraverso una system call. Il kernel controlla i permessi, interagisce con il filesystem e individua il dispositivo sul quale devono essere salvati i dati.
A quel punto il kernel utilizza il driver del controller di archiviazione. Il driver trasforma la richiesta generale di scrittura in istruzioni comprensibili dall’SSD o dal disco.
L’utente vede soltanto il documento salvato, ma sotto quella semplice operazione hanno collaborato applicazione, librerie, system call, filesystem, kernel, driver e hardware.
Che cos’è un device driver
Un device driver, o driver di dispositivo, è un componente software che permette al kernel di comunicare con uno specifico tipo di hardware.
Ogni dispositivo possiede caratteristiche proprie. Un disco NVMe non funziona come una tastiera USB. Una scheda di rete non utilizza gli stessi comandi di una GPU. Una webcam produce dati diversi da quelli generati da una scheda audio.
Il kernel non può trattare tutti questi componenti nello stesso identico modo. Ha quindi bisogno di un elemento che conosca il comportamento specifico del dispositivo.
Il driver sa come inizializzare l’hardware, quali comandi inviargli, come leggere il suo stato e come interpretare i dati ricevuti.
Possiamo immaginarlo come un interprete.
Da una parte si trova il kernel, che ragiona attraverso operazioni relativamente generali come leggere, scrivere, inviare, ricevere, attivare o sospendere. Dall’altra parte si trova il dispositivo, che segue un protocollo specifico stabilito dal produttore o da uno standard hardware.
Il driver traduce le richieste del kernel nel linguaggio utilizzato dal dispositivo.
Il driver non è un’applicazione
Nel linguaggio comune viene talvolta chiamato driver qualsiasi programma fornito insieme a una periferica. In realtà è utile distinguere i diversi componenti.
Il driver è il software che permette al kernel di controllare l’hardware. Una utility di configurazione è invece un programma che consente all’utente di modificare alcune impostazioni. L’applicazione utilizza le funzionalità offerte dal dispositivo, mentre il firmware è software eseguito normalmente all’interno del dispositivo stesso.
Prendiamo come esempio una scheda grafica.
Il suo funzionamento può richiedere un modulo all’interno del kernel, un firmware caricato sulla GPU, alcune librerie grafiche nello user space e un pannello di configurazione utilizzabile dall’utente.
Tutti questi elementi collaborano, ma non sono la stessa cosa.
Il pannello di configurazione non è il driver. Le librerie grafiche non coincidono con il firmware. Il modulo del kernel non è l’applicazione che utilizza la GPU.
Questa distinzione diventa molto utile quando dobbiamo diagnosticare un problema.
Perché i driver lavorano nel kernel space
Molti driver Linux operano nel kernel space, cioè nella parte privilegiata del sistema operativo.
Questa posizione è necessaria perché un driver deve poter interagire con memoria, registri hardware, controller, interrupt e altre strutture interne del kernel.
Un driver deve poter configurare il dispositivo, inviare comandi e gestire trasferimenti di dati. Per svolgere queste operazioni ha bisogno di privilegi molto più elevati rispetto a quelli posseduti da un normale programma.
Questa potenza rende però i driver particolarmente delicati.
Quando un’applicazione normale va in errore, il problema rimane spesso confinato al suo processo. Il programma può chiudersi, ma il resto del sistema continua a funzionare.
Un errore in un driver può invece influenzare l’intero kernel. Può provocare instabilità, blocchi, perdita di dati o un kernel panic.
Per questo motivo il codice dei driver deve essere sviluppato con particolare attenzione. Un driver non gestisce soltanto un programma: lavora direttamente nel livello che controlla la macchina.
Linux è monolitico, ma anche modulare
Linux viene spesso definito un kernel monolitico con capacità modulari.
Monolitico significa che molti servizi fondamentali, tra cui gestione della memoria, processi, rete, filesystem e dispositivi, lavorano all’interno del kernel.
Modulare significa invece che alcune funzionalità possono essere aggiunte o rimosse dinamicamente sotto forma di moduli.
I moduli del kernel utilizzano generalmente l’estensione .ko, che significa kernel object.
Un driver può essere compilato direttamente all’interno del kernel oppure distribuito come modulo caricabile. Quando Linux rileva un determinato dispositivo, può caricare automaticamente il modulo adatto.
Questo meccanismo consente allo stesso kernel di funzionare su computer molto diversi.
Un server, un portatile, un dispositivo embedded e una macchina virtuale non hanno lo stesso hardware. Non è quindi necessario mantenere sempre attivo il codice per ogni periferica mai prodotta.
Linux può caricare i componenti necessari in base alla macchina sulla quale viene eseguito.
Il comando lsmod permette di vedere i moduli attualmente caricati:
lsmod
Il comando modinfo mostra invece informazioni su un modulo specifico:
modinfo nome_modulo
Per richiedere il caricamento di un modulo si può utilizzare modprobe:
sudo modprobe nome_modulo
Nella maggior parte dei casi non è necessario eseguire manualmente queste operazioni, perché la distribuzione riconosce l’hardware e carica automaticamente i driver appropriati.
Che cos’è l’Hardware Abstraction Layer
Quando parliamo del collegamento tra kernel e dispositivi possiamo incontrare l’espressione Hardware Abstraction Layer, spesso abbreviata in HAL.
Non dobbiamo necessariamente immaginare la HAL come un singolo programma. È soprattutto un concetto architetturale.
L’obiettivo è nascondere una parte della complessità dell’hardware e offrire ai sottosistemi del kernel interfacce più uniformi.
Il filesystem deve poter leggere e scrivere dati senza conoscere ogni dettaglio elettronico di ogni SSD esistente. Lo stack di rete deve poter inviare pacchetti senza essere riscritto completamente per ogni modello di scheda Ethernet. Il sistema grafico deve poter utilizzare GPU differenti attraverso interfacce coerenti.
Questo livello di astrazione comprende driver, gestori degli interrupt, interfacce firmware, gestione del DMA e power management.
L’astrazione non elimina le differenze tra i dispositivi, ma le concentra nei componenti che devono conoscerle.
Il resto del sistema può così lavorare con modelli più generali.
Esempio: come Linux legge un file dal disco
Il filesystem organizza file e directory, ma non controlla direttamente i circuiti dell’SSD.
Immaginiamo di aprire il seguente documento:
/home/mario/documenti/appunti.txt
L’applicazione richiede al kernel di aprire il file. Il kernel controlla che il percorso esista e verifica che l’utente possieda i permessi necessari.
Il filesystem determina dove si trovano i dati del documento. Questi dati sono memorizzati in blocchi presenti su un dispositivo di archiviazione.
La richiesta viene quindi inoltrata al sottosistema di input e output. Il driver del controller traduce la lettura in comandi che il disco o l’SSD sono in grado di comprendere.
Il dispositivo recupera i dati e li trasferisce in memoria. Il kernel li restituisce infine all’applicazione, che mostra il contenuto del documento.
In questa operazione dobbiamo distinguere il file dal dispositivo.
Il percorso:
/home/mario/documenti/appunti.txt
indica un oggetto gestito dal filesystem.
Un percorso come:
/dev/nvme0n1
rappresenta invece un dispositivo di archiviazione.
Il primo è un file organizzato dentro una struttura di directory. Il secondo è un punto di accesso al dispositivo fisico o logico sul quale può essere presente il filesystem.
Esempio: la tastiera
Quando premi un tasto, la tastiera genera un evento.
Il dispositivo invia l’informazione attraverso il proprio collegamento, che può essere USB, Bluetooth oppure un’altra interfaccia.
Il driver riceve i dati e li trasmette al sottosistema di input del kernel. Il sottosistema li trasforma in un evento più uniforme, che può essere utilizzato dalla console, dall’ambiente grafico o dall’applicazione attiva.
Il percorso semplificato è questo:
Tastiera
↓
Controller hardware
↓
Driver
↓
Sottosistema input del kernel
↓
Ambiente grafico o console
↓
Applicazione
L’editor di testo non deve conoscere il protocollo specifico utilizzato dalla tastiera. Riceve un evento già interpretato dal sistema.
Questo permette allo stesso programma di funzionare con migliaia di tastiere differenti.
Il dispositivo fisico può cambiare, ma l’interfaccia offerta all’applicazione rimane coerente.
Esempio: la scheda di rete
Nel caso della rete, il percorso coinvolge diversi componenti del kernel.
Un’applicazione apre un socket e invia dei dati. Il kernel gestisce il protocollo TCP o UDP, gli indirizzi IP, le porte, il routing e le regole del firewall.
Dopo aver elaborato queste informazioni, deve consegnare i dati alla scheda di rete.
Il driver configura il dispositivo, prepara le code di trasmissione e invia i pacchetti all’hardware.
Quando la scheda riceve nuovi dati, comunica l’evento al kernel. Il driver recupera i pacchetti e li consegna allo stack TCP/IP.
Il kernel individua quindi il socket corretto e trasferisce i dati al processo interessato.
L’interfaccia di rete, per esempio enp3s0 o wlp2s0, è l’oggetto logico con cui Linux identifica il punto di comunicazione.
La scheda è il componente fisico. Il driver collega il componente hardware al sottosistema di rete del kernel.
Esempio: la GPU
La GPU è uno dei dispositivi più complessi presenti in un computer moderno.
Non si limita a mostrare immagini sul monitor. Può gestire rendering tridimensionale, decodifica video, calcolo parallelo, memoria dedicata, accelerazione grafica e più schermi.
Per questo motivo il supporto grafico Linux coinvolge generalmente sia componenti del kernel sia componenti nello user space.
L’applicazione utilizza librerie grafiche come OpenGL o Vulkan. Queste librerie comunicano con componenti del driver presenti nello user space. Le richieste arrivano quindi al kernel, che controlla l’accesso al dispositivo, la memoria e la sincronizzazione.
Il percorso può essere rappresentato così:
Applicazione
↓
Librerie grafiche
↓
Componenti del driver nello user space
↓
Kernel
↓
Modulo del driver
↓
GPU
Questa struttura spiega perché un problema grafico può dipendere da più livelli.
Il modulo del kernel potrebbe non essere compatibile. Potrebbe mancare il firmware. Le librerie grafiche potrebbero essere incomplete. Wayland, X11 o l’ambiente desktop potrebbero utilizzare una configurazione non adatta.
Dire semplicemente che “il driver video non funziona” può quindi indicare problemi molto diversi.
Che cos’è un interrupt
Finora abbiamo immaginato il kernel che invia richieste ai dispositivi.
In molte situazioni, però, è il dispositivo ad avere bisogno di richiamare l’attenzione del sistema.
Questo avviene attraverso un interrupt.
Un interrupt è un segnale con cui l’hardware comunica alla CPU che si è verificato un evento.
Una tastiera può segnalare che è stato premuto un tasto. Una scheda di rete può comunicare l’arrivo di un pacchetto. Un controller di archiviazione può informare il sistema che una lettura è terminata.
Senza gli interrupt, la CPU dovrebbe controllare continuamente ogni dispositivo chiedendo se sono disponibili nuovi dati oppure se un’operazione è stata completata.
Questa attività viene chiamata polling.
Il polling può essere utile in alcuni contesti, ma controllare continuamente tutti i dispositivi sarebbe spesso inefficiente.
Con gli interrupt, l’hardware può notificare il kernel soltanto quando si verifica qualcosa di rilevante.
Come funziona un interrupt handler
Quando arriva un interrupt, il kernel esegue un interrupt handler, cioè un gestore progettato per rispondere a quel particolare evento.
Il gestore identifica la sorgente dell’interrupt, controlla ciò che è accaduto ed esegue le operazioni più urgenti.
Questa fase deve essere rapida. Mentre viene gestito un interrupt, il kernel si trova in un contesto particolarmente delicato.
Le attività più lunghe vengono quindi spesso rimandate a un momento successivo.
Il percorso generale è questo:
Evento hardware
↓
Interrupt
↓
Interrupt handler
↓
Elaborazione successiva
↓
Sottosistema del kernel
Il gestore dell’interrupt non deve necessariamente completare tutto il lavoro. Deve soprattutto riconoscere l’evento e mettere il sistema nella condizione di elaborarlo correttamente.
Driver e firmware non sono la stessa cosa
Driver e firmware sono due termini spesso confusi.
Il driver è software eseguito dalla CPU come parte del sistema operativo. Il suo compito è permettere al kernel di comunicare con il dispositivo.
Il firmware è invece software progettato per essere eseguito dal dispositivo stesso oppure da un controller presente al suo interno.
Possiamo ricordare la differenza con una frase semplice:
Il driver parla con il dispositivo. Il firmware lavora dentro il dispositivo.
Una scheda di rete, una GPU, una webcam o un SSD possono contenere un proprio firmware.
In alcuni casi il firmware è memorizzato permanentemente nel dispositivo. In altri casi deve essere caricato dal sistema operativo durante l’inizializzazione.
Firmware caricato durante l’avvio
Su Linux i file firmware vengono spesso conservati nella directory:
/lib/firmware
Quando il kernel rileva un dispositivo, il driver può cercare il file firmware necessario. Se lo trova, lo trasferisce all’hardware e completa l’inizializzazione.
Se il driver è presente ma il firmware manca, il dispositivo può essere rilevato senza diventare pienamente operativo.
Questa situazione si verifica talvolta con schede Wi-Fi, dispositivi Bluetooth, GPU, schede audio e altre periferiche.
Il sistema sa che il dispositivo esiste e può aver caricato il driver corretto, ma non possiede il software interno necessario per attivarlo.
In questi casi i messaggi del kernel possono contenere riferimenti al firmware non trovato.
Per cercarli si può utilizzare:
dmesg
oppure:
journalctl -k
Un messaggio che indica l’impossibilità di caricare un file firmware è quindi diverso da un messaggio che segnala l’assenza del driver.
Che cos’è il DMA
DMA significa Direct Memory Access, cioè accesso diretto alla memoria.
È un meccanismo che consente a un dispositivo di trasferire dati verso la RAM senza richiedere alla CPU di copiare personalmente ogni singolo byte.
Immaginiamo una scheda di rete che riceve una grande quantità di pacchetti.
Senza DMA, la CPU dovrebbe leggere continuamente i dati dal dispositivo e copiarli in memoria. Questa operazione consumerebbe molto tempo di elaborazione.
Con il DMA, il kernel e il driver preparano un’area di memoria e configurano il trasferimento. Il dispositivo può quindi spostare i dati direttamente verso quell’area autorizzata.
Quando l’operazione termina, l’hardware può notificare la CPU attraverso un interrupt.
Il DMA è fondamentale per le prestazioni di dischi, SSD, schede di rete, GPU e schede audio.
Permette alla CPU di continuare a svolgere altri compiti mentre il dispositivo trasferisce i dati.
DMA non significa accesso illimitato alla memoria
Il termine “accesso diretto” potrebbe far pensare che un dispositivo possa leggere e scrivere liberamente in qualunque area della RAM.
Non dovrebbe essere così.
Il kernel e il driver definiscono le aree autorizzate, preparano il trasferimento e controllano il risultato.
Nei sistemi moderni può essere presente anche una IOMMU, cioè un componente che aiuta a limitare e tradurre gli accessi alla memoria effettuati dai dispositivi.
Queste protezioni sono importanti perché un dispositivo difettoso, un driver errato o un componente compromesso non dovrebbe poter modificare liberamente la memoria del kernel o quella appartenente ad altri processi.
Il DMA migliora le prestazioni, ma richiede un controllo rigoroso da parte del sistema operativo.
Come Linux gestisce il consumo energetico
Il kernel non deve soltanto rendere operativo l’hardware. Deve anche controllarne i consumi.
Il power management è particolarmente importante sui portatili, sugli smartphone, nei sistemi embedded e nei data center.
CPU e GPU possono modificare frequenze e livelli di potenza. Una scheda di rete può utilizzare modalità di risparmio energetico. Un dispositivo inutilizzato può essere sospeso. Un disco può entrare in uno stato a consumo ridotto.
Il kernel, i driver e il firmware devono collaborare per decidere quando ridurre i consumi e quando ripristinare le prestazioni.
Se questa collaborazione non funziona correttamente, il computer può consumare più energia, scaldarsi, ridurre l’autonomia della batteria oppure mostrare problemi durante la sospensione.
Che cosa succede durante la sospensione
Quando sospendi un computer, non vengono semplicemente fermate le applicazioni.
Anche i dispositivi devono essere preparati.
Il driver deve interrompere le operazioni in corso, salvare lo stato necessario e portare l’hardware in una modalità sicura. Al risveglio deve ripristinare la configurazione e rendere nuovamente disponibile il dispositivo.
Questo spiega perché alcuni problemi compaiono soltanto dopo la sospensione.
La rete Wi-Fi potrebbe non riconnettersi. L’audio potrebbe scomparire. Un monitor esterno potrebbe non essere rilevato. Una periferica USB potrebbe non rispondere.
In questi casi il dispositivo funzionava prima della sospensione, ma qualcosa nella sequenza di spegnimento e riattivazione non è stato gestito correttamente.
Il problema può trovarsi nel driver, nel firmware o nell’interazione tra i due.
Perché Linux rappresenta i dispositivi dentro /dev
Nel filesystem Linux esiste una directory chiamata:
/dev
Il nome deriva da devices.
Al suo interno troviamo file speciali che rappresentano punti di accesso a molti dispositivi o a servizi forniti dal kernel.
Alcuni esempi sono:
/dev/sda
/dev/nvme0n1
/dev/tty
/dev/null
/dev/random
Questi elementi non sono file normali che contengono una copia del dispositivo.
Sono interfacce.
Quando un programma apre un file dispositivo, il kernel collega l’operazione al driver corrispondente.
Il percorso /dev/sda, per esempio, non è un disco memorizzato dentro una cartella. È il modo con cui Linux espone un determinato dispositivo di archiviazione attraverso il filesystem.
Questo è uno dei significati della frase secondo cui in Linux “tutto è un file”.
Non significa che ogni risorsa sia un documento di testo. Significa che molte risorse vengono utilizzate attraverso operazioni e interfacce simili a quelle dei file.
Hardware reale, driver e file dispositivo
Conviene distinguere chiaramente tre elementi.
L’hardware reale è il componente fisico, come un disco, una tastiera o un controller.
Il driver è il codice che permette al kernel di controllare quel componente.
Il file dispositivo è il punto di accesso esposto nel filesystem.
Nel caso di un disco possiamo immaginare questo percorso:
Disco fisico
↓
Driver del controller
↓
File dispositivo in /dev
↓
Partizione
↓
Filesystem
↓
File e directory
Aprire /dev/sda non significa vedere automaticamente i documenti presenti sul disco.
Per navigare file e directory è necessario che sul dispositivo esista un filesystem e che quel filesystem venga montato all’interno dell’albero delle directory Linux.
Dispositivi a blocchi e dispositivi a caratteri
I file dispositivo possono appartenere a categorie differenti.
Due delle categorie fondamentali sono i dispositivi a blocchi e i dispositivi a caratteri.
Un dispositivo a blocchi gestisce dati organizzati in blocchi e permette normalmente di accedere a posizioni differenti. Dischi, SSD e unità NVMe sono esempi tipici.
Un dispositivo a caratteri gestisce invece un flusso di dati, spesso in modo sequenziale. Terminali e porte seriali rientrano in questa categoria.
Utilizzando:
ls -l /dev
possiamo osservare il tipo del file dispositivo attraverso il primo carattere della riga.
La lettera b indica un block device, mentre la lettera c indica un character device.
Questa distinzione mostra ancora una volta che non tutti gli oggetti presenti nel filesystem sono file normali.
Chi crea i file dentro /dev
In un sistema Linux moderno, i file presenti in /dev vengono creati dinamicamente.
Quando il kernel rileva un dispositivo, espone informazioni attraverso un filesystem virtuale chiamato sysfs e genera un evento.
Nello user space, un componente come udev riceve questo evento e crea il nodo appropriato dentro /dev.
udev può stabilire il nome del dispositivo, il proprietario, il gruppo e i permessi di accesso. Può inoltre creare collegamenti simbolici più stabili e leggibili.
Per i dischi, per esempio, possiamo trovare collegamenti all’interno di directory come:
/dev/disk/by-uuid/
/dev/disk/by-id/
Un nome come /dev/sda può dipendere dall’ordine con cui i dispositivi vengono rilevati. Un UUID o un identificativo hardware può invece rimanere più stabile.
Il funzionamento di /dev collega quindi diversi concetti già affrontati nella serie: kernel, user space, servizi, filesystem, dispositivi e permessi.
Non tutti i dispositivi appaiono nello stesso modo
Dire che Linux rappresenta i dispositivi come file non significa che ogni periferica venga gestita esclusivamente aprendo manualmente un file dentro /dev.
I dischi vengono esposti come dispositivi a blocchi, per esempio:
/dev/sda
/dev/nvme0n1
Le GPU possono utilizzare file presenti dentro:
/dev/dri/
I terminali possono essere rappresentati da elementi come:
/dev/tty
/dev/pts/
I dispositivi di input possono comparire dentro:
/dev/input/
Le schede di rete vengono invece gestite soprattutto attraverso interfacce logiche come:
enp3s0
wlp2s0
lo
Ogni categoria di hardware viene inserita nel sottosistema più adatto.
Il principio comune è che il kernel mantiene il controllo e offre interfacce organizzate. La forma concreta dell’interfaccia cambia in base al tipo di dispositivo.
Come Linux riconosce un dispositivo
Quando colleghi una periferica, Linux deve capire quale driver può utilizzarla.
Il dispositivo comunica attraverso un bus hardware, per esempio PCI Express, USB, SATA, I²C, SPI o Thunderbolt.
Attraverso il bus vengono forniti identificativi relativi al produttore, al modello e alla classe del dispositivo.
Il kernel confronta queste informazioni con i driver disponibili.
Se trova una corrispondenza, associa il driver al dispositivo e avvia l’inizializzazione. Il driver configura l’hardware, verifica le sue capacità e richiede eventualmente il caricamento di un firmware.
Al termine della procedura, il dispositivo può comparire come disco, interfaccia di rete, periferica di input, scheda audio oppure GPU.
Se non esiste un driver compatibile, il dispositivo può essere rilevato dal bus senza diventare utilizzabile.
Linux può quindi sapere che un componente è collegato, ma non conoscere il modo corretto per controllarlo.
Perché alcuni dispositivi funzionano immediatamente
Quando una periferica funziona senza richiedere interventi manuali, si potrebbe pensare che non utilizzi alcun driver.
In realtà il driver è già presente.
Il kernel Linux include il supporto per una quantità enorme di hardware. Le distribuzioni aggiungono inoltre numerosi moduli e pacchetti firmware.
Quando il sistema rileva un dispositivo conosciuto, carica automaticamente il modulo appropriato e completa l’inizializzazione.
Questo accade frequentemente con tastiere, mouse, dischi, controller USB, dispositivi NVMe, molte schede Ethernet e numerose periferiche basate su standard comuni.
Il cosiddetto plug and play non indica l’assenza del driver.
Indica che il sistema possiede già tutto ciò che serve per riconoscere e utilizzare il dispositivo.
Perché altri dispositivi richiedono driver specifici
Non tutto l’hardware funziona immediatamente su Linux.
Un primo motivo è la recente introduzione del dispositivo. Il supporto potrebbe essere stato aggiunto soltanto in una versione più nuova del kernel.
Un secondo problema riguarda la disponibilità delle specifiche tecniche. Se il produttore non pubblica informazioni sufficienti, gli sviluppatori Linux possono avere difficoltà a creare un driver completo.
In alcuni casi il produttore distribuisce soltanto un driver proprietario. Questo può accadere con determinate GPU, schede Wi-Fi o periferiche specializzate.
Può inoltre mancare il firmware richiesto. Il driver è disponibile, ma la distribuzione non include il file necessario a causa della sua licenza oppure perché il pacchetto non è stato installato.
Esistono infine situazioni di supporto parziale.
Una scheda Wi-Fi può funzionare, mentre la parte Bluetooth rimane inattiva. Una GPU può mostrare il desktop senza offrire tutte le funzionalità di accelerazione. Una stampante può stampare ma non utilizzare lo scanner integrato.
Dire che un dispositivo “funziona” o “non funziona” può quindi nascondere diversi livelli di compatibilità.
Driver open source e driver proprietari
Linux può utilizzare driver open source e driver proprietari.
Un driver open source mette a disposizione il proprio codice. Può essere analizzato, corretto, adattato e integrato nel processo di sviluppo del kernel.
Questo facilita la manutenzione nel tempo, perché le modifiche alle interfacce interne del kernel possono essere accompagnate dagli aggiornamenti necessari al driver.
Un driver proprietario viene invece distribuito come componente chiuso dal produttore.
In alcuni casi può offrire prestazioni migliori, supportare più funzioni o essere l’unico modo per utilizzare completamente il dispositivo.
Al tempo stesso, la compatibilità con i nuovi kernel dipende maggiormente dal lavoro del produttore.
Non esiste una regola assoluta secondo cui un driver open source sia sempre migliore di uno proprietario, o viceversa.
La qualità dipende dal dispositivo, dal progetto, dalla documentazione disponibile e dall’impegno dedicato alla manutenzione.
Dal punto di vista dell’integrazione, però, un driver incluso direttamente nel kernel tende a seguire più facilmente l’evoluzione del sistema Linux.
Come capire se il problema dipende dal driver
Quando un dispositivo non funziona, è utile evitare conclusioni immediate.
Il problema non dipende necessariamente dal driver. Potrebbe trovarsi nell’hardware, nel firmware, nei permessi, in un servizio o nella configurazione dell’applicazione.
Il primo passo consiste nel verificare se il sistema rileva fisicamente il dispositivo.
Per l’hardware collegato attraverso PCI si può utilizzare:
lspci
Per i dispositivi USB si può utilizzare:
lsusb
Se il dispositivo appare nell’elenco, significa che il bus ne ha rilevato la presenza.
Per alcuni dispositivi PCI, il comando:
lspci -k
mostra il driver associato e gli eventuali moduli disponibili.
L’elenco dei moduli caricati può essere consultato con:
lsmod
I messaggi del kernel possono invece indicare errori durante l’inizializzazione:
dmesg
oppure:
journalctl -k
Se compare un errore relativo a un firmware mancante, il problema è diverso dall’assenza completa del driver.
Se il dispositivo viene inizializzato correttamente, ma un utente non riesce a utilizzarlo, è opportuno controllare anche proprietario, gruppo e permessi del file dispositivo.
Il metodo corretto consiste nel seguire il percorso a livelli e individuare il punto preciso nel quale la comunicazione si interrompe.
Gli errori più comuni sui driver Linux
Un primo errore consiste nel pensare che un oggetto presente in /dev contenga direttamente i file salvati sul dispositivo.
Un file dispositivo è un punto di accesso all’hardware. Per visualizzare documenti e directory serve un filesystem montato.
Un altro errore è pensare che, in assenza del driver, Linux non possa rilevare in alcun modo la periferica.
Il bus USB o PCI può segnalare la presenza del dispositivo anche se il kernel non possiede un driver in grado di utilizzarlo.
Driver e firmware non devono inoltre essere considerati sinonimi. Il driver lavora nel sistema operativo, mentre il firmware viene normalmente eseguito dal dispositivo.
Anche il concetto di plug and play viene talvolta frainteso. Un dispositivo che funziona immediatamente utilizza comunque un driver. Semplicemente, il supporto è già incluso nel kernel e viene caricato automaticamente.
Infine, un programma grafico di configurazione non coincide necessariamente con il driver. Può essere soltanto un’applicazione nello user space che modifica impostazioni attraverso un driver già attivo.
Il percorso completo tra applicazione e hardware
A questo punto possiamo ricostruire l’intero percorso.
L’utente compie un’azione all’interno di un’applicazione. Il programma utilizza una libreria e, quando necessita di un servizio controllato, effettua una system call.
Il kernel riceve la richiesta e la inoltra al sottosistema appropriato.
Il sottosistema utilizza il driver, che traduce l’operazione nel linguaggio del dispositivo.
L’hardware esegue il lavoro. Può trasferire dati attraverso il DMA e notificare la conclusione dell’operazione attraverso un interrupt.
Il kernel elabora il risultato e lo restituisce all’applicazione.
Utente
↓
Applicazione
↓
Librerie
↓
System call
↓
Sottosistema del kernel
↓
Driver
↓
Hardware
↓
DMA e interrupt
↓
Kernel
↓
Applicazione
Questo percorso completa la mappa dell’architettura Linux iniziata nel primo episodio della serie.
Siamo partiti dall’utente e dai programmi e siamo arrivati fino ai componenti fisici della macchina.
Perché questa architettura è importante
La separazione tra applicazioni, kernel, driver e hardware rende Linux più sicuro, modulare e portabile.
Le applicazioni non devono conoscere il funzionamento specifico di ogni dispositivo. Possono utilizzare interfacce comuni offerte dal sistema operativo.
Il kernel mantiene il controllo sulle operazioni privilegiate e impedisce ai programmi normali di accedere liberamente all’hardware.
I driver concentrano la conoscenza specifica dei dispositivi e possono essere caricati in base alle necessità.
Lo stesso programma può funzionare su macchine molto diverse, perché non comunica direttamente con la scheda di rete, il disco o la GPU.
Naturalmente questa architettura non elimina ogni problema.
Un driver difettoso può destabilizzare il sistema. Un firmware può mancare. Un dispositivo molto recente può non essere ancora supportato. Un produttore può distribuire soltanto componenti proprietari.
Nonostante questi limiti, senza la separazione tra i livelli sarebbe quasi impossibile gestire la grande varietà di hardware esistente.
Riepilogo
Il driver è il componente che collega il kernel a uno specifico dispositivo hardware.
Il firmware è software eseguito normalmente dal dispositivo o da un controller presente al suo interno.
Gli interrupt permettono all’hardware di notificare alla CPU l’arrivo di un evento. Il DMA consente di trasferire dati tra dispositivo e memoria riducendo il lavoro diretto della CPU.
Il power management coordina consumi, sospensione e riattivazione.
La directory /dev contiene file speciali che rappresentano punti di accesso ai dispositivi, ma questi oggetti non coincidono con l’hardware reale e non contengono direttamente i file salvati.
Alcuni componenti funzionano immediatamente perché il driver e il firmware necessari sono già disponibili. Altri richiedono un kernel più recente, un pacchetto firmware oppure un driver specifico.
Capire questi livelli aiuta a interpretare in modo più ordinato i problemi con dischi, Wi-Fi, Bluetooth, GPU, audio e dispositivi USB.
Invece di dire semplicemente che Linux “non vede” una periferica, possiamo chiederci se il dispositivo è stato rilevato, se esiste un driver compatibile, se il firmware è disponibile e se l’utente possiede i permessi necessari.