Kiedy powinienem używać mmap do dostępu do plików?

276

Środowiska POSIX zapewniają co najmniej dwa sposoby dostępu do plików. Jest średnia wywołania systemowe open(), read(), write()i przyjaciół, ale istnieje również możliwość korzystania mmap()zmapować plik do pamięci wirtualnej.

Kiedy lepiej jest używać jednego nad drugim? Jakie są ich indywidualne zalety, w tym dwa interfejsy?

Peter Burns
źródło
16
Zobacz także mmap () vs. czytanie bloków i ten post Linusa Torvaldsa przywołany w jednej z odpowiedzi tam.
MvG

Odpowiedzi:

299

mmapjest świetny, jeśli masz wiele procesów uzyskujących dostęp do danych w trybie tylko do odczytu z tego samego pliku, co jest powszechne w tego typu systemach serwerów, które piszę. mmapumożliwia wszystkim tym procesom współużytkowanie tych samych stron pamięci fizycznej, oszczędzając dużo pamięci.

mmapumożliwia także systemowi operacyjnemu optymalizację operacji stronicowania. Rozważmy na przykład dwa programy; program, Aktóry wczytuje 1MBplik do bufora tworzącego za pomocą malloci program B, który mmaps1 MB pliku do pamięci. Jeśli system operacyjny musi zamienić część Apamięci, musi zapisać zawartość bufora do zamiany, zanim będzie mógł ponownie użyć pamięci. W takim Bprzypadku wszelkie niezmodyfikowane mmapstrony mogą zostać ponownie użyte natychmiast, ponieważ system operacyjny wie, jak je przywrócić z istniejącego pliku, z którego były mmap. (System operacyjny może wykryć, które strony są niezmodyfikowane, początkowo oznaczając strony do zapisu mmapjako tylko do odczytu i wychwytując błędy seg , podobnie jak w przypadku strategii Kopiuj przy zapisie).

mmapjest również przydatny do komunikacji między procesami . Plik można mmapodczytywać / zapisywać w procesach, które muszą się komunikować, a następnie używać operacji podstawowych synchronizacji w mmap'dregionie (po to jest MAP_HASSEMAPHOREflaga).

Jedno miejsce mmapmoże być niewygodne, jeśli musisz pracować z bardzo dużymi plikami na komputerze 32-bitowym. Wynika to z faktu, że mmapmusi znaleźć ciągły blok adresów w przestrzeni adresowej procesu, który jest wystarczająco duży, aby zmieścił się w całym zakresie mapowanego pliku. Może to stanowić problem, jeśli twoja przestrzeń adresowa zostanie pofragmentowana, gdzie możesz mieć 2 GB wolnej przestrzeni adresowej, ale żaden indywidualny jej zakres nie mieści się w mapowaniu plików 1 GB. W takim przypadku może być konieczne mapowanie pliku na mniejsze fragmenty, niż gdybyś chciał go dopasować.

Inną potencjalną niezręcznością związaną z mmapzastępowaniem odczytu / zapisu jest konieczność mapowania na przesunięciach rozmiaru strony. Jeśli chcesz tylko uzyskać dane z przesunięciem X, musisz naprawić to przesunięcie, aby było zgodne mmap.

I wreszcie, odczytu / zapisu są jedynym sposobem może działać z niektórymi typami plików. mmapnie można go używać do rur i tty .

