2013-04-22 13 views
5

Sto scrivendo un kernel e ho bisogno (e voglio) di mettere più stack e heap nella memoria virtuale, ma non riesco a capire come metterli in modo efficiente. Come fanno i programmi normali?Dove sono collocati più stack e heap nella memoria virtuale?

In che modo (o dove) sono posizionati stack e heap nella memoria virtuale limitata fornita da un sistema a 32 bit, in modo tale che abbiano lo stesso spazio di crescita il più possibile?

Ad esempio, quando un programma banale è caricato in memoria, il layout del suo spazio di indirizzi potrebbe essere simile:

[ Code Data BSS Heap-> ... <-Stack ] 

In questo caso il cumulo può crescere grande come memoria virtuale consente (ad esempio, fino allo stack), e credo che questo sia il modo in cui l'heap funziona per la maggior parte dei programmi. Non esiste un limite superiore predefinito.

Molti programmi hanno librerie condivise che si trovano da qualche parte nello spazio degli indirizzi virtuali. Quindi ci sono programmi multi-thread che hanno stack multipli, uno per ogni thread. E i programmi .NET hanno multiple heaps, ognuno dei quali deve essere in grado di crescere in un modo o nell'altro.

Semplicemente non vedo come ciò sia fatto ragionevolmente efficiente senza mettere un limite predefinito alle dimensioni di tutti gli heap e gli stack.

risposta

0

In poche parole, poiché le risorse di sistema sono sempre limitate, non è possibile andare senza limiti.

La gestione della memoria consiste sempre di più livelli ciascuno con responsabilità ben definite. Dal punto di vista del programma, è visibile il gestore a livello di applicazione che di solito riguarda solo il singolo heap assegnato. Un livello superiore potrebbe occuparsi della creazione di più heap, se necessario, da un heap globale e assegnandoli a sottoprogrammi (ciascuno con il proprio gestore di memoria). Sopra questo potrebbe essere lo standard malloc()/free() che utilizza e soprattutto il sistema operativo che gestisce le pagine e l'effettiva allocazione della memoria per processo (in sostanza non riguarda solo più heap, ma anche heap di livello utente in generale).

La gestione della memoria è costosa e quindi è in trapping nel kernel. La combinazione dei due potrebbe imporre un grave calo delle prestazioni, quindi quella che sembra essere la vera gestione dell'heap dal punto di vista dell'applicazione è effettivamente implementata nello spazio utente (la libreria di runtime C) a fini di prestazioni (e altre ragioni fuori portata per ora).

Quando si carica una libreria condivisa (DLL), se è caricata all'avvio del programma, verrà probabilmente caricata molto probabilmente su CODICE/DATI/ecc, quindi non si verifica alcuna frammentazione dell'heap. D'altra parte, se è caricato in fase di runtime, non ci sono praticamente altre possibilità che utilizzare lo spazio heap. Le biblioteche statiche sono, ovviamente, semplicemente collegate nelle sezioni CODICE/DATI/BSS/ecc.

Alla fine della giornata, è necessario imporre limiti a cumuli e cataste in modo che non possano sovraccaricare, ma è possibile assegnarne altri. Se uno ha bisogno di crescere oltre tale limite, è possibile

  • Terminare l'applicazione con l'errore
  • Avere il gestore della memoria allocare/ridimensionare/spostare il blocco di memoria per quella pila/heap e molto probabilmente deframmentare il mucchio (il suo livello) in seguito; questo è il motivo per cui lo free() di solito funziona male.

