Quando dovrei usare mmap per l'accesso ai file?


276

Gli ambienti POSIX offrono almeno due modi per accedere ai file. C'è lo standard chiamate di sistema open(), read(), write(), e gli amici, ma c'è anche la possibilità di utilizzare mmap()per mappare il file nella memoria virtuale.

Quando è preferibile usarne uno rispetto all'altro? Quali sono i loro vantaggi individuali che meritano tra cui due interfacce?


16
Vedi anche mmap () vs. lettura di blocchi e questo post di Linus Torvalds a cui fa riferimento una delle risposte lì.
MvG,

Risposte:


299

mmapè fantastico se hai più processi che accedono ai dati in sola lettura dallo stesso file, cosa comune nel tipo di sistemi server che scrivo. mmapconsente a tutti questi processi di condividere le stesse pagine di memoria fisica, risparmiando molta memoria.

mmapconsente inoltre al sistema operativo di ottimizzare le operazioni di paginazione. Ad esempio, considera due programmi; programma Ache legge in un 1MBfile in un buffer con cui viene creato malloce programma B con mmapsil file da 1 MB in memoria. Se il sistema operativo deve scambiare parte della Amemoria, deve scrivere il contenuto del buffer da scambiare prima di poter riutilizzare la memoria. Nel Bcaso in cui eventuali mmappagine non modificate possano essere riutilizzate immediatamente perché il sistema operativo sa come ripristinarle dal file esistente da cui mmapprovengono. (Il sistema operativo è in grado di rilevare quali pagine non sono modificate contrassegnando inizialmente mmaple pagine scrivibili come di sola lettura e rilevando errori seg , simile alla strategia Copia su scrittura ).

mmapè utile anche per la comunicazione tra processi . È possibile mmapun file come lettura / scrittura nei processi che devono comunicare e quindi utilizzare le primitive di sincronizzazione nella mmap'dregione (questo è lo scopo del MAP_HASSEMAPHOREflag).

Un posto mmappuò essere imbarazzante è se devi lavorare con file molto grandi su una macchina a 32 bit. Questo perché mmapdeve trovare un blocco contiguo di indirizzi nello spazio degli indirizzi del processo che è abbastanza grande da adattarsi all'intero intervallo del file da mappare. Questo può diventare un problema se lo spazio degli indirizzi diventa frammentato, dove potresti avere 2 GB di spazio di indirizzi libero, ma nessun intervallo individuale può adattarsi a un mapping di file da 1 GB. In questo caso, potrebbe essere necessario mappare il file in blocchi più piccoli di quelli che si desidera renderlo idoneo.

Un altro potenziale imbarazzo con mmapuna sostituzione per lettura / scrittura è che devi iniziare la tua mappatura su offset delle dimensioni della pagina. Se vuoi solo ottenere alcuni dati in offset X, dovrai correggere quell'offset in modo che sia compatibile con mmap.

E, infine, lettura / scrittura sono l'unico modo è possibile lavorare con alcuni tipi di file. mmapnon può essere usato su cose come pipe e tty .


10
Puoi usare mmap () su file in crescita? O la dimensione è fissa nel punto in cui si alloca la memoria / file mmap ()?
Jonathan Leffler,

29
Quando si effettua la chiamata mmap, è necessario specificare una dimensione. Quindi, se vuoi fare qualcosa come un'operazione di coda, non è molto adatto.
Don Neufeld,

5
Afaik MAP_HASSEMAPHOREè specifico di BSD.
Patrick Schlüter,

6
@JonathanLeffler Certamente puoi usare mmap () su file in crescita, ma devi chiamare di nuovo mmap () con le nuove dimensioni quando il file raggiunge il limite dello spazio inizialmente allocato. PosixMmapFile di LevelDB ti dà un buon esempio. Ma ha smesso di usare mmap da 1.15. Puoi ottenere la vecchia versione da Github
baotiao il

4
mmap potrebbe anche essere utile nel caso in cui un file debba essere elaborato in più passaggi: il costo di allocazione delle pagine di memoria virtuale viene pagato una sola volta.
Jib

69

Un'area in cui ho trovato mmap () non essere un vantaggio era quando leggevo file di piccole dimensioni (meno di 16 KB). L'overhead della pagina che ha sbagliato a leggere l'intero file è stato molto elevato rispetto al solo fare una singola chiamata di sistema read (). Questo perché il kernel a volte può soddisfare una lettura interamente nella tua fascia oraria, il che significa che il tuo codice non cambia. Con un errore di pagina, sembrava più probabile che fosse programmato un altro programma, rendendo l'operazione di file con una latenza più alta.