Don Neufeld
źródło
10
Czy możesz używać mmap () na rosnących plikach? A może rozmiar jest ustalony w momencie przydzielania pamięci / pliku mmap ()?
Jonathan Leffler
29
Kiedy wykonujesz połączenie mmap, musisz określić rozmiar. Więc jeśli chcesz zrobić coś takiego jak operacja ogonem, nie jest to zbyt odpowiednie.
Don Neufeld,
5
Afaik MAP_HASSEMAPHOREjest specyficzny dla BSD.
Patrick Schlüter
6
@JonathanLeffler Na pewno możesz używać mmap () na rosnących plikach, ale musisz ponownie wywołać mmap () z nowym rozmiarem, gdy plik osiągnie limit przydzielonego miejsca. PosixMmFFile pliku LevelDB daje dobry przykład. Ale przestał używać mmapa od 1.15. Możesz pobrać starą wersję z Github
baotiao
4
mmap może być również przydatny w przypadku, gdy plik wymaga przetworzenia w wielu przebiegach: koszt przydzielenia stron pamięci wirtualnej jest płacony tylko raz.
Jib,
69

Jednym z obszarów, w którym uważam, że mmap () nie jest zaletą, było czytanie małych plików (poniżej 16 KB). Narzut związany z błędem odczytu strony przez cały plik był bardzo wysoki w porównaniu z wykonaniem pojedynczego wywołania systemowego read (). Wynika to z faktu, że jądro czasami może całkowicie zaspokoić odczyt w wycinku czasu, co oznacza, że ​​kod się nie przełącza. W przypadku błędu strony bardziej prawdopodobne było zaplanowanie innego programu, co spowodowałoby większe opóźnienie operacji na pliku.

Ben Combee
źródło
4
+1 Mogę to potwierdzić. W przypadku małych plików jest to szybsze do mallocfragmentu pamięci i zrobienia z niego 1 read. To pozwala mieć ten sam kod, który obsługuje mapy pamięci, a Malloc'ed obsługuje.
Patrick Schlüter
35
To powiedziawszy, twoje uzasadnienie tego jest niewłaściwe. Harmonogram nie ma nic wspólnego z różnicą. Różnica polega na dostępie do zapisu do tablic stron, które są globalną strukturą jądra przechowującą to, co przetrzymuje, która strona pamięci i jej prawa dostępu. Ta operacja może być bardzo kosztowna (może unieważnić linie pamięci podręcznej, może przechodzić przez TLB, tabela jest globalna, więc musi być chroniona przed równoczesnym dostępem itp.). Potrzebujesz określonego rozmiaru mapy, aby narzut związany z readdostępami był większy niż narzut związany z manipulowaniem pamięcią wirtualną.
Patrick Schlüter
1
@ PatrickSchlüter Okay, rozumiem, że na początku mmap () występuje narzut związany z modyfikowaniem tabeli stron. Powiedzmy, że mapujemy 16K pliku do pamięci. W przypadku rozmiaru strony 4K mmapnależy zaktualizować 4 wpisy w tabeli stron. Ale użycie readdo skopiowania do bufora 16K obejmuje również aktualizację 4 pozycji tabeli stron, nie wspominając już o tym, że trzeba skopiować 16K do przestrzeni adresu użytkownika. Czy mógłbyś więc wyjaśnić różnice między operacjami w tabeli stron i jak to jest droższe mmap?
flow2k
45

mmapma tę zaletę, gdy masz losowy dostęp do dużych plików. Kolejną zaletą jest to, że uzyskujesz do niego dostęp za pomocą operacji pamięci (memcpy, arytmetyka wskaźnika), bez zawracania sobie głowy buforowaniem. Normalne operacje we / wy mogą czasami być dość trudne, gdy używasz buforów, gdy masz struktury większe niż bufor. Kod do obsługi, który jest często trudny do poprawnego wykonania, mmap jest ogólnie łatwiejszy. To powiedziawszy, istnieją pewne pułapki podczas pracy z mmap. Jak już wspomniano, mmapkonfiguracja jest dość kosztowna, dlatego warto ją stosować tylko dla danego rozmiaru (w zależności od maszyny).

W przypadku czystego sekwencyjnego dostępu do pliku nie zawsze jest to lepsze rozwiązanie, chociaż odpowiednie wywołanie madvisemoże złagodzić problem.

