Linux non utilizza la segmentazione ma solo il paging?


24

L'interfaccia di programmazione Linux mostra il layout di uno spazio di indirizzi virtuale di un processo. Ogni regione nel diagramma è un segmento?

inserisci qui la descrizione dell'immagine

Dalla comprensione del kernel Linux ,

è corretto che ciò che segue significhi che l'unità di segmentazione in MMU mappa i segmenti e gli offset all'interno dei segmenti nell'indirizzo di memoria virtuale e l'unità di paging quindi mappa l'indirizzo di memoria virtuale all'indirizzo di memoria fisica?

La Memory Management Unit (MMU) trasforma un indirizzo logico in un indirizzo lineare mediante un circuito hardware chiamato unità di segmentazione; successivamente, un secondo circuito hardware chiamato unità di paging trasforma l'indirizzo lineare in un indirizzo fisico (vedi Figura 2-1).

inserisci qui la descrizione dell'immagine

Allora perché dice che Linux non usa la segmentazione ma solo il paging?

La segmentazione è stata inclusa nei microprocessori 80x86 per incoraggiare i programmatori a dividere le loro applicazioni in entità logicamente correlate, come subroutine o aree di dati globali e locali. Tuttavia, Linux utilizza la segmentazione in modo molto limitato. In effetti, la segmentazione e il paging sono in qualche modo ridondanti, poiché entrambi possono essere utilizzati per separare gli spazi degli indirizzi fisici dei processi: la segmentazione può assegnare un diverso spazio degli indirizzi lineari a ciascun processo, mentre il paging può mappare lo stesso spazio degli indirizzi lineari in diversi spazi degli indirizzi fisici . Linux preferisce il paging alla segmentazione per i seguenti motivi:

• La gestione della memoria è più semplice quando tutti i processi utilizzano gli stessi valori del registro di segmento, ovvero quando condividono lo stesso insieme di indirizzi lineari.

• Uno degli obiettivi di progettazione di Linux è la portabilità a una vasta gamma di architetture; Le architetture RISC, in particolare, hanno un supporto limitato per la segmentazione.

La versione 2.6 di Linux utilizza la segmentazione solo quando richiesto dall'architettura 80x86.


Puoi specificare le edizioni per favore. Potrebbe anche essere utile specificare i nomi degli autori. So che almeno il primo proviene da una figura di spicco. Tuttavia, entrambi i nomi dei titoli sono un po 'generici, all'inizio non mi era chiaro che stavi parlando di libri :-).
sourcejedi,

2
Ri "La segmentazione è stata inclusa nei microprocessori 80x86 ...": Non è vero. È un'eredità dei processori 808x, che avevano puntatori di dati a 16 bit e segmenti di memoria a 64 Kb. I puntatori a segmento ti hanno permesso di cambiare segmento per indirizzare più memoria. Quell'architettura è stata trasferita su 80x86 (con dimensioni del puntatore aumentate a 33 bit). Al giorno d'oggi nel modello x86_64, hai puntatori a 64 bit che possono (teoricamente - penso che siano effettivamente utilizzati solo 48 bit) indirizzare 16 exabyte, quindi i segmenti non sono necessari.
jamesqf,

2
@jamesqf, beh, la modalità protetta a 32 bit nel 386 supporta segmenti che sono abbastanza diversi dai puntatori ridimensionati a 16 byte che sono nell'8086, quindi non è solo una semplice eredità. Questo non vuol dire nulla sulla loro utilità, ovviamente.
ilkkachu,

@jamesqf 80186 aveva lo stesso modello di memoria dell'8086, nessun "33 bit"
Jasen

Non merita una risposta, quindi solo un commento: segmenti e pagine sono comparabili solo nel contesto dello scambio (ad esempio scambio di pagine vs scambio di segmenti) e in quel contesto lo scambio di pagine fa semplicemente saltare lo scambio di segmenti fuori dall'acqua. Se si scambiano segmenti in / out, è necessario scambiare l' intero segmento, che potrebbe essere 2-4 GB. Questa non è mai stata una cosa reale da usare su x86. Con le pagine puoi sempre lavorare su unità 4KB. Quando si tratta di accedere alla memoria, i segmenti e le pagine sono correlati attraverso le tabelle delle pagine e il confronto sarebbe da mele ad arance.
Vhu,

Risposte:


20

L'architettura x86-64 non utilizza la segmentazione in modalità lunga (modalità 64 bit).

Quattro dei registri di segmento: CS, SS, DS ed ES sono forzati a 0 e il limite a 2 ^ 64.

https://en.wikipedia.org/wiki/X86_memory_segmentation#Later_developments

Non è più possibile per il sistema operativo limitare gli intervalli di "indirizzi lineari" disponibili. Pertanto non può utilizzare la segmentazione per la protezione della memoria; deve fare affidamento interamente sul paging.