4
+1 Posso confermarlo. Per file di piccole dimensioni è più veloce mallocun pezzo di memoria e crearne uno read. Ciò consente di avere lo stesso codice che gestisce le mappe di memoria gestite da malloc.
Patrick Schlüter,

35
Detto questo, la tua giustificazione non è giusta. Lo scheduler non ha nulla a che fare con la differenza. La differenza deriva dagli accessi in scrittura alle tabelle delle pagine, che è una struttura globale del kernel che contiene quali processi contengono quale pagina di memoria e i suoi diritti di accesso. Questa operazione può essere molto costosa (può invalidere le linee della cache, può passare via TLB, la tabella è globale quindi deve essere protetta dall'accesso simultaneo, ecc.). È necessaria una determinata dimensione della mappa in modo che l'overhead degli readaccessi sia maggiore dell'overhead della manipolazione della memoria virtuale.
Patrick Schlüter,

1
@ PatrickSchlüter Va bene, capisco che all'inizio di mmap () c'è un overhead che implica la modifica della tabella delle pagine. Supponiamo di mappare 16K di un file in memoria. Per una dimensione di pagina di 4K, mmapdeve aggiornare 4 voci nella tabella delle pagine. Ma l'utilizzo readper copiare in un buffer di 16 KB comporta anche l'aggiornamento delle voci della tabella di 4 pagine, per non parlare del fatto che è necessario copiare il 16 KB nello spazio degli utenti. Quindi potresti approfondire le differenze di operazioni sulla tabella delle pagine e come è più costoso mmap?
flow2k,

45

mmapha il vantaggio quando si ha accesso casuale su file di grandi dimensioni. Un altro vantaggio è che si accede ad esso con operazioni di memoria (memcpy, aritmetica del puntatore), senza preoccuparsi del buffering. L'I / O normale a volte può essere piuttosto difficile quando si utilizzano i buffer quando si hanno strutture più grandi del buffer. Il codice da gestire che è spesso difficile da ottenere correttamente, mmap è generalmente più semplice. Detto questo, ci sono alcune trappole quando si lavora con mmap. Come già accennato, mmapè abbastanza costoso da installare, quindi vale la pena usarlo solo per una determinata dimensione (che varia da macchina a macchina).

Per gli accessi sequenziali puri al file, non è sempre la soluzione migliore, anche se una chiamata appropriata per madvisemitigare il problema.

Devi stare attento con le restrizioni di allineamento della tua architettura (SPARC, itanium), con IO di lettura / scrittura i buffer sono spesso correttamente allineati e non intrappolano quando si dereferenzia un puntatore cast.

Devi anche stare attento a non accedere al di fuori della mappa. Può succedere facilmente se usi le funzioni stringa sulla tua mappa e il tuo file non contiene un \ 0 alla fine. Funzionerà la maggior parte delle volte quando la dimensione del file non è un multiplo della dimensione della pagina poiché l'ultima pagina è riempita con 0 (l'area mappata ha sempre la dimensione di un multiplo della dimensione della pagina).


30

Oltre ad altre belle risposte, una citazione dalla programmazione del sistema Linux scritta dall'esperto di Google Robert Love:

Vantaggi di mmap( )

La manipolazione dei file tramite mmap( )presenta una manciata di vantaggi rispetto alle chiamate standard read( )e di write( )sistema. Tra questi ci sono:

  • La lettura e la scrittura in un file mappato in memoria evita la copia estranea che si verifica quando si utilizzano le chiamate di sistema read( )o write( ), in cui i dati devono essere copiati da e verso un buffer dello spazio utente.

  • A parte qualsiasi potenziale errore di pagina, la lettura e la scrittura in un file mappato in memoria non comporta alcun sovraccarico di chiamata di sistema o cambio di contesto. È semplice come accedere alla memoria.

  • Quando più processi mappano lo stesso oggetto in memoria, i dati vengono condivisi tra tutti i processi. I mapping scrivibili di sola lettura e condivisi sono condivisi nella loro interezza; i mapping scrivibili privati ​​hanno le loro pagine non ancora COW (copia su scrittura) condivise.

  • Cercare intorno alla mappatura implica manipolazioni di puntatori banali. Non è necessario per la lseek( )chiamata di sistema.

Per questi motivi, mmap( )è una scelta intelligente per molte applicazioni.

Svantaggi di mmap( )

