Perché (non) segmentazione?

39

Sto studiando i sistemi operativi e l'architettura x86, e mentre leggevo sulla segmentazione e sul paging ero naturalmente curioso di sapere come i moderni SO gestiscono la gestione della memoria. Da quello che ho trovato Linux e la maggior parte degli altri sistemi operativi essenzialmente evitano la segmentazione a favore del paging. Alcuni dei motivi che ho riscontrato sono la semplicità e la portabilità.

Quali usi pratici ci sono per la segmentazione (x86 o altro) e vedremo mai sistemi operativi solidi che lo utilizzano o continueranno a favorire un sistema basato sul paging.

Ora so che questa è una domanda carica ma sono curioso di sapere come la segmentazione sarà gestita con i sistemi operativi di nuova concezione. Ha molto senso favorire il paging che nessuno considererà un approccio più "segmentato"? Se sì, perché?

E quando dico segmentazione 'shun' sto implicando che Linux lo usi solo per quanto è necessario. Solo 4 segmenti per segmenti di codice / dati utente e kernel. Leggendo la documentazione Intel ho avuto la sensazione che la segmentazione sia stata progettata pensando a soluzioni più solide. Poi di nuovo mi è stato detto in molte occasioni quanto può essere complicato x86.

Ho trovato questo aneddoto interessante dopo essere stato collegato all'annuncio originale di Linux Torvald per Linux. Ha detto questo alcuni post più tardi:

Simply, I'd say that porting is impossible. It's mostly in C, but most people wouldn't call what I write C. It uses every conceivable feature of the 386 I could find, as it was also a project to teach me about the 386. As already mentioned, it uses a MMU, for both paging (not to disk yet) and segmentation. It's the segmentation that makes it REALLY 386 dependent (every task has a 64Mb segment for code & data - max 64 tasks in 4Gb. Anybody who needs more than 64Mb/task - tough cookies).

Suppongo che la mia stessa sperimentazione con x86 mi abbia portato a fare questa domanda. Linus non ha avuto StackOverflow, quindi l'ha appena implementato per provarlo.

    
posta Mr. Shickadance 10.08.2011 - 16:20
fonte

8 risposte

29

Con la segmentazione sarebbe, ad esempio, possibile mettere ogni oggetto allocata dinamicamente (malloc) nel proprio segmento di memoria. L'hardware controllerebbe automaticamente i limiti del segmento e l'intera classe di bug di sicurezza (sovraccarico del buffer) verrebbe eliminata.

Inoltre, poiché tutti gli offset di segmento iniziano da zero, tutto il codice compilato sarebbe automaticamente indipendente dalla posizione. La chiamata in un'altra DLL si riduce a una chiamata remota con offset costante (a seconda della funzione chiamata). Ciò semplificherebbe notevolmente i linker e i caricatori.

Con 4 anelli di protezione, è possibile escogitare un controllo di accesso più dettagliato (con il cercapersone hai solo 2 livelli di protezione: utente e supervisore) e kernel OS più robusti. Ad esempio, solo lo squillo 0 ha accesso completo all'hardware. Separando il nucleo del kernel del sistema operativo e i driver del dispositivo negli anelli 0 e 1, è possibile creare un sistema operativo microkernel più robusto e molto veloce in cui la maggior parte dei controlli di accesso rilevanti verranno eseguiti da HW. (I driver di dispositivo potevano accedere all'hardware tramite bitmap di accesso I / O nel TSS.)

Tuttavia .. x86 è un po 'limitato. Ha solo 4 registri di segmento dati "liberi"; ricaricarli è piuttosto costoso, ed è possibile accedere simultaneamente solo a 8192 segmenti. (Supponendo di voler massimizzare il numero di oggetti accessibili, quindi GDT contiene solo descrittori di sistema e descrittori LDT.)

Ora, con la segmentazione in modalità a 64 bit viene descritta come "legacy" e i controlli dei limiti hardware vengono eseguiti solo in circostanze limitate. IMHO, un grande errore. In realtà non incolpo Intel, principalmente biasimo gli sviluppatori, la maggior parte dei quali pensava che la segmentazione fosse "troppo complicata" e desiderava uno spazio di indirizzamento piatto. Incolpo anche gli scrittori del sistema operativo che non avevano l'immaginazione per utilizzare la segmentazione. (AFAIK, OS / 2 era l'unico sistema operativo che sfrutta appieno le funzionalità di segmentazione.)

    
risposta data 10.08.2011 - 22:42
fonte
22