Musisz być ostrożny z ograniczeniami wyrównania w swojej architekturze (SPARC, itanium), w przypadku we / wy odczytu / zapisu bufory są często odpowiednio wyrównane i nie pułapkują podczas dereferencji rzutowanego wskaźnika.

Musisz także uważać, aby nie uzyskać dostępu poza mapą. Może się to łatwo zdarzyć, jeśli użyjesz funkcji ciągów na mapie, a plik nie będzie zawierał \ 0 na końcu. Będzie działał przez większość czasu, gdy rozmiar pliku nie jest wielokrotnością rozmiaru strony, ponieważ ostatnia strona jest wypełniona cyfrą 0 (obszar odwzorowany ma zawsze rozmiar wielokrotności rozmiaru strony).

Patrick Schlüter
źródło
30

Oprócz innych fajnych odpowiedzi cytat z programowania systemu Linux napisany przez eksperta Google, Roberta Love'a:

Zalety mmap( )

Manipulowanie plikami za pomocą mmap( )ma kilka zalet w stosunku do wywołań standardowych read( )i write( )systemowych. Wśród nich są:

  • Odczytywanie i zapisywanie do pliku odwzorowanego w pamięci pozwala uniknąć dodatkowej kopii, która występuje podczas korzystania z wywołań systemowych read( )lub write( ), w których dane muszą być kopiowane do iz bufora przestrzeni użytkownika.

  • Oprócz potencjalnych błędów strony, odczyt i zapis do pliku odwzorowanego w pamięci nie powoduje żadnego wywołania systemowego ani przełączenia kontekstu. To jest tak proste, jak dostęp do pamięci.

  • Gdy wiele procesów mapuje ten sam obiekt do pamięci, dane są współużytkowane przez wszystkie procesy. Odwzorowania tylko do odczytu i udostępnione do zapisu są udostępniane w całości; prywatne mapowania, w których można zapisywać, mają udostępnione strony, które nie są jeszcze COW (kopiowanie przy zapisie).

  • Wyszukiwanie wokół mapowania wymaga trywialnych manipulacji wskaźnikiem. Nie ma potrzeby lseek( )wywołania systemowego.

Z tych powodów mmap( )jest dobrym wyborem dla wielu aplikacji.

Wady mmap( )

Podczas używania należy pamiętać o kilku kwestiach mmap( ):

  • Odwzorowania pamięci mają zawsze całkowitą liczbę stron. Zatem różnica między rozmiarem pliku kopii zapasowej a liczbą całkowitą stron jest „marnowana” jako wolne miejsce. W przypadku małych plików znaczna część mapowania może zostać zmarnowana. Na przykład przy stronach o wielkości 4 KB mapowanie 7 bajtów marnuje 4089 bajtów.

  • Odwzorowania pamięci muszą pasować do przestrzeni adresowej procesu. Dzięki 32-bitowej przestrzeni adresowej bardzo duża liczba mapowań o różnych rozmiarach może powodować fragmentację przestrzeni adresowej, co utrudnia znalezienie dużych wolnych sąsiadujących regionów. Ten problem jest oczywiście znacznie mniej widoczny w 64-bitowej przestrzeni adresowej.

  • Tworzenie i utrzymywanie mapowań pamięci i powiązanych struktur danych w jądrze wiąże się z dodatkowymi kosztami. Narzutu tego na ogół unika się poprzez wyeliminowanie podwójnej kopii wspomnianej w poprzedniej sekcji, szczególnie w przypadku większych i często używanych plików.

Z tych powodów korzyści mmap( )są najbardziej widoczne, gdy zmapowany plik jest duży (a zatem każda zmarnowana przestrzeń stanowi niewielki procent całkowitego mapowania) lub gdy całkowity rozmiar zmapowanego pliku jest równomiernie podzielny przez rozmiar strony ( i dlatego nie ma zmarnowanej przestrzeni).

Miljen Mikic
źródło
13