Ci sono alcuni punti da tenere a mente quando si utilizza mmap( ):

  • I mapping di memoria sono sempre un numero intero di pagine di dimensioni. Pertanto, la differenza tra la dimensione del file di backup e un numero intero di pagine viene "sprecata" come spazio lento. Per file piccoli, una percentuale significativa della mappatura può essere sprecata. Ad esempio, con pagine da 4 KB, una mappatura da 7 byte spreca 4.089 byte.

  • I mapping di memoria devono adattarsi allo spazio degli indirizzi del processo. Con uno spazio degli indirizzi a 32 bit, un numero molto elevato di mappature di varie dimensioni può comportare la frammentazione dello spazio degli indirizzi, rendendo difficile trovare aree contigue libere di grandi dimensioni. Questo problema, ovviamente, è molto meno evidente con uno spazio di indirizzi a 64 bit.

  • C'è un sovraccarico nella creazione e nella gestione dei mapping di memoria e delle strutture dati associate all'interno del kernel. Questo sovraccarico è generalmente ovviato dall'eliminazione della doppia copia menzionata nella sezione precedente, in particolare per i file più grandi e con accesso frequente.

Per questi motivi, i vantaggi di mmap( )sono maggiormente realizzati quando il file mappato è grande (e quindi qualsiasi spazio sprecato è una piccola percentuale della mappatura totale) o quando la dimensione totale del file mappato è uniformemente divisibile per la dimensione della pagina ( e quindi non c'è spazio sprecato).


13

La mappatura della memoria ha il potenziale per un enorme vantaggio di velocità rispetto all'IO tradizionale. Consente al sistema operativo di leggere i dati dal file sorgente quando vengono toccate le pagine nel file mappato in memoria. Funziona creando pagine difettose, che il sistema operativo rileva e quindi carica automaticamente i dati corrispondenti dal file.

Funziona allo stesso modo del meccanismo di paging e di solito è ottimizzato per l'I / O ad alta velocità leggendo i dati sui limiti e sulle dimensioni della pagina del sistema (in genere 4K), una dimensione per cui è ottimizzata la maggior parte delle cache del file system.


15
Si noti che mmap () non è sempre più veloce di read (). Per letture sequenziali, mmap () non ti darà alcun vantaggio misurabile - questo si basa su prove empiriche e teoriche. Se non mi credi, scrivi il tuo test.
Tim Cooper,

1
Posso dare numeri provenienti dal nostro progetto, una sorta di indice di testo per un database di frasi. L'indice è grande diversi Gigabyte e le chiavi sono contenute in un albero ternario. L'indice continua a crescere in parallelo per l'accesso in lettura, l'accesso all'esterno delle parti mappate viene effettuato tramite pread. Su Solaris 9 Sparc (V890) gli accessi al pread sono da 2 a 3 volte più lenti rispetto memcpyal mmap. Ma hai ragione nel dire che l'accesso sequenziale non è necessariamente più veloce.
Patrick Schlüter,

19
Solo un piccolo pignolo. Non funziona come il meccanismo di paging, è il meccanismo di paging. La mappatura di un file sta assegnando un'area di memoria a un file anziché al file di scambio anonimo.
Patrick Schlüter,

2

Un vantaggio non ancora elencato è la possibilità di mmap()mantenere una mappatura di sola lettura come pagine pulite . Se si alloca un buffer nello spazio degli indirizzi del processo, quindi si utilizza read()per riempire il buffer da un file, le pagine di memoria corrispondenti a quel buffer sono ora sporche da quando sono state scritte.

Le pagine sporche non possono essere eliminate dalla RAM dal kernel. Se è presente spazio di scambio, è possibile effettuare il paging per scambiare. Ma questo è costoso e su alcuni sistemi, come i piccoli dispositivi integrati con solo memoria flash, non vi è alcuno scambio. In tal caso, il buffer rimarrà bloccato nella RAM fino a quando il processo non termina, o forse lo restituisce madvise().

Le mmap()pagine non scritte sono pulite. Se il kernel ha bisogno di RAM, può semplicemente rilasciarli e usare la RAM in cui si trovavano le pagine. Se il processo che aveva la mappatura vi accede di nuovo, provoca un errore di pagina che il kernel ricarica le pagine dal file da cui provenivano originariamente . Allo stesso modo sono stati popolati in primo luogo.

Ciò non richiede più di un processo utilizzando il file mappato per essere un vantaggio.


Il kernel non può eliminare una pagina mmap'd 'sporca' scrivendo prima i suoi contenuti nel file sottostante?
Jeremy Friesner,

2
Durante l'utilizzo read(), le pagine in cui vengono inseriti i dati non hanno alcuna relazione con il file da cui potrebbero provenire. Quindi non possono essere scritti, tranne per scambiare spazio. Se un file è mmap()ed, e la mappatura è scrivibile (al contrario della sola lettura), e scritta in, allora dipende dal fatto che la mappatura fosse MAP_SHAREDo MAP_PRIVATE. Una mappatura condivisa può / deve essere scritta nel file, ma una privata non può esserlo.
TrentP
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.