Il legacy di x86 & x64 – parte 7 (istruzioni non privilegiate)

Oltre alle istruzioni privilegiate, per la gestione dei selettori ne sono state introdotte poche altre da Intel che non lo sono, ma che possono essere utili in modalità utente per effettuare alcune operazioni che possono rivelarsi più o meno utili.

Fra quelle che lo sono di meno c’è sicuramente ARPL, il cui scopo è quello di “aggiustare”, se possibile, il livello di privilegio di un selettore (che può risiedere in un registro o in memoria) sulla base di un altro (che si trova in un registro) che viene utilizzato per il confronto.

Com’è stato illustrato nell’articolo che descrive la modalità protetta introdotta con l’80286, il processore può lavorare in uno dei quattro livelli di privilegio disponibili: dal 3, che è il più basso (per le applicazioni) fino allo 0, che è il più elevato (per il kernel).

Tutti i selettori, quindi sia di dati che di codice, hanno un livello di privilegio associato, per cui nel momento in cui un programma in esecuzione deve accedere all’area individuata da un selettore (anche qui, di qualunque natura esso sia), l’operazione può andare in porto soltanto se il livello di privilegio del codice correntemente eseguito è compatibile con quello del selettore che sta referenziando.

Considerata la gerarchia dei livelli di privilegio messa in piedi con la modalità protetta di x86, ciò si verifica nel caso in cui il livello del codice in esecuzione sia maggiore o uguale a quello del selettore a cui accede. Ad esempio un driver che gira a livello 1 può accedere a tutti i selettori di livello 1, 2 e 3. Un’applicazione, che gira a livello 3, può accedere soltanto a selettori di pari livello.

ARPL, che in genere viene utilizzata da un s.o. (anche se è eseguibile da qualunque tipo di codice, anche utente), modifica il campo RPL (Requested Priviledge Level) del selettore destinazione soltanto se il suo livello di privilegio risulta inferiore a quello del selettore sorgente (il quale, in genere, è proprio il selettore del codice che effettuerà l’accesso al selettore destinazione).

Il flag Z (Zero) del registro di stato riflette il risultato dell’operazione, essendo impostato a 1 se l’RPL viene aggiornato, oppure a 0 altrimenti.

Si tratta, tutto sommato, di un’operazione molto semplice nel funzionamento come pure nell’implementazione, ma il cui impatto nel codice normalmente eseguito risulta nullo, poiché nel modello “flat“, ormai utilizzato da anni nei s.o. più diffusi, lo spazio d’indirizzamento è unico e non vengono più usati i selettori per segmentare la memoria né tanto meno per differenziarne l’accesso sulla base dei livelli di privilegio (sempre che qualche s.o. l’abbia mai messo in pratica).

Il “contributo” di ARPL al concetto di legacy che x86 si porta dietro rimane, oltre che nella necessaria implementazione (poca cosa, come già detto), quello di dover mappare quest’istruzione nella opcode table, ma, essendo in buona compagnia con le centinaia e centinaia di altre, l’impatto sui decoder non risulta significativo.

Di positivo c’è il fatto che con la nuova incarnazione a 64 bit, x64, AMD ha pensato bene di rimuovere l’opcode associato a questa istruzione nella nuova modalità (long mode), lasciandolo pertanto libero per essere utilizzato eventualmente in futuro.

Il motivo è semplice: lavorando in long mode i selettori hanno sostanzialmente perso di significato, come abbiamo ampiamente avuto modo di vedere, se non per il solo indirizzo base in alcuni casi (tramite l’uso dei prefissi FS e GS), per cui ARPL non aveva più senso.

Se ARPL serviva ad assicurarsi che un segmento fosse accessibile in esecuzione, le altre quattro istruzioni che tratteremo (abbastanza velocemente, perché non ci sarà molto da dire tenendo conto dell’argomento trattato, cioè il legacy), LAR, LSL, VERR e VERW, hanno uno scopo che possiamo definire “complementare”, in quanto servono per ottenere informazioni (le prime due) ed effettuare dei controlli (le altre).

LAR è l’acronimo di Load Access Rights e, come il nome lascia intendere, serve a recuperare alcune informazioni, quelle di “accesso” (tipo di selettore, flag “presente”, il livello di privilegio, ecc.), del descrittore associato al selettore specificato come indirizzo di memoria o come registro.

Esiste in due incarnazioni: con registro destinazione a 16 bit o a 32/64 bit. Nel primo caso vengono recuperate soltanto le informazioni che erano presenti nei descrittori introdotti con l’80286, mentre negli altri due sono aggiunti, nei 16 bit successivi, i rimanenti flag.

