La memoria virtuale di un processo Linux è suddivisa in un certo numero di aree di memoria virtuale (VMA) per differenziare le diverse parti del programma eseguibile. Questo è fatto rispettivamente per:
Ogni VMA è caratterizzata da una coppia di indirizzi virtuali di pagina che ne definiscono l'inizio e la fine. Ogni area è quindi costituita da un numero intero di pagine consecutive con caratteristiche di accesso omogenee.
La VMA di un processo è composta da:
brk
.struct vm_area_struct {
struct mm_struct* vm_mm;
unsigned long vm_start;
unsigned long vm_end;
struct vm_area_struct *vm_next, *vm_prev;
unsigned long vm_flags;
...
unsigned long vm_pgoff;
struct file *vm_file;
}
Un'area virtuale di memoria può essere mappata su file detto backing store. In caso contrario l'area è detta anonima.
La struttura vm_area_struct
contiene l'informazione struct file * vm_file
per il puntatore al file utilizzato come backing store e la posizione all'interno del file unsigned long vm_pgoff
.
Per le aree C, K ed S il backing store è il file eseguibile, con offset il punto del file eseguibile dove inizia il corrispondente segmento.
Esistono due tipi di aree di memoria virtuale:
La creazione di una VMA consiste esclusivamente nella definizione dello spazio virtuale associato senza alcuna allocazione di pagine fisiche. Durante questo procedimento si predispone la porzione di tabella delle pagine necessaria a rappresentare le pagine virtuale della VMA. Per ogni pagina virtuale la corrispondente PTE indicherà che la pagina non è fisicamente allocata.
L'allocazione delle pagine fisiche avviene solamente quando un processo legge o scrive una pagina virtuale NPV. In questo caso nella PTE associata a tale NPV viene inserito in tale caso il numero di NPF di pagina fisica.
È quindi importante distinguere tra le operazioni che modificano esclusivamente lo spazio virtuale del processo e e le operazioni che allocano memoria fisica.
Quando la MMU genera un interrupt di page fault per una pagina virtuale NPV viene attivata la routine di risposta Page Fault Handler.
Assumiamo che la tabella delle pagine contenga solo le pagine virtuali appartenenti alle VMA del processo. Distinguiamo se una pagina virtuale è residente in memoria tramite il flag P=1
.
Linux crea sempre
La tecnica utilizzata per il caricamento delle pagine in memoria fisica è il demand paging, cioè il caricamento delle pagine necessarie in base alle richieste mediante page fault.
Al momento dell'esecuzione di un programma la VMA viene creata con
Il tentativo di accedere ad una pagina virtuale posta subito dopo l'area esistente (pagina di growsdown) produce una crescita automatica dell'area, mentre il tentativo di accedere pagine diverse da quelle esistenti o quella di growsdown produce una segmentation fault.
La memoria dati dinamica (heap) cresce su richiesta del programma. Normalmente in C si usa la funzione malloc
, che alloca un nuovo spazio nella VMA D tramite il servizio di sistema brk
o sbrk
.
void* sbrk(intptr_t incremento)
Incrementa l'area dati dinamici del valore incremento
.
int brk(void* end_data_sergment)
Assegna il valore end_data_segment
al campo end
della VMA dei dati dinamici, restituisce
Si ha che il nuovo processo creato è un'immagine del padre. Linux aggiorna la tabella dei processi ma non alloca nuova memoria fisica.
Il nuovo processo condivide la memoria del padre tranne quella di pila, che contiene la variabile pid.
Quando uno dei due processi cerca di scrivere in memoria si duplica la pagina. Questa tecnica è detta copy on write. Per identificare un'operazione di scrittura tutte le pagine del padre condivise con il figlio al momento della fork vengono modificate nei diritti di accesso solo lettura.
Ad ogni pagine fisica è associato un descrittore di pagina che contiene un contatore ref_count
che indica il numero di utilizzatori della pagina fisica.
Dopo una fork le pagine duplicate fisicamente sono solo quelle scritte durante l'esecuzione della fork.
In Linux nella Copy on Write la pagina fisica originale è attribuita al processo figlio, mentre per il processo padre viene allocata una nuova pagina fisica.
L'esecuzione della exec
pone a "non valido" tutte le pagine relative a codice e dati del processo. La struttura delle VMA viene riscostruita in base al contenuto del file eseguibile e vengono predisposte le PTE necessarie nella TP.
Viene quindi caricata in memoria fisica la pagina di codice contenente la prima istruzione eseguibile, la prima pagina di pila nella quale vengono posti i parametri passati al main e il programma inizia l'esecuzione.
Il context switch causa lo svuotamento del TLB, e copia il valore del dirty bit associato alle pagine del TLB nel descrittore della pagina fisica page_descriptor
.
La procedura di exit elimina la struttura delle VMA del processo, elimina la tabella delle pagine del processo e dealloca le pagine virtuali del processo con ref_count=1
liberando le pagine fisiche. Infine si esegue il context switch con relativo flush del TLB.
I thread condividono la stessa struttura di aree virtuali e tabella delle pagine del processo padre.
In Linux la creazione di ogni thread alloca un'area di tipo T di
La pila del primo thread inizia logicamente al NPV
Le VMA possono essere create dal sistema operativo tramite la system call mmap
. Come detto in precedenza le VMA possono essere classificate in base a due criteri: Mappate su file o anonime, e shared o private.
Consideriamo i tipi di aree shared e mappate su file, private e mappate su file e anonime e private.
#include <sys/mman.h>
void* mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
Le page cache sono l'insieme di pagine fisiche utilizzate per rappresentare i file in memoria e le strutture dati e funzioni necessarie per la gestione.
Quando un processo richiede di accedere ad una pagina virtuale mappata su file esso
I dati vengono scritti sulla pagina della page cache condivisa. Quindi la pagina fisica viene modificata, tutti i processi che mappano tale pagina fisica vedono immediatamente tutte le modifiche, e la pagina modificata verrà scritta su disco una volta sola al termine dell'utilizzo.
Per quanto riguarda il meccanismo di copy on write le pagine delle VMA di tipo scrivibile vengono abilitate solo in lettura. Quando un processo P scrive su una di queste pagine si verifica un page fault per violazione di protezione. Quindi il page fault handler alloca una nuova pagina fisica, e ci copia il contenuto della pagina che ha generato la violazione. Pone quindi nella tabella delle pagine la corrispondenza NPV - NPF e modifica il diritto di accesso in scrittura.
Tutte la pagine nelle page cache non vengono liberate neppure quando tutti i processi che le utilizzavano non le usano più. Linux applica il principio di mantenere in memoria le pagine lette da disco il più a lungo possibile, per evitare successivi accessi a disco se richiesti nuovamente.
Le pagine in page cache vengono liberate in base alla politica di gestione della memoria quando si arriva ad una situazione di memoria quasi piena. In questo caso vengono salvate sul file del disco.
Le aree anonime non hanno un file associato, e quindi la definizione di un'area anonima non alloca memoria fisica. Le pagine virtuali sono tutte mappate sulla ZeroPage (pagina fisica di tutti
Le pagine anonime se devono essere scaricate dalla memoria vengono salvate nello swap file.
Le VMA di codice sono mappate su file e sono private. Se due processi eseguono contemporaneamente lo stesso codice le pagine di codice sono condivise e il secondo processo trova le pagine in Page Cache.
Il linker dinamico utilizza le VMA per la condivisione delle pagine fisiche delle librerie condivise. Il linker crea le VMA per le librerie utilizzando MAP_PRIVATE
.