Nonostante il ruolo sempre più “gestionale” di Linus Torvalds nell’ambito del kernel Linux, l’informatico finlandese continua a contribuire con l’aggiunta di codice significativo, come dimostra l’avvio del ciclo di sviluppo di Linux 6.14. Tra i suoi interventi più recenti spicca una patch che introduce l’uso dell’istruzione CMOV sui sistemi x86/x86-64 per il masking degli indirizzi utente. Di cosa si tratta? E come può davvero velocizzare i sistemi Linux?
Pipelining delle istruzioni e predizione dei salti
Per eseguire il codice assembly alla massima velocità, le CPU implementano il pipelining delle istruzioni. Nell’articolo sul funzionamento di un processore, abbiamo visto che quando la CPU inizia l’esecuzione dell’istruzione A, non aspetta che essa termini e “si avvantaggia” con l’esecuzione dell’istruzione B, e così via. Più istruzioni sono eseguite simultaneamente per guardare alle massime prestazioni. Tutto funziona, però, se e solo se le istruzioni A, B, C, D e così via sono indipendenti tra loro.
Se B dipende dal risultato di A, allora non può essere eseguita fino a quando non si conosce il risultato di A. Questo implica che la CPU deve attendere che l’istruzione A termini completamente le sue operazioni prima di poter eseguire B. L’esecuzione parallela subisce quindi un ritardo perché il codice necessita di più informazioni prima di proseguire con le successive elaborazioni: ne abbiamo parlato anche nell’articolo sul perché abbiamo bisogno delle CPU se le GPU sono più veloci.
Quando incontriamo un’istruzione condizionale, come un if o un loop, ci si potrebbe aspettare che l’esecuzione parallela si fermi. Infatti, per sapere quale codice eseguire successivamente, la CPU deve conoscere il risultato della condizione. In teoria, non sappiamo quale codice eseguire fino a quando non conosciamo il risultato, ma la CPU tenta comunque di fare una previsione. La predizione dei salti (branch prediction) consente alla CPU di indovinare quale parte del codice sarà eseguita successivamente, e spesso ci azzecca, il che permette di continuare l’esecuzione a velocità piena, senza aspettare di sapere se la previsione è corretta o meno. Se la CPU avesse fatto una previsione errata, si scarta il lavoro fatto fino a quel punto e si ricomincia, con un’operazione che risulta relativamente lenta. In generale, tuttavia, i benefici della predizione superano i costi.
CMOV: un’alternativa al branching
La CMOV (conditional move) è un tipo speciale di if-else
che non implica l’esecuzione di un blocco di codice diverso, ma semplicemente l’assegnazione di una variabile a seconda di una condizione. La differenza principale è che non si crea un salto vero e proprio, ma una singola istruzione che effettua un’assegnazione condizionale. Questo riduce la necessità di gestire le dipendenze dei dati (l’istruzione B che non può essere elaborata perché manca il risultato dell’istruzione A), evitando l’incertezza legata alla branch prediction.
Dal punto di vista delle performance, CMOV può essere preferibile rispetto all’approccio più tradizionale in quanto elimina i problemi legati a predizioni errate, che possono risultare in un rallentamento significativo del sistema.
Se la CPU non è in grado di “indovinare” quale codice eseguire, può fallire nel mascherare i risultati errati di una predizione, come nel caso della celeberrima vulnerabilità Spectre, che sfruttava le debolezze nel processo di predizione dei salti per abilitare l’accesso a dati riservati.
Implicazioni per la sicurezza: CMOV contro Spectre e i suoi “fratelli”
Nel contesto di vulnerabilità come Spectre, le predizioni errate dei salti possono esporre i dati elaborati dalla CPU a rischi di attacco. In queste situazioni, infatti, i risultati di un’istruzione mal gestita non sono correttamente “nascosti”, causando potenziali falle di sicurezza.
Utilizzando CMOV, si evita il problema della branch prediction in quanto la CPU non ha bisogno di fare ipotesi sul risultato dell’istruzione condizionale, riducendo il rischio di esecuzione speculativa non sicura.
L’approccio seguito da Linus Torvalds, che ha deciso di integrare CMOV nel kernel Linux, si basa proprie su queste logiche: con l’intento da un lato di migliorare le performance e dall’altro di scongiurare vulnerabilità derivanti da predizioni errate. Il nuovo schema semplifica il codice, riducendo la necessità di ulteriori operazioni e migliorando la sicurezza senza compromettere le prestazioni.
Torvalds ha spiegato che la sua patch, partorita a valle di un suggerimento proveniente da David Laight, risulta una scelta azzeccata. Nonostante iniziali preoccupazioni per un potenziale comportamento imprevisto su alcune microarchitetture, CMOV risulta essere una scelta sicura, come documentato nel whitepaper Intel “Speculative Execution Side Channel Mitigations“. Per approfondire, suggeriamo la lettura del nostro articolo sugli attacchi side channel ai processori.
Benefici e prospettive
L’adozione di CMOV non solo riduce il numero di istruzioni necessarie per gestire le varie operazioni, ma elimina anche la necessità di un registro temporaneo, semplificando ulteriormente il codice.
Il ciclo di sviluppo del kernel Linux 6.14 si preannuncia così foriero di migliorie, con contributi che spaziano dall’ottimizzazione delle performance a interventi più ampi, come la risoluzione di problematiche su ARM64.