Poiché risulta necessario prelevare questi dati dal descrittore associato al selettore, il “costo” dell’implementazione di questa istruzione è simile a quello del caricamento di un selettore in uno dei 6 registri di segmento, come abbiamo visto in precedenza nell’articolo che ha trattato i selettori. Inoltre se non è possibile accedere al descrittore il flag Z viene resettato, altrimenti sarà posto a 1.

Molto simile a LAR è LSL, acronimo di Load Segment Limit, il cui scopo evidentemente risulta quello di ottenere il limite associato al selettore, andandolo a prendere sempre dal descrittore a esso associato.

Come abbiamo visto sempre nell’articolo al link di cui sopra, il limite associato a un selettore è impostabile in due diversi modi: con o senza granularità. Senza granularità si può specificare un limite massimo di 1MB, in maniera estremamente fine, a passi di un singolo byte. Col bit di granularità impostato, invece, il limite arriva a 4GB, ma con “tagli” da 4KB.

L’istruzione, quindi, si comporta in maniera diversa, a seconda delle due possibilità. Senza granularità restituisce i 20 bit del limite “grezzo”, quindi fino a 1MB (meno un byte, per la precisione). Con granularità riporta i 20 bit “shiftati” a sinistra di 12 (cioè moltiplicati per il “passo” di 4KB), e impostando infine tutti a 1 i 12 bit bassi (0 -> 4095; 1 -> 8191, 2 -> 12.287, ecc., fino a 4GB – 1).

La versione a 32/64 bit riporta tutto il valore completo, mentre quella a 16 bit tronca il risultato (che finirà poi in un registro) recuperando soltanto i 16 bit bassi. D’altra parte l’80286 poteva indirizzare soltanto 64KB di memoria per ogni selettore, di conseguenza il limite era un valore a 16 bit (sono stati aggiunti gli altri 4 bit soltanto con l’80386). Come per la LAR, anche la LSL imposta opportunamente il flag Z a seconda se è stato possibile accedere o meno al descrittore associato al selettore.

Infine le istruzioni VERR e VERW, acronimi di Verify Read e Verify Write, servono esclusivamente a verificare, per l’appunto, se è possibile rispettivamente leggere o scrivere nell’area di memoria associata al selettore specificato (è l’unico argomento delle due istruzioni).

Come negli altri due casi, il flag Z riporta il successo o fallimento dell’operazione, che consta sia del controllo del descrittore associato, sia del caso specifico da verificare (se, alla fine, il selettore è leggibile o scrivibile).

Anche se non si tratta propriamente di una nuova istruzione, una particolare menzione va a CALL, che può essere utilizzata, oltre che per le classiche chiamate a subroutine, per passare il controllo a quello che si chiama (call) “gate“, cioè un meccanismo che consente di chiamare codice eseguito a un livello di privilegio diverso rispetto a quello attuale.

Un gate può essere specificato con una CALL di tipo “far“, che consta della coppia segmento/selettore e offset (a 16, 32 o 64 bit). Nello specifico, il selettore di tipo gate informa la CALL che deve procedere in maniera diversa dal solito, conservando non soltanto le informazioni utili al ritorno al codice chiamante, ma anche lo stack (poiché ne viene usato un altro, quello associato al nuovo livello di privilegio).

Inoltre consente di trasferire un certo numero di longword (32 bit) dallo stack del chiamante al nuovo, permettendo quindi il passaggio di alcuni parametri senza che il nuovo codice debba andare a recuperarli accedendo direttamente a un qualche selettore del chiamante.

Senza entrare troppo nei dettagli, un meccanismo di questo tipo, per quanto potente e utile, risulta decisamente complicato da implementare, oltre che enormemente lento, sebbene rispetto a 80286 e 80386 ci siano stati netti miglioramenti con le microarchitetture successive.

Per evitare questo pesante fardello, un po’ di anni fa è stata introdotta un’altra coppia di istruzioni, SYSENTER/SYSEXIT, per richiamare molto più velocemente il kernel. AMD ne ha poi introdotta un’altra, SYSCALL/SYSRET, che, oltre a essere ancora più semplice e veloce, consente anche di richiamare il codice di un kernel a 64 bit.

Ci stiamo ormai approssimando alla chiusura di questa serie di articoli. Nel successivo analizzeremo brevemente cosa si porta dietro la sezione MMX (e 3DNow! di AMD), mentre l’ultimo sarà un riepilogo di chiusura con alcune riflessioni sul peso dei vari costi del legacy che sono stati analizzati.

Press ESC to close