Non preoccuparti dei dettagli delle CPU x86 che si applicherebbero solo quando si eseguono le modalità legacy a 32 bit. Linux per le modalità a 32 bit non è utilizzato così tanto. Può anche essere considerato "in uno stato di abbandono benigno per diversi anni". Vedi supporto x86 a 32 bit in Fedora [LWN.net, 2017].

(Succede che Linux a 32 bit non usi nemmeno la segmentazione. Ma non devi fidarti di me su questo, puoi semplicemente ignorarlo :-).


È un po 'un'esagerazione. base / limite sono fissati a 0 / -1 in modalità lunga per i segmenti originali-8086 legacy (CS / DS / ES / SS), ma FS e GS hanno ancora una base di segmenti arbitraria. E il descrittore di segmento caricato in CS determina se la CPU viene eseguita in modalità 32 o 64 bit. E lo spazio utente su x86-64 Linux utilizza FS per l'archiviazione locale thread ( mov eax, [fs:rdi + 16]). Il kernel usa GS (after swapgs) per trovare lo stack del kernel del processo corrente nel syscallpunto di ingresso. Sì, la segmentazione non viene utilizzata come parte del principale meccanismo di gestione della memoria del sistema operativo / protezione della memoria.
Peter Cordes,

Questo è fondamentalmente ciò che la citazione nella domanda intendeva con "La versione 2.6 di Linux usa la segmentazione solo quando richiesto dall'architettura 80x86". Ma il tuo secondo paragrafo è sostanzialmente sbagliato. Linux utilizza la segmentazione sostanzialmente in modo identico nelle modalità a 32 e 64 bit. cioè base = 0 / limite = 2 ^ 32 o 2 ^ 64 per i segmenti classici (CS / DS / ES / SS) che sono usati implicitamente dalle normali istruzioni. Non c'è nulla di "extra" di cui preoccuparsi nel codice Linux a 32 bit; la funzionalità HW è presente ma non utilizzata.
Peter Cordes,

@PeterCordes stai praticamente interpretando la risposta sbagliata :-). Quindi l'ho modificato per cercare di rendere il mio argomento meno ambiguo.
sourcejedi,

Buon miglioramento, ora non è fuorviante. Concordo pienamente con il tuo vero punto, che è che puoi e dovresti assolutamente ignorare la segmentazione x86, perché viene utilizzato solo per le cose di gestione del sistema osdev e per TLS. Se alla fine vuoi saperne di più, è molto più facile da capire dopo aver già capito x86 asm con un modello di memoria piatta.
Peter Cordes,

8

Poiché x86 ha segmenti, non è possibile non usarli. Ma entrambi gli indirizzi di base cs(segmento di codice) e ds(segmento di dati) sono impostati su zero, quindi la segmentazione non è realmente utilizzata. Un'eccezione sono i dati locali di thread, uno dei segmenti normalmente inutilizzati registra punti per thread di dati locali. Ma ciò è principalmente per evitare di riservare uno dei registri di uso generale per questo compito.

Non dice che Linux non usi la segmentazione su x86, poiché ciò non sarebbe possibile. Hai già evidenziato una parte, Linux utilizza la segmentazione in modo molto limitato . La seconda parte è che Linux utilizza la segmentazione solo quando richiesto dall'architettura 80x86

Hai già citato i motivi, il paging è più semplice e portatile.


7

Ogni regione nel diagramma è un segmento?

No.

Mentre il sistema di segmentazione (in modalità protetta a 32 bit su un x86) è progettato per supportare segmenti separati di codice, dati e stack, in pratica tutti i segmenti sono impostati sulla stessa area di memoria. Cioè, iniziano a 0 e terminano alla fine della memoria (*) . Ciò rende uguali gli indirizzi logici e gli indirizzi lineari.

Questo è chiamato modello di memoria "piatto" ed è in qualche modo più semplice del modello in cui sono presenti segmenti distinti e quindi puntatori al loro interno. In particolare, un modello segmentato richiede puntatori più lunghi, poiché il selettore di segmento deve essere incluso oltre al puntatore offset. (Selettore di segmento a 16 bit + offset a 32 bit per un totale di puntatore a 48 bit; rispetto a un puntatore piatto a 32 bit.)

La modalità lunga a 64 bit non supporta nemmeno la segmentazione diversa dal modello di memoria flat.

Se dovessi programmare in modalità protetta a 16 bit sul 286, avresti più bisogno di segmenti, poiché lo spazio degli indirizzi è di 24 bit ma i puntatori sono solo 16 bit.

(* Notare che non ricordo come Linux a 32 bit gestisca la separazione kernel / spazio utente. La segmentazione lo consentirebbe impostando i limiti dei segmenti di spazio utente in modo che non includano lo spazio del kernel. Il paging lo consente poiché fornisce un livello di protezione per pagina.)

