Vedo perché sei confuso. Il diagramma è un po 'confuso e potrebbe non essere corretto.
Prima di tutto, pensiamo al motivo per cui un kernel ha bisogno di un allocatore di memoria al di sotto del livello delle pagine. Probabilmente sono già cose che conosci per lo più, ma le esaminerò per completezza.
Le pagine sono la tipica "unità" delle operazioni di memoria. Quando un'applicazione per lo spazio utente alloca memoria o mappa la memoria di un file o qualcosa del genere, in genere ottiene un multiplo delle dimensioni della pagina della macchina. Vi sono notevoli eccezioni; Windows utilizza 64k come unità di allocazione della memoria virtuale, indipendentemente dalle dimensioni della pagina della CPU. Tuttavia, pensiamolo in questo modo.
Su una CPU moderna, per quanto riguarda il codice dello spazio utente, ha uno spazio di indirizzamento piatto. Questa è in realtà un'illusione fornita dal sistema di memoria virtuale. Il sistema operativo fornisce pagine da qualsiasi punto della RAM (o forse non nella RAM, nel caso di memoria scambiata o file mappati in memoria) e li mappa in uno spazio di indirizzi virtuale contiguo.
Il punto di tutto ciò è che, a parte alcuni casi speciali per il sistema operativo stesso (forse buffer DMA, forse alcune strutture dati speciali impostate all'avvio, oh e l'immagine del kernel stessa), probabilmente il kernel del sistema operativo non deve mai gestire qualsiasi blocco di RAM più grande di una pagina. Questo semplifica enormemente le cose, perché significa che per quanto riguarda le pagine, ogni allocazione e deallocazione ha le stesse dimensioni. Elimina inoltre efficacemente la frammentazione esterna a livello macro.
Tuttavia, i kernel devono anche implementare alcune strutture di dati proprie e per questo hanno bisogno di un diverso tipo di allocatore di memoria. Queste strutture di dati di solito possono essere pensate come una raccolta di singoli oggetti (ad esempio un oggetto può essere un "thread" o un "mutex"). Le dimensioni di questi oggetti sono in genere molto più piccole di quelle di una pagina.
Quindi, ad esempio, un oggetto che rappresenta le credenziali di sicurezza di un processo (si pensi all'id utente e all'ID gruppo in POSIX, diciamo) potrebbe essere di soli 16 byte o giù di lì, mentre un "processo" o "thread" potrebbe essere fino a 1kb di dimensioni. Chiaramente non si desidera utilizzare un'intera pagina per questi piccoli record, quindi l'idea è quella di implementare un allocatore in cima alle pagine.
Il sistema di allocazione di livello inferiore deve soddisfare molti degli stessi problemi dell'allocatore a livello di pagina: deve essere ragionevolmente veloce (anche su sistemi multicore), si desidera ridurre al minimo la frammentazione e così via. Ma soprattutto, dovrebbe essere sintonizzabile e configurabile a seconda del tipo di struttura di dati che stai memorizzando.
Alcune strutture di dati sono intrinsecamente "cache-like". Ad esempio, molti sistemi operativi mantengono una cache di nomi di percorso per gli oggetti del filesystem per evitare lunghe catene di ricerca di directory (chiamate "cache dei nomi" o "cache dei nomi" in Unix-speak). Questi oggetti sono necessari solo per le prestazioni, non per la correttezza, quindi potresti (in teoria) dimenticare una intera pagina piena di voci se la memoria è ridotta e devi liberare rapidamente un frame di pagina.
Altre strutture di dati potrebbero essere scambiate su disco se la memoria è stretta e non ne hai bisogno presto. Ma non vuoi farlo con le strutture di dati che controllano lo scambio o il sistema di memoria virtuale!
Alcune strutture di dati possono essere spostate in memoria senza penalità (ad esempio se nessuno si riferisce a esse con un puntatore), quindi potrebbero "compattarsi" per evitare la frammentazione se necessario.
Quindi l'idea principale dell'allocatore di lastre è che una pagina dovrebbe memorizzare solo strutture di dati dello stesso "tipo". Questo spunta tutte le caselle: ogni oggetto in una pagina ha le stesse dimensioni, quindi non c'è frammentazione esterna. Gli oggetti dello stesso "tipo" hanno gli stessi requisiti di prestazione e la stessa semantica.
Per inciso, è una storia simile con allocazione. Per alcuni tipi di oggetti, probabilmente va bene aspettare se non c'è memoria immediatamente disponibile per allocare quell'oggetto. Un oggetto che rappresenta un file aperto potrebbe essere un esempio; l'apertura di un file è un'operazione costosa nel migliore dei casi, quindi attendere un po 'di più non farà molto male.
Per altri tipi di oggetti (ad esempio un oggetto che rappresenta un evento in tempo reale che deve accadere da un certo momento a oggi), non si vuole davvero aspettare. Quindi ha senso che alcuni tipi di oggetti si allocino eccessivamente (diciamo, abbiano alcune pagine libere riservate) in modo che le richieste possano essere soddisfatte senza aspettare.
Quello che stai sostanzialmente facendo è consentire ad ogni tipo di oggetto di avere un proprio allocatore, che può essere configurato per le esigenze di quell'oggetto. Questi allocatori per oggetto sono chiamati confusamente "cache". Allocare una cache per tipo di oggetto. (Sì, in genere implementeresti anche una "cache di cache".) Ogni cache memorizza solo oggetti dello stesso tipo (ad esempio solo strutture di thread o solo strutture di spazi di indirizzi).
Ogni cache, a sua volta, gestisce "lastre". Una lastra è una cornice di pagina che contiene una matrice di oggetti dello stesso tipo. Le lastre possono essere "piene" (tutti gli oggetti in uso), "vuote" (nessun oggetto in uso) o "parziali" (alcuni oggetti in uso).
Le lastre parziali sono probabilmente le più interessanti, poiché l'allocatore di lastre mantiene un elenco gratuito per ogni lastra parziale. (Le lastre complete e le lastre vuote non hanno bisogno di un elenco gratuito.) Gli oggetti vengono prima allocati dalle lastre parziali (e probabilmente dalle lastre parziali "più complete") per cercare di evitare l'allocazione di pagine non necessarie.
La cosa bella dell'allocazione delle lastre è che tutte queste opzioni della politica di allocazione (così come la semantica della memoria) possono essere ottimizzate per ogni tipo di oggetto. Alcune cache potrebbero conservare un pool di lastre vuote e altre no. Alcuni potrebbero essere in grado di essere scambiati con memoria secondaria e altri no.
Linux ha tre diversi tipi di allocatore di lastre, a seconda che tu abbia bisogno di compattezza, compatibilità con la cache o velocità raw. C'è stata una buona presentazione su questo un paio d'anni fa che spiega bene i compromessi.
L'allocatore di lastre Solaris (vedere il documento per i dettagli ) ha alcuni dettagli in più per ottenere prestazioni ancora maggiori. Per cominciare, in Solaris, tutto viene eseguito con l'allocazione delle lastre, inclusa l'allocazione dei frame di pagina. (Questa è la soluzione di Solaris per l'allocazione di oggetti di dimensioni superiori a mezza pagina.) Gestisce oggetti più piccoli annidando gli allocatori delle lastre nello spazio allocato sulle lastre.
Alcuni oggetti in Solaris richiedono costruzioni e distruzioni complesse e costose (ad esempio oggetti che hanno un blocco del kernel), e quindi potrebbero essere "parzialmente liberi" (ovvero costruiti ma non allocati). Solaris ottimizza inoltre l'allocazione gratuita delle lastre mantenendo elenchi gratuiti in base alla CPU, garantendo che alcune operazioni siano completamente prive di attesa.
Per supportare l'allocazione per scopi generici (ad es. Per array le cui dimensioni non sono note in fase di compilazione), la maggior parte dei sistemi operativi di tipo macrokernel ha anche cache che rappresentano le dimensioni degli oggetti anziché i tipi di oggetti . FreeBSD, ad esempio, mantiene le cache per oggetti sconosciuti le cui dimensioni sono potenze di 2 byte, da 4 a 256.
Ciò che spero che possiate vedere è che l'allocazione delle lastre è un framework molto flessibile che può essere ottimizzato per le esigenze di diversi tipi di dati. Non compete con il paging, ma lo integra (sebbene in Solaris, i frame di pagina siano allocati con lastre).
Spero che questo possa essere d'aiuto. Fammi sapere se qualcosa necessita di chiarimenti.