Considerando un piuttosto ampio, stack frame 1KB su ogni call come media (potrebbe accadere se lo sviluppatore dell'applicazione è inesperto) una pila 10 MB sarebbe sufficiente per 10240 nidificate call -s. A parte ciò, non c'è praticamente più bisogno di più di uno stack e un heap per thread.

+0

Tuttavia, il passaggio da un thread all'altro non deve modificare l'intero spazio indirizzo (e causare il flush del TLB), quindi per ogni thread utilizzato da un processo, lo stack _must_ deve essere presente nello spazio di indirizzamento del processo. E il link nel mio post mostra un'immagine di come un processo CLR ha molti heap. Quindi c'è bisogno di più di uno stack e heap in uno spazio di indirizzi. – Virtlink

+0

Lo spazio degli indirizzi è un livello molto diverso di astrazione. In realtà, in questo caso hai gli stack multipli e gli heap nello stesso spazio degli indirizzi. Lo stesso spazio degli indirizzi è gestito dal sistema operativo mentre l'heap non lo è; è gestito dal codice della libreria a livello utente. – Powerslave

2

Suppongo che tu abbia le nozioni di base nel tuo kernel, un gestore trap per errori di pagina che può mappare una pagina di memoria virtuale su RAM. Di livello successivo, è necessario un gestore di spazio degli indirizzi di memoria virtuale da cui il codice usermode può richiedere lo spazio degli indirizzi. Scegli una granularità del segmento che impedisce un'eccessiva frammentazione, 64 KB (16 pagine) è un buon numero. Consenti il ​​codice usermode sia per riservare spazio che per impegnare lo spazio. Una semplice bitmap di 4 GB/64 KB = 64 KB x 2 bit per tenere traccia dello stato del segmento consente di completare il lavoro. Anche il gestore di trap degli errori di pagina deve consultare questa bitmap per sapere se la richiesta di pagina è valida o meno.

Uno stack è un'allocazione VM di dimensioni fisse, in genere 1 megabyte. Di solito un thread richiede solo una manciata di pagine, a seconda del livello di nidificazione delle funzioni, quindi riserva 1 MB e impegna solo le prime pagine. Quando il thread si annida più in profondità, si verificherà un errore di pagina e il kernel può semplicemente mappare la pagina aggiuntiva su RAM per consentire al thread di continuare. Ti consigliamo di contrassegnare le poche pagine in basso come speciali, quando la pagina del thread non funziona, dichiarare il nome di questo sito web.

Il compito più importante del gestore di heap è impedire la frammentazione. Il modo migliore per farlo è creare un elenco lookaside che le partizioni ammucchino le richieste in base alla dimensione. Tutto meno di 8 byte proviene dal primo elenco di segmenti. Da 8 a 16 dal secondo, da 16 a 32 dal terzo, eccetera. Aumentando il secchio delle dimensioni man mano che si sale. Dovrai giocare con le dimensioni della benna per ottenere il miglior equilibrio. Le allocazioni molto grandi provengono direttamente dal gestore degli indirizzi VM.

La prima volta che si accede a una voce nell'elenco di lookaside, si assegna un nuovo segmento di macchina virtuale. Suddividi il segmento in blocchi più piccoli con un elenco collegato. Quando viene rilasciata tale allocazione, si aggiunge il blocco all'elenco di blocchi liberi. Tutti i blocchi hanno le stesse dimensioni indipendentemente dalla richiesta del programma, quindi non ci sarà alcuna frammentazione. Quando il segmento è completamente utilizzato e non sono disponibili blocchi liberi, si assegna un nuovo segmento. Quando un segmento non contiene nient'altro che blocchi liberi, è possibile restituirlo al gestore VM.

Questo schema consente di creare un numero qualsiasi di pile e cumuli.

+0

Descrivete bene come funziona un heap, ma non vedo come questo 'schema' mi permetta di creare un numero qualsiasi di heap. Se riservo 16 MB per il primo heap subito dopo il codice e i dati dell'utente, allora dove inserisco un secondo heap? Subito dopo il primo? Quindi il primo heap non può crescere oltre i suoi iniziali 16 MB. Oppure frammento che accumula sull'espansione (dividi l'heap), ma ciò è negativo per la localizzazione della cache (ad esempio l'heap ad alta frequenza), la dimensione massima dell'oggetto (ad esempio l'heap di oggetti di grandi dimensioni), la garbage collection o per qualsiasi motivo vengono utilizzati più heap. Per esempio. .NET ha molti heap, come fanno? – Virtlink

+0

Manca la parte in cui le allocazioni dell'heap sono segmenti, non una dimensione fissa. Un mucchio avrà molti segmenti quando crescerà, possono essere dispersi in tutto lo spazio degli indirizzi. La localizzazione della cache è generalmente molto buona perché gli array hanno elementi di dimensione fissi che provengono dalla stessa catena di elenchi di lookaside. –