Allora perché dice che Linux non usa la segmentazione ma solo il paging?

L'x86 ha ancora i segmenti e non è possibile disabilitarli. Sono solo usati il ​​meno possibile. In modalità protetta a 32 bit, i segmenti devono essere impostati per il modello flat e anche in modalità a 64 bit esistono ancora.


Suppongo che un kernel a 32 bit potrebbe mitigare Meltdown in modo più economico rispetto alla modifica delle tabelle di pagina impostando limiti di segmento su CS / DS / ES / SS che impediscono l'accesso allo spazio utente al di sopra di 2G o 3G. (Melnown vuln è una soluzione alternativa per il bit kernel / utente nelle voci della tabella delle pagine, che consente allo spazio utente di leggere le pagine mappate solo nel kernel). Le pagine VDSO potrebbero essere mappate nella parte superiore del 4G, tuttavia: / wrfsbaseè illegale in modalità protetta / compatibile, solo in modalità lunga, quindi su uno spazio utente del kernel a 32 bit non è possibile impostare FS su valori alti.
Peter Cordes,

Su un kernel a 64 bit, lo spazio utente a 32 bit potrebbe potenzialmente saltare molto lontano a un segmento di codice a 64 bit, quindi non si può dipendere dai limiti di segmento per la protezione di Meltdown, forse solo in un kernel a 32 bit puro. (Il che presenta grossi svantaggi su macchine con molta RAM fisica, ad es. Esaurimento di mem basse per stack di thread.) Comunque, sì Linux protegge la memoria del kernel con il paging, lasciando base / limite = 0 / -1 nello spazio utente per il normale segmenti (non FS / GS utilizzati per l'archiviazione locale thread).
Peter Cordes,

Prima che il bit NX fosse supportato nelle tabelle delle pagine hardware (PAE), alcune prime patch di sicurezza utilizzavano la segmentazione per creare stack non eseguibili per il codice dello spazio utente. es. linux.com/news/exec-shield-new-linux-security-feature (il post di Ingo Molnar menziona "eccellente" stack stack non esecutivo "di Solar Designer").
Peter Cordes,

3

Linux x86 / 32 non usa la segmentazione nel senso che inizializza tutti i segmenti allo stesso indirizzo lineare e limite. L'architettura x86 richiede che il programma abbia segmenti: il codice può essere eseguito solo dal segmento di codice, lo stack può essere posizionato solo nel segmento di stack, i dati possono essere manipolati solo in uno dei segmenti di dati. Linux ignora questo meccanismo impostando tutti i segmenti allo stesso modo (con eccezioni che il tuo libro non menziona comunque), in modo che lo stesso indirizzo logico sia valido in qualsiasi segmento. Ciò equivale in realtà a non avere affatto segmenti.


2

Ogni regione nel diagramma è un segmento?

Questi sono 2 usi quasi totalmente diversi della parola "segmento"

  • segmentazione x86 / registri di segmenti: i moderni sistemi operativi x86 usano un modello di memoria piatta in cui tutti i segmenti hanno la stessa base = 0 e limite = max in modalità 32-bit, lo stesso che l'hardware applica a quello in modalità 64-bit , rendendo la segmentazione un po 'vestigiale . (Ad eccezione di FS o GS, utilizzato per l'archiviazione locale thread anche in modalità 64 bit.)
  • Linker / sezioni / segmenti del caricatore di programmi. ( Qual è la differenza di sezione e segmento nel formato di file ELF )

Gli usi hanno un'origine comune: se si stesse utilizzando un modello di memoria segmentata (soprattutto senza memoria virtuale di paging), si potrebbe avere i dati e gli indirizzi BSS essere relativo alla base DS segmento di stack rispetto alla base SS, e il codice relativo alla Indirizzo di base CS.

Pertanto, è possibile caricare più programmi diversi su indirizzi lineari diversi o addirittura spostarli dopo l'avvio, senza modificare gli offset a 16 o 32 bit rispetto alle basi del segmento.

Ma poi devi sapere a quale segmento è relativo un puntatore, quindi hai "puntatori lontani" e così via. (I programmi x86 a 16 bit reali spesso non avevano bisogno di accedere al loro codice come dati, quindi potevano usare un segmento di codice 64k da qualche parte, e forse un altro blocco 64k con DS = SS, con lo stack che scendeva da offset elevati e i dati a in fondo. O un minuscolo modello di codice con tutte le basi dei segmenti uguali).


In che modo la segmentazione x86 interagisce con il paging

Il mapping degli indirizzi in modalità 32/64 bit è:

  1. segment: offset (base del segmento implicita dal registro che tiene l'offset o sovrascritta con un prefisso di istruzione)
  2. Indirizzo virtuale lineare a 32 o 64 bit = base + offset. (In un modello di memoria piatta come quello utilizzato da Linux, anche puntatori / offset = indirizzi lineari. Tranne quando si accede a TLS rispetto a FS o GS.)
  3. le tabelle delle pagine (memorizzate nella cache da TLB) mappano l'indirizzo lineare a 32 (modalità legacy), 36 (PAE legacy) o 52 bit (x86-64). ( /programming/46509152/why-in-64bit-the-virtual-address-are-4-bits-short-48bit-long-compared-with-the ).

    Questo passaggio è facoltativo: il paging deve essere abilitato durante l'avvio impostando un bit in un registro di controllo. Senza paginazione, gli indirizzi lineari sono indirizzi fisici.

Si noti che la segmentazione non consente di utilizzare più di 32 o 64 bit di spazio di indirizzi virtuali in un singolo processo (o thread) , poiché lo spazio di indirizzi piatto (lineare) in cui tutto è mappato ha solo lo stesso numero di bit degli offset stessi. (Questo non era il caso di x86 a 16 bit, in cui la segmentazione era effettivamente utile per utilizzare più di 64k di memoria con registri e offset per lo più a 16 bit.)


La CPU memorizza nella cache i descrittori di segmenti caricati dal GDT (o LDT), inclusa la base del segmento. Quando si dereferenzia un puntatore, a seconda del registro in cui si trova, il valore predefinito è DS o SS come segmento. Il valore del registro (puntatore) viene trattato come un offset dalla base del segmento.

Poiché la base del segmento è normalmente pari a zero, le CPU lo fanno in casi speciali. O da un altro punto di vista, se si fare avere una base segmento diverso da zero, i carichi hanno la latenza in più perché il caso (normale) "speciale" di bypassare aggiungere l'indirizzo di base non si applica.


Come Linux imposta i registri di segmenti x86:

La base e il limite di CS / DS / ES / SS sono tutti 0 / -1 in modalità 32 e 64 bit. Questo è chiamato modello di memoria piatta perché tutti i puntatori puntano nello stesso spazio degli indirizzi.

(Gli architetti della CPU AMD hanno sterilizzato la segmentazione applicando un modello di memoria piatta per la modalità a 64 bit perché i sistemi operativi tradizionali non lo utilizzavano, ad eccezione della protezione no-exec fornita in modo molto migliore tramite il paging con PAE o x86- 64 formato tabella di pagine.)

  • TLS (Thread Local Storage): FS e GS non sono fissi alla base = 0 in modalità lunga. (Erano nuovi con 386 e non usati implicitamente da alcuna istruzione, nemmeno dalle repistruzioni -string che usano ES). x86-64 Linux imposta l'indirizzo di base FS per ciascun thread sull'indirizzo del blocco TLS.

    ad esempio mov eax, [fs: 16]carica un valore a 32 bit da 16 byte nel blocco TLS per questo thread.

  • il descrittore di segmento CS sceglie la modalità in cui si trova la CPU (modalità protetta 16/32/64 bit / modalità lunga). Linux utilizza una singola voce GDT per tutti i processi dello spazio utente a 64 bit e un'altra voce GDT per tutti i processi dello spazio utente a 32 bit. (Affinché la CPU funzioni correttamente, anche DS / ES deve essere impostato su voci valide, così come SS). Seleziona anche il livello di privilegio (kernel (anello 0) rispetto all'utente (anello 3)), quindi anche quando ritorna allo spazio utente a 64 bit, il kernel deve comunque provvedere alla modifica di CS, usando ireto sysretinvece di un normale istruzione jump o ret.

  • In x86-64, il syscallpunto di ingresso usa swapgsper girare GS dal GS dello spazio utente a quello del kernel, che usa per trovare lo stack del kernel per questo thread. (Un caso specializzato di archiviazione locale thread). L' syscallistruzione non modifica il puntatore dello stack in modo che punti allo stack del kernel; punta ancora allo stack dell'utente quando il kernel raggiunge il punto di ingresso 1 .

  • DS / ES / SS devono anche essere impostati su descrittori di segmento validi affinché la CPU funzioni in modalità protetta / modalità lunga, anche se la base / limite di tali descrittori viene ignorata in modalità lunga.

Quindi, in pratica, la segmentazione x86 viene utilizzata per TLS e per le cose obbligatorie per osdev x86 che l'hardware richiede di fare.


Nota a piè di pagina 1: Storia divertente: ci sono archivi di mailing list di messaggi tra sviluppatori del kernel e architetti AMD risalenti a un paio di anni prima che il silicio AMD64 fosse rilasciato, con conseguenti modifiche alla progettazione e syscallquindi utilizzabili. Vedi i collegamenti in questa risposta per i dettagli.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.