Mapowanie pamięci ma potencjalnie ogromną przewagę prędkości w porównaniu do tradycyjnego IO. Pozwala systemowi operacyjnemu odczytać dane z pliku źródłowego po dotknięciu stron w pliku odwzorowanym w pamięci. Działa to poprzez tworzenie stron zawierających błędy, które system operacyjny wykrywa, a następnie system operacyjny automatycznie ładuje odpowiednie dane z pliku.

Działa to w taki sam sposób, jak mechanizm stronicowania i jest zwykle optymalizowane pod kątem szybkich operacji we / wy, odczytując dane na temat granic i rozmiarów stron systemowych (zwykle 4K) - rozmiaru, dla którego zoptymalizowana jest większość pamięci podręcznych systemu plików.

AndyG
źródło
15
Zauważ, że mmap () nie zawsze jest szybszy niż read (). W przypadku odczytów sekwencyjnych mmap () nie daje żadnej mierzalnej przewagi - jest to oparte na dowodach empirycznych i teoretycznych. Jeśli mi nie wierzysz, napisz własny test.
Tim Cooper,
1
Mogę podać liczby pochodzące z naszego projektu, rodzaj indeksu tekstowego dla bazy fraz. Indeks ma kilka gigabajtów, a klucze są przechowywane w drzewie trójskładnikowym. Indeks wciąż rośnie równolegle, aby uzyskać dostęp do odczytu, dostęp poza mapowanymi częściami odbywa się za pośrednictwem pread. W Solarisie 9 Sparc (V890) dostęp do pready jest od 2 do 3 razy wolniejszy niż memcpyz mapy. Ale masz rację, że sekwencyjny dostęp nie jest koniecznie szybszy.
Patrick Schlüter
19
Tylko trochę nitpick. Nie działa jak mechanizm stronicowania, to mechanizm stronicowania. Mapowanie pliku przypisuje obszar pamięci do pliku zamiast anonimowego pliku wymiany.
Patrick Schlüter
2

Zaletą, której jeszcze nie ma na liście, jest możliwość mmap()utrzymania mapowania tylko do odczytu jako czystych stron. Jeśli ktoś przydzieli bufor w przestrzeni adresowej procesu, a następnie użyje go read()do wypełnienia bufora z pliku, strony pamięci odpowiadające temu buforowi są teraz brudne, ponieważ zostały zapisane.

Jądro nie może usunąć brudnych stron z pamięci RAM. Jeśli jest miejsce na zamianę, można je przywołać w celu zamiany. Jest to jednak kosztowne i w niektórych systemach, takich jak małe urządzenia wbudowane z tylko pamięcią flash, w ogóle nie ma wymiany. W takim przypadku bufor utknie w pamięci RAM, dopóki proces się nie zakończy lub może go zwróci madvise().

Nie zapisane na mmap()stronach są czyste. Jeśli jądro potrzebuje pamięci RAM, może je po prostu upuścić i użyć pamięci RAM, w której były strony. Jeśli proces, który miał mapowanie, ponownie do niego dostęp, spowoduje błąd strony, jądro ponownie ładuje strony z pliku, z którego pochodzi. . W ten sam sposób zostały zaludnione.

Nie wymaga to więcej niż jednego procesu z wykorzystaniem mapowanego pliku.

TrentP
źródło
Czy jądro nie może upuścić „brudnej” strony z mapą mmap, pisząc najpierw jej zawartość do pliku źródłowego?
Jeremy Friesner
2
Podczas używania read() ze stron, na które są ostatecznie umieszczane dane, nie ma związku z plikiem, z którego mogły pochodzić. Dlatego nie można ich zapisać, z wyjątkiem zamiany miejsca. Jeśli plik jest mmap()ed, a mapowanie jest zapisywalne (w przeciwieństwie do tylko do odczytu) i zapisywane, to zależy to od tego, czy mapowanie było MAP_SHAREDczy MAP_PRIVATE. Wspólne mapowanie może / musi zostać zapisane do pliku, ale prywatne nie może.
TrentP