Lezione T14 Gestione memoria kernel Sistemi Operativi (9 CFU), CdL Informatica, A. A. 2013/2014 Dipartimento di Scienze Fisiche, Informatiche e Matematiche Università di Modena e Reggio Emilia http://weblab.ing.unimo.it/people/andreolini/didattica/sistemi-operativi 1
Quote of the day (Meditate, gente, meditate ...)
“Caching is important because the cost of constructing an object can be significantly higher than the cost of allocating memory for it.” Jeff Bonwick(1965-) Programmatore Inventore del file system ZFS Inventore dell'allocatore SLAB 2
LAYOUT DI MEMORIA X86 3
Layout di memoria (x86) (3GB al processo, 1GB al kernel)
Nell'architettura x86 lo spazio degli indirizzi lineari è diviso in due grossi tronconi. 0-3GB.
4GB Kernel
3GB
Assegnato ad ogni processo. Sostituito ad ogni cambio contesto con lo spazio del nuovo processo.
Processo
3-4GB.
Assegnato al kernel. Mai sostituito.
0 4
Organizzazione dello spazio del kernel (Che cosa è memorizzato in questo GB?)
Lo spazio di indirizzamento lineare del kernel è suddiviso in aree distinte. Le aree sono separate da piccole zone non utilizzate (gap), usate come sentinelle per il controllo degli overflow da una zona all'altra. 3GB (0xc0000000)
3GB + 896MB (0xf8000000)
Immagine del kernel
Frame fisici
Gap
Area vmalloc
8MB
~880MB
8MB
120 MB
896MB
4GB - 1 (0xffffffff )
Gap Area kmap Area fixmap Gap
8KB
~1-6 MB
128MB
~1-2MB
8KB 5
La suddivisione esatta (Ottenibile con il comando dmesg)
La suddivisione proposta è semplificata. Se si ha a disposizione una macchina a 32 bit e si vuole conoscere l'esatta suddivisione: si legga il log del kernel con dmesg | less -Mr. si cerchi la riga “virtual kernel memory layout”. si leggano le righe successive fino a “.text”.
ATTENZIONE! Una macchina a 64 bit ha un layout di memoria completamente diverso! → Non cercate il messaggio (non lo trovereste).
6
L'immagine del kernel (Contiene il codice “permanente” del kernel)
I primi 8MB di memoria sono dedicati all'immagine del kernel. Qui è decompresso il codice del kernel all'inizio del boot. Quest'area di memoria non è usata per i moduli! L'area è fisicamente e linearmente contigua. L'indirizzo fisico iniziale dell'area è 0x00000000; quello lineare è 0xc0000000. 7
Traduzione degli indirizzi lineari ↔ fisici (Una semplice sottrazione!)
La contiguità fisica e lineare implica una enorme semplificazione nella traduzione degli indirizzi. Si usano sottrazioni o addizioni. Ad esempio, se “pa” è l'indirizzo fisico e “la” quello lineare: pa = la – 0xc0000000 la = pa + 0xc0000000
Per quest'area di memoria la traduzione è diretta, senza ricorrere alla tabella delle pagine! 8
Funzioni per la traduzione (__pa(), __va())
Le due funzioni di traduzione sono le seguenti.
__pa(x): traduce l'indirizzo lineare x nell'indirizzo fisico corrispondente. __va(x): traduce l'indirizzo fisico x nell'indirizzo lineare corrispondente.
Esse sono definite nel file:
$LINUX/arch/x86/include/asm/page.h. 9
Frame liberi
(A disposizione del kernel)
I successivi 880MB sono occupati dai frame fisici. Essi possono essere liberi o allocati ad un componente del kernel. Frame liberi: gestiti dall'allocatore buddy. Frame occupati: gestiti dall'allocatore SLAB.
Come per l'immagine del kernel, gli indirizzi sono contigui sia fisicamente, sia linearmente. Pertanto, si usa la traduzione diretta vista in precedenza. 10
Area vmalloc (Per i moduli del kernel)
L'area vmalloc, grande circa 120MB, è dedicata alle porzioni di codice e dati dei moduli del kernel. Poiché i moduli sono attivati e disattivati più volte durante il ciclo di vita del kernel, l'area di memoria sottostante non è garantita essere fisicamente contigua. → Si usa la consueta tabella delle pagine per la traduzione degli indirizzi. 11
Gestione memoria area vmalloc (vmalloc(), vfree())
È possibile allocare e deallocare blocchi di memoria contigui virtualmente con le due funzioni seguenti. vmalloc(): allocazione blocco. vfree(): deallocazione blocco.
File $LINUX/mm/vmalloc.c.
12
Low memory, High memory (Non c'entra nulla con i 640KB dei primi PC)
4GB
Low Memory: memoria accessibile direttamente, poiché esiste una mappatura (diretta o via tabella delle pagine). Immagine del kernel. Area vmalloc. → I primi 896MB del kernel.
Memoria fisica
High Memory
896MB
High Memory: memoria non accessibile direttamente al kernel, poiché non esiste una mappatura.
Low Memory
0 13
Accesso alla High Memory (Buffer I/O)
In alcuni casi può essere necessario, da parte del kernel, accedere alla memoria fisica sopra il GB. Buffer di rete copiati direttamente in userspace. Buffer video copiati direttamente in userspace.
Il kernel non può accedere a tale memoria, poiché:
non è prevista una mappatura diretta verso gli indirizzi relativi (non sono fisicamente contigui!). non è prevista una mappatura nella tabella delle pagine del kernel (lo spazio di indirizzamento è riservato ai processi).
Come fare per accedere a tale memoria?
14
Area kmap
(Si usano gli indirizzi lineari nell'area kmap!)
L'area kmap, grande circa 1-6MB, è dedicata alla creazione di mappature lineari verso frame fisici oltre il GB. Tali mappature sono di breve durata (tipicamente, qualche operazione di I/O).
kmap(struct page *p): ritorna un indirizzo lineare nell'area kmap, associato all'indirizzo iniziale del frame fisico rappresentato da p. kunmap(struct page *p): distrugge l'associazione fra indirizzo lineare nell'area kmap e indirizzo iniziale del frame fisico rappresentato da p.
File: $LINUX/arch/x86/mm/highmem_32.c.
15
Area fixmap
(Blocchi di memoria con indirizzi lineari costanti)
L'ultima area di memoria ospita pagine di 4KB i cui indirizzi lineari iniziali sono costanti. Esempio: un blocco contiene il codice della libreria dinamica VDSO (Virtual Dynamic Shared Object).
Implementazione del meccanismo di system call (SYSENTER o int 0x80, a seconda del processore). Indirizzo iniziale del blocco sempre uguale → facilmente individuabile dal loader dinamico. 16
Individuazione dei blocchi nelle fixmap (Si usa un indice intero)
Internamente gli indirizzi lineari delle pagine sono associati ad un numero intero definito dalla enumerazione: enum fixed_addresses File $LINUX/arch/x86/include/asm/fixmap.h.
Per esempio, al VDSO corrisponde l'indice FIX_VDSO. 17
Gestione dei blocchi nell'area fixmap (Tramite un'altra famiglia di funzioni)
La gestione dei blocchi avviene tramite una opportuna famiglia di funzioni nel file: $LINUX/arch/x86/include/asm/fixmap.h.
Funzioni.
set_fixmap(idx, phys): associa l'indirizzo lineare corrisposto all'indice idx all'indirizzo fisico phys. clear_fixmap(idx, phys): rimuove l'associazione creata da set_fixmap(). __fix_to_virt(x): ritorna l'indirizzo lineare associato all'indirizzo lineare corrisposto ad x. __virt_to_fix(x): ritorna l'indice corrisposto all'indirizzo 18 lineare x.
LAYOUT DI MEMORIA X86-64 19
Layout di memoria (x86-64) (3GB al processo, 1GB al kernel)
Nell'architettura x86-64 è simile concettualmente alla x86. I numeri sono leggermente diversi. 0-128TB.
Assegnato ad ogni processo. Sostituito ad ogni cambio contesto con lo spazio del nuovo processo.
128TB-256TB.
Assegnato al kernel. Mai sostituito.
256TB Kernel
128TB Processo
0 20
Organizzazione dello spazio del kernel (Concettualmente simile all'x86, ma guardate le dimensioni delle aree)
Indirizzo iniziale 0xffff800000000000 0xffff810000000000 0xffff880000000000 0xffffc80000000000 0xffffc90000000000 0xffffe90000000000 0xffffea0000000000 0xffffeb0000000000 0xffffffff80000000 0xffffffffa0000000 0xffffffffff600000 0xffffffffffe00000
Indirizzo finale
0xffff80ffffffffff 0xffff87ff00000000 0xffffc7ffffffffff 0xffffc8ffffffffff 0xffffe8ffffffffff 0xffffe9ffffffffff 0xffffeaffffffffff 0xffffffff7fffffff 0xffffffffa0000000 0xffffffffff5fffff 0xffffffffffdfffff 0xffffffffffffffff
Dimensione
1TB 16EB 64TB 1TB 32TB 1TB 1TB 21TB 512MB 1525MB 8MB 2MB
Scopo
Gap Spazio non indirizzabile Frame fisici Gap Area vmalloc (I/O) Gap Area fixmap Non usato Immagine del kernel Area moduli kernel Area vsyscall Non usato 21
GESTIONE DEI FRAME LIBERI 22
Lo scenario
(Perché serve un gestore dei frame liberi)
La memoria allocata dal kernel e dai processi deve essere supportata da frame fisici. È necessario gestire in maniera efficiente una lista di frame liberi. Allocazione: si pescano frame dalla lista. Deallocazione: si restituiscono i frame alla lista.
23
Il problema
(Perché una semplice lista di frame è un'idea folle)
Si potrebbe pensare di usare una lista collegata di frame (struct page). Tale approccio sarebbe folle.
Quante struct page esistono in un sistema con 8GB di RAM?
Occorre pensare alternativa.
ad
una
rappresentazione
24
Buddy system
(Un allocatore a potenze di due)
Il kernel gestisce l'area dei frame liberi mediante un allocatore a potenze di due.
Nei processori più recenti, tale area di memoria è gestita da più allocatori, ciascuno per ogni CPU. Per semplicità, si propone la versione su singola CPU.
In un allocatore a potenze di due, le richieste di memoria sono soddisfatte, per l'appunto, a potenze di due. 4KB, 8KB, 16KB, 32KB, … Richiesta di 11KB: soddisfatta con un blocco di 16KB.
25
Rappresentazione ad albero
(I nodi sono aree di memoria libera e fisicamente contigua)
I frame fisici sono organizzati in un albero. Ciascun nodo è un blocco di memoria fisica contigua. Si supponga di dover servire una richiesta di 21KB. Come opera l'allocatore?
256KB
128KB
64KB
128KB
64KB
32KB
32KB 26
Un esempio di uso (Costruzione ricorsiva dell'albero)
256KB
Inizialmente è presente un solo nodo (di 256KB per semplicità). Viene ricevuta la richiesta di 21KB. 27
Un esempio di uso (Costruzione ricorsiva dell'albero)
L'allocatore considera l'unico blocco foglia da 256KB. La dimensione del blocco libero è maggiore del doppio di 21KB. L'allocatore spacca a metà il blocco, creando due blocchi da 128KB. I due blocchi da 128KB si chiamano compari (buddy).
256KB
128KB
128KB
28
Un esempio di uso (Costruzione ricorsiva dell'albero)
L'allocatore considera il primo blocco foglia da 128KB. La dimensione del blocco libero è maggiore del doppio di 21KB. L'allocatore spacca a metà il blocco, creando due blocchi da 64KB.
256KB
128KB
64KB
128KB
64KB
29
Un esempio di uso (Costruzione ricorsiva dell'albero)
L'allocatore considera il primo blocco foglia da 64KB. La dimensione del blocco libero è maggiore del doppio di 21KB. L'allocatore spacca a metà il blocco, creando due blocchi da 32KB.
256KB
128KB
64KB
128KB
64KB
32KB
32KB 30
Un esempio di uso (Assegnazione di un blocco)
L'allocatore considera il primo blocco foglia da 32KB. La dimensione del blocco libero è: maggiore di 21KB. minore di 42KB.
→ Non si può spezzare ulteriormente. Si associa un blocco da 32KB.
256KB
128KB
64KB
128KB
64KB
32KB
32KB 31
Un esempio di uso (Fusione di blocchi liberi adiacenti)
256KB
Quando il blocco è restituito l'allocatore prova a fondere il blocco liberato con il suo vicino (coalescing).
128KB
64KB
128KB
64KB
32KB
32KB
Coalescing
32
Un esempio di uso (Fusione di blocchi liberi adiacenti)
256KB
Se ci riesce (ossia, se il compare è libero) l'allocatore prova a fondere compari contigui sempre più grandi.
128KB
64KB
128KB
64KB
Coalescing
33
Cenni implementativi (Buddy system in Linux)
order free_area 0 1 2 3 4 5 6 i 8 9 10 11
In Linux, l'albero è implementato tramite la struttura dati seguente: struct free_area. $LINUX/include/mm.h.
L'elemento i contiene blocchi di 2 frame fisici (detti di ordine i).
34
Allocazione e rilascio di frame fisici (Tramite funzioni molto semplici)
L'allocazione ed il rilascio di frame fisici avviene tramite le funzioni già viste (e parte integrante dell'allocatore buddy): alloc_pages(). free_pages(). 35
Riassunto
(Delle considerazioni precedenti)
L'allocatore buddy è buono per gestire grandi aree di memoria libera. Serve un ulteriore allocatore che:
tenga conto delle aree di memoria allocate (e non solo di quelle libere). sappia risolvere il problema della frammentazione interna. 36
ALLOCAZIONE DI MEMORIA 37
Allocatore SLAB
(Una “cache” di blocchetti di memoria preallocata)
L'allocatore a lastre (SLAB allocator) è stato inizialmente implementato in Solaris 2.4 (1994). Esso gestisce una “cache” di porzioni di memoria con le seguenti caratteristiche: preallocate. fisicamente contigue. linearmente contigue.
Se il kernel richiede una porzione di memoria, l'allocatore fornisce un'area preallocata dalla cache. Quando la memoria è liberata, è restituita 38 alla cache.
Organizzazione dell'allocatore
(Per “lastre” di “oggetti”; si allarga e si accorcia come una fisarmonica)
Uno SLAB è un insieme di pagine logiche associate a frame fisici contigui. Uno o più SLAB contigui formano una cache. Ciascuna cache è un contenitore di oggetti. Un oggetto è un'area di memoria di una specifica dimensione. Struttura dati, buffer.
Una cache contiene solo oggetti di un dato tipo. Implementazione: in due file. $LINUX/mm/slab.c, $LINUX/mm/slab-common.c.
39
Architettura dello SLAB (Un'immagine vale più di 1000 parole)
Oggetti Oggetti da 3KB
Cache
SLAB
Uno SLAB (gruppo di pagine contigue fisicamente) Padding
Oggetti da 7KB
40
Stati di uno SLAB
(Pieno, vuoto, parzialmente vuoto)
Pieno. Tutti i suoi oggetti sono marcati in uso. Vuoto. Tutti i suoi oggetti sono marcati liberi. Parzialmente vuoto. I primi oggetti sono marcati in uso, i rimanenti sono marcati liberi. Tutti gli oggetti in un dato stato sono mantenuti vicini → si riduce la frammentazione esterna. 41
Creazione cache
(“Formatta” lo spazio di memoria fisico)
Una cache è creata in fase di inizializzazione. Boot del kernel, caricamento di un modulo.
Operazioni svolte:
Prenotazione memoria logica. Associazione con frame fisici contigui. Suddivisione di una cache in SLAB. Creazione degli oggetti liberi all'interno degli SLAB. Padding fra oggetti.
Funzione kmem_cache_create().
42
Esempio di creazione cache (Inode cache di EXT4)
Un esempio di kmem_cache_create() è preso dal codice di EXT4 per la creazione delle strutture dati relative agli inode. Si usa una cache di struct ext4_inode_info. File $LINUX/fs/ext4/super.c. Funzione init_inodecache().
43
Recupero di un elemento dalla cache (Semplicissimo)
La funzione kmem_cache_alloc() individua un oggetto libero dalla cache e lo ritorna all'utente. Dopo l'uso, l'oggetto può essere restituito alla cache con la funzione kmem_cache_free(). 44
Individuazione di un oggetto libero 1/2 (L'allocatore mantiene aggiornati diversi puntatori)
L'allocatore SLAB gestisce diversi puntatori per motivi di efficienza
puntatore allo SLAB parzialmente vuoto. puntatore al primo SLAB vuoto. puntatore al primo oggetto libero all'interno di uno SLAB. 45
Individuazione di un oggetto libero 2/2 (L'allocatore mantiene aggiornati diversi puntatori)
Quando il kernel richiede una struttura dati, l'allocatore cerca di fornire il primo oggetto libero all'interno dello SLAB parzialmente vuoto. Se non esistono SLAB parzialmente vuoti, si fornisce il primo oggetto libero del primo SLAB vuoto. Se non esiste uno SLAB vuoto, l'allocatore ne crea uno (cache grow) e lo appende in fondo alla cache. 46
Gestione dimensione cache (L'allocatore mantiene aggiornati diversi puntatori)
La dimensione di una cache è gestita in maniera trasparente all'utente e dinamica attraverso due funzioni.
kmem_cache_grow(): aggiunge uno SLAB ad una cache esistente. kmem_cache_shrink(): rimuove uno SLAB da una cache esistente.
La memoria è presa/restituita dall'/all'allocatore buddy.
47
Distruzione cache (Rilascia la memoria prenotata)
La funzione kmem_cache_destroy() distrugge le strutture dati di gestione della cache e ritorna le pagine all'allocatore buddy. 48
Vantaggi dell'allocatore SLAB (Diversi)
L'allocatore SLAB non soffre del problema della frammentazione interna (la memoria viene ritornata “su misura”). Gli oggetti sono preallocati in fase di creazione degli SLAB; le funzioni di allocazione e deallocazione della memoria sono velocissime. 49
Svantaggi dell'allocatore SLAB (Uno, ma particolarmente fastidioso)
L'allocatore SLAB richiede una struttura dati ben precisa su cui costruire la cache. Che succede se un utente vuole allocare un buffer generico di n byte (n potenza di due)? 50
kmalloc(), kfree()
(La soluzione al problema precedente)
Il kernel mette a disposizione un allocatore di buffer generici con sintassi molto simile a quella dell'allocatore della libreria del C.
void *malloc(size_t size): ritorna un buffer in grado di contenere l'allocazione richiesta. void free(const void *objp): distrugge il buffer.
Tali funzioni sono implementate tramite diverse cache di buffer a potenze di due crescenti.
51
Quante cache esistono nel sistema? (È possibile scoprirlo con alcuni semplici comandi)
Il file /proc/slabinfo contiene l'elenco delle cache attivate dal kernel, con statistiche sull'uso. Il primo campo di ogni riga è il nome della cache.
Notate le cache di nome kmalloc-8, kmalloc-16, … È da queste cache che kmalloc() pesca gli oggetti per soddisfare le allocazioni di buffer generici. 52