La risposta breve è che la segmentazione è un hack, utilizzato per creare un processore con una capacità limitata di indirizzare la memoria oltre tali limiti.

Nel caso dell'8086, c'erano 20 linee di indirizzo sul chip, il che significava che poteva accedere fisicamente a 1Mb di memoria. Tuttavia, l'architettura interna era basata su un indirizzamento a 16 bit, probabilmente dovuto al desiderio di mantenere la coerenza con l'8080. Quindi il set di istruzioni includeva i registri di segmento che sarebbero stati combinati con gli indici a 16 bit per consentire l'indirizzamento dell'intero 1Mb di memoria . L'80286 ha esteso questo modello con una vera MMU, per supportare la protezione basata su segmenti e l'indirizzamento di più memoria (iirc, 16Mb).

Nel caso del PDP-11, i modelli successivi del processore fornivano una segmentazione in spazi di istruzioni e dati, ancora una volta per supportare le limitazioni di uno spazio di indirizzi a 16 bit.

Il problema con la segmentazione è semplice: il tuo programma deve esplicitamente aggirare i limiti dell'architettura. Nel caso dell'8086, ciò significava che il più grande blocco di memoria contigua a cui si poteva accedere era 64k. se dovessi accedere a più di questo, dovresti modificare i tuoi registri di segmento. Il che significava, per un programmatore C, che dovevi dire al compilatore C che tipo di puntatori dovresti generare.

È stato molto più semplice programmare l'MC68k, che aveva un'architettura interna a 32 bit e uno spazio di indirizzi fisico a 24 bit.

    
risposta data 10.08.2011 - 16:35
fonte
13

Per 80x86 ci sono 4 opzioni - "niente", solo la segmentazione, solo il paging e sia la segmentazione che il paging.

Per "niente" (nessuna segmentazione o paginazione) si finisce con un modo semplice per proteggere un processo da sé, nessun modo semplice per proteggere i processi gli uni dagli altri, nessun modo per gestire cose come la frammentazione dello spazio degli indirizzi fisico, in nessun modo per evitare codice indipendente dalla posizione, ecc. Nonostante tutti questi problemi potrebbe (in teoria) essere utile in alcune situazioni (es. dispositivo incorporato che esegue solo un'applicazione, o forse qualcosa che usa il JIT e virtualizza tutto comunque).

Solo per segmentazione; risolve quasi il problema di "proteggere un processo da se stesso", ma ci vuole un sacco di soluzioni per renderlo utilizzabile quando un processo vuole utilizzare più di 8192 segmenti (supponendo un LDT per processo), il che lo rende per lo più rotto. Si risolve quasi il problema di "proteggere i processi gli uni dagli altri"; ma diversi pezzi di software in esecuzione allo stesso livello di privilegio possono caricare / utilizzare i rispettivi segmenti (ci sono modi per ovviare a questo: modificare le voci GDT durante i trasferimenti di controllo e / o l'utilizzo di LDT). Risolve anche il problema del "codice indipendente dalla posizione" (può causare un problema di "codice dipendente dal segmento" ma è molto meno significativo). Non fa nulla per il problema di "frammentazione dello spazio di indirizzo fisico".

Solo per il paging; non risolve molto il problema di "proteggere un processo da se stesso" (ma siamo onesti qui, questo è solo un vero problema per il debugging / test del codice scritto in lingue non sicure, e comunque ci sono strumenti molto più potenti come valgrind). Risolve completamente il problema di "proteggere i processi gli uni dagli altri", risolve completamente il problema del "codice indipendente dalla posizione" e risolve completamente il problema della "frammentazione dello spazio di indirizzo fisico". Come bonus aggiuntivo si aprono alcune tecniche molto potenti che non sono neanche lontanamente pratici senza impaginare; incluse cose come "copia su scrittura", file mappati in memoria, efficiente gestione dello spazio di scambio, ecc.

Ora penseresti che usare sia la segmentazione che il paging ti darebbero i benefici di entrambi; e in teoria lo può fare, a parte il fatto che l'unico vantaggio derivante dalla segmentazione (che non è migliorato dal paging) è una soluzione al problema "proteggere un processo da se stesso" a cui nessuno importa davvero. In pratica quello che ottieni è la complessità di entrambi e il sovraccarico di entrambi, con un beneficio molto piccolo.

Questo è il motivo per cui quasi tutti i sistemi operativi progettati per 80x86 non utilizzano la segmentazione per la gestione della memoria (lo usano per cose come memoria per CPU e per attività, ma soprattutto per comodità per evitare di consumare uno scopo generale più utile registrati per queste cose).

Ovviamente i produttori di CPU non sono stupidi - non spenderanno tempo e denaro per ottimizzare qualcosa che sanno che nessuno usa (stanno andando ad ottimizzare qualcosa che quasi tutti usano invece). Per questo motivo i produttori di CPU non ottimizzano la segmentazione, il che rende la segmentazione più lenta di quanto potrebbe essere, il che rende gli sviluppatori di sistemi operativi desiderosi di evitarlo ancora di più. Per lo più hanno mantenuto la segmentazione solo per compatibilità con le versioni precedenti (che è importante).

Alla fine, AMD ha progettato la modalità lunga. Non c'era nessun codice vecchio / esistente a 64 bit, quindi (per codice a 64 bit) AMD si liberò della maggior segmentazione possibile. Ciò ha dato agli sviluppatori del sistema operativo un altro motivo (nessun modo semplice per il codice porta progettato per la segmentazione a 64 bit) per continuare a evitare la segmentazione.

    
risposta data 20.07.2013 - 07:49
fonte
12

Sono piuttosto sbalordito dal fatto che in tutto il tempo da quando è stata pubblicata questa domanda nessuno abbia menzionato le origini delle architetture di memoria segmentate e il vero potere che possono permettersi.

Il sistema originale che ha inventato, o rifinito in forma utile, tutte le funzionalità che circondano la progettazione e l'uso di sistemi di memoria virtuale a segmenti segmentati (insieme a file system multi-elaboratori e gerarchici simmetrici) era Multics (e vedi anche il sito Multicians ). La memoria segmentata consente a Multics di offrire una vista all'utente che tutto si trova nella memoria (virtuale) e consente il livello finale di condivisione di tutto in forma diretta (cioè direttamente indirizzabile in memoria). Il filesystem diventa semplicemente una mappa per tutti i segmenti in memoria. Se usato correttamente in modo sistematico (come in Multics), la memoria segmentata libera l'utente dai numerosi fardelli della gestione dello storage secondario, della condivisione di dati e delle comunicazioni tra processi. Altre risposte hanno fatto affermazioni a mano libera che la memoria segmentata è più difficile da usare, ma questo semplicemente non è vero, e Multics lo ha dimostrato con successo clamoroso decenni fa.

Intel ha creato una versione zoppicante della memoria segmentata 80286 che, sebbene sia abbastanza potente, i suoi limiti hanno impedito che venisse utilizzato per qualcosa di veramente utile. L'80386 ha migliorato questi limiti, ma al momento le forze di mercato hanno praticamente impedito il successo di qualsiasi sistema che potesse davvero trarre vantaggio da questi miglioramenti. Negli anni successivi sembra che troppe persone abbiano imparato a ignorare le lezioni del passato.

Intel ha anche provato presto a costruire un super-micro più capace chiamato iAPX 432 che avrebbe superato di gran lunga qualsiasi cosa altro al momento, e aveva un'architettura di memoria segmentata e altre caratteristiche strongmente orientate alla programmazione orientata agli oggetti. L'implementazione originale è stata comunque troppo lenta e non sono stati effettuati ulteriori tentativi per risolverlo.

Una discussione più dettagliata su come Multics ha utilizzato la segmentazione e il paging può essere trovata nel documento di Paul Green Multics Memoria virtuale - Tutorial e riflessioni

    
risposta data 02.11.2015 - 03:36
fonte
5

La segmentazione è stata un trucco / soluzione alternativa per consentire l'indirizzamento di un massimo di 1 MB di memoria da parte di un processore a 16 bit: normalmente sarebbero stati accessibili solo 64 KB di memoria.

Quando arrivavano processori a 32 bit, era possibile indirizzare fino a 4 GB di memoria con un modello di memoria piatto e non c'era più bisogno di segmentazione - I registri di segmento venivano ri-proposti come selettori per GDT / paging in modalità protetta ( anche se puoi avere la modalità protetta a 16 bit).

Anche una modalità memoria piatta è molto più comoda per i compilatori - puoi scrivere Programmi segmentati a 16 bit in C , ma sono un po 'ingombranti. Un modello di memoria piatta rende tutto più semplice.

    
risposta data 10.08.2011 - 16:46
fonte
3

Alcune architetture (come ARM) non supportano affatto i segmenti di memoria. Se Linux fosse stato dipendente dalla sorgente sui segmenti, non avrebbe potuto essere trasferito su tali architetture molto facilmente.

Guardando l'immagine più ampia, il fallimento dei segmenti di memoria ha a che fare con la continua popolarità di C e l'aritmetica dei puntatori. Lo sviluppo C è più pratico su un'architettura con memoria piatta; e se vuoi memoria piatta, scegli il paging della memoria.

C'è stato un periodo intorno agli anni '80 quando Intel, come organizzazione, stava anticipando la popolarità futura di Ada e di altri linguaggi di programmazione di livello superiore. Questo è fondamentalmente il punto da cui provengono alcuni dei loro errori più spettacolari, come l'orribile segmentazione della memoria APX432 e 286. Con il 386 capitolarono a programmatori di memoria piatta; è stato aggiunto il paging e un TLB e i segmenti sono stati resi ridimensionabili a 4 GB. E poi AMD ha praticamente rimosso i segmenti con x86_64 rendendo il reg di base a dont-care / implicito-0 (ad eccezione di fs? Per TLS, penso?)

Detto questo, i vantaggi dei segmenti di memoria sono ovvi: cambiare gli spazi degli indirizzi senza dover ripopolare un TLB. Forse un giorno qualcuno realizzerà una CPU competitiva dal punto di vista delle prestazioni che supporti la segmentazione, potremo programmare un sistema operativo orientato alla segmentazione e i programmatori possono rendere Ada / Pascal / D / Rust / another-langage-that-does-t-require-flat - programmi di memoria per questo.

    
risposta data 15.02.2016 - 19:14
fonte
1

La segmentazione è un enorme onere per gli sviluppatori di applicazioni. Qui è dove è arrivata la grande spinta per eliminare la segmentazione.

È interessante notare che spesso mi chiedo quanto potrebbe essere meglio l'i86 se Intel eliminasse tutto il supporto legacy per queste vecchie modalità. Qui meglio implicherebbe una potenza inferiore e un'operazione forse più veloce.

Suppongo che si possa sostenere che Intel abbia inacidito il latte con segmenti a 16 bit portando a una sorta di rivolta degli sviluppatori. Ma ammettiamolo, uno spazio di indirizzamento di 64k non è niente in particolare quando si guarda all'app moderna. Alla fine dovettero fare qualcosa perché la concorrenza poteva e lo faceva sul mercato efficacemente contro i problemi di spazio degli indirizzi di i86.

    
risposta data 14.08.2011 - 19:22
fonte
1

La segmentazione porta a traduzioni e scambi di pagine più lenti

Per questi motivi, la segmentazione è stata in gran parte abbandonata su x86-64.

La principale differenza tra loro è che:

  • il paging divide la memoria in chunk di dimensioni fisse
  • La segmentazione
  • consente diverse larghezze per ogni blocco

Anche se potrebbe sembrare più intelligente avere ampiezze di segmento configurabili, mentre aumenti le dimensioni della memoria per un processo, la frammentazione è inevitabile, ad esempio:

|   | process 1 |       | process 2 |                        |
     -----------         -----------
0                                                            max

alla fine diventerà come il processo 1 cresce:

|   | process 1        || process 2 |                        |
     ------------------  -------------
0                                                            max

fino a quando una divisione è inevitabile:

|   | process 1 part 1 || process 2 |   | process 1 part 2 | |
     ------------------  -----------     ------------------
0                                                            max

A questo punto:

  • l'unico modo per tradurre pagine è fare ricerche binarie su tutte le pagine del processo 1, che accetta un log (n) inaccettabile
  • uno swap fuori processo 1 parte 1 potrebbe essere enorme dato che quel segmento potrebbe essere enorme

Con pagine di dimensioni fisse tuttavia:

  • ogni traduzione a 32 bit esegue solo 2 letture della memoria: directory e tabella della tabella camminano
  • ogni scambio è un 4KiB accettabile

Pezzi di memoria di dimensioni fisse sono semplicemente più maneggevoli e hanno dominato la progettazione attuale del sistema operativo.

Vedi anche: link

    
risposta data 11.06.2017 - 17:07
fonte

Leggi altre domande sui tag