Eine benutzerfreundliche, plattformübergreifende C++11-Speicherzuordnungsbibliothek nur für Header mit einer MIT-Lizenz.
mio wurde mit dem Ziel erstellt, problemlos (dh ohne Abhängigkeiten) in jedes C++-Projekt eingebunden werden zu können, das speicherzugeordnete Datei-E/A benötigt, ohne dass Boost eingebunden werden muss.
Bitte zögern Sie nicht, ein Problem zu eröffnen. Ich werde versuchen, alle Bedenken so gut wie möglich auszuräumen.
Denn Memory Mapping ist das Beste seit geschnittenem Brot!
Im Ernst: Der Hauptgrund für das Schreiben dieser Bibliothek anstelle der Verwendung von Boost.Iostreams war die fehlende Unterstützung für die Einrichtung einer Speicherzuordnung mit einem bereits geöffneten Dateihandle/Deskriptor. Dies ist mit mio möglich.
Darüber hinaus erfordert die Lösung von Boost.Iostreams, dass der Benutzer Offsets genau an den Seitengrenzen auswählt, was umständlich und fehleranfällig ist. mio hingegen verwaltet dies intern, indem es jeden Versatz akzeptiert und die nächstgelegene Seitengrenze findet.
Auch wenn es ein kleiner Trottel ist, implementiert Boost.Iostreams speicherzugeordnete Datei-E/A mit einem std::shared_ptr
, um gemeinsame Semantik bereitzustellen, auch wenn dies nicht erforderlich ist, und der Overhead der Heap-Zuweisung kann unnötig und/oder unerwünscht sein. In mio gibt es zwei Klassen, die die beiden Anwendungsfälle abdecken: eine, die nur verschoben werden kann (im Grunde eine kostenlose Abstraktion über die systemspezifischen mmapping-Funktionen), und die andere, die sich genau wie ihr Boost.Iostreams-Gegenstück verhält, mit gemeinsame Semantik.
HINWEIS: Die Datei muss vorhanden sein, bevor eine Zuordnung erstellt werden kann.
Es gibt drei Möglichkeiten, eine Datei im Speicher abzubilden:
std::system_error
auslöst: mio::mmap_source mmap (path, offset, size_to_map);
Alternativ können Sie die Argumente offset
und size_to_map
weglassen. In diesem Fall wird die gesamte Datei zugeordnet:
mio::mmap_source mmap (path);
std::error_code error;
mio::mmap_source mmap = mio::make_mmap_source(path, offset, size_to_map, error);
oder:
mio::mmap_source mmap = mio::make_mmap_source(path, error);
map
Member-Funktion: std::error_code error;
mio::mmap_source mmap;
mmap.map(path, offset, size_to_map, error);
oder:
mmap.map(path, error);
HINWEIS: Für die Konstruktoren müssen Ausnahmen aktiviert sein. Wenn Sie Ihre Projekte lieber mit -fno-exceptions
erstellen möchten, können Sie trotzdem die anderen Möglichkeiten nutzen.
Darüber hinaus können Sie in jedem Fall entweder einen String-Typ für den Pfad der Datei angeben oder ein vorhandenes, gültiges Datei-Handle verwenden.
# include < sys/types.h >
# include < sys/stat.h >
# include < fcntl.h >
# include < mio/mmap.hpp >
// #include <mio/mio.hpp> if using single header
# include < algorithm >
int main ()
{
// NOTE: error handling omitted for brevity.
const int fd = open ( " file.txt " , O_RDONLY);
mio::mmap_source mmap (fd, 0 , mio::map_entire_file);
// ...
}
Allerdings prüft mio nicht, ob der bereitgestellte Dateideskriptor über die gleichen Zugriffsberechtigungen wie die gewünschte Zuordnung verfügt, sodass die Zuordnung möglicherweise fehlschlägt. Solche Fehler werden über den Out-Parameter std::error_code
gemeldet, der an die Mapping-Funktion übergeben wird.
WINDOWS-BENUTZER : Diese Bibliothek unterstützt die Verwendung von Breitzeichentypen für Funktionen, bei denen Zeichenfolgen erwartet werden (z. B. Pfadparameter).
# include < mio/mmap.hpp >
// #include <mio/mio.hpp> if using single header
# include < system_error > // for std::error_code
# include < cstdio > // for std::printf
# include < cassert >
# include < algorithm >
# include < fstream >
int handle_error ( const std::error_code& error);
void allocate_file ( const std::string& path, const int size);
int main ()
{
const auto path = " file.txt " ;
// NOTE: mio does *not* create the file for you if it doesn't exist! You
// must ensure that the file exists before establishing a mapping. It
// must also be non-empty. So for illustrative purposes the file is
// created now.
allocate_file (path, 155 );
// Read-write memory map the whole file by using `map_entire_file` where the
// length of the mapping is otherwise expected, with the factory method.
std::error_code error;
mio::mmap_sink rw_mmap = mio::make_mmap_sink (
path, 0 , mio::map_entire_file, error);
if (error) { return handle_error (error); }
// You can use any iterator based function.
std::fill (rw_mmap. begin (), rw_mmap. end (), ' a ' );
// Or manually iterate through the mapped region just as if it were any other
// container, and change each byte's value (since this is a read-write mapping).
for ( auto & b : rw_mmap) {
b += 10 ;
}
// Or just change one value with the subscript operator.
const int answer_index = rw_mmap. size () / 2 ;
rw_mmap[answer_index] = 42 ;
// Don't forget to flush changes to disk before unmapping. However, if
// `rw_mmap` were to go out of scope at this point, the destructor would also
// automatically invoke `sync` before `unmap`.
rw_mmap. sync (error);
if (error) { return handle_error (error); }
// We can then remove the mapping, after which rw_mmap will be in a default
// constructed state, i.e. this and the above call to `sync` have the same
// effect as if the destructor had been invoked.
rw_mmap. unmap ();
// Now create the same mapping, but in read-only mode. Note that calling the
// overload without the offset and file length parameters maps the entire
// file.
mio::mmap_source ro_mmap;
ro_mmap. map (path, error);
if (error) { return handle_error (error); }
const int the_answer_to_everything = ro_mmap[answer_index];
assert (the_answer_to_everything == 42 );
}
int handle_error ( const std::error_code& error)
{
const auto & errmsg = error. message ();
std::printf ( " error mapping file: %s, exiting... n " , errmsg. c_str ());
return error. value ();
}
void allocate_file ( const std::string& path, const int size)
{
std::ofstream file (path);
std::string s (size, ' 0 ' );
file << s;
}
mio::basic_mmap
kann nur verschoben werden. Wenn jedoch mehrere Kopien derselben Zuordnung erforderlich sind, verwenden Sie mio::basic_shared_mmap
mit der Semantik std::shared_ptr
und derselben Schnittstelle wie mio::basic_mmap
.
# include < mio/shared_mmap.hpp >
mio::shared_mmap_source shared_mmap1 ( " path " , offset, size_to_map);
mio::shared_mmap_source shared_mmap2 (std::move(mmap1)); // or use operator=
mio::shared_mmap_source shared_mmap3 (std::make_shared<mio::mmap_source>(mmap1)); // or use operator=
mio::shared_mmap_source shared_mmap4;
shared_mmap4.map( " path " , offset, size_to_map, error);
Es ist möglich, den Typ eines Bytes zu definieren (der die gleiche Breite wie char
haben muss), obwohl Aliase für die gebräuchlichsten standardmäßig bereitgestellt werden:
using mmap_source = basic_mmap_source< char >;
using ummap_source = basic_mmap_source< unsigned char >;
using mmap_sink = basic_mmap_sink< char >;
using ummap_sink = basic_mmap_sink< unsigned char >;
Es kann jedoch nützlich sein, eigene Typen zu definieren, beispielsweise wenn Sie den neuen Typ std::byte
in C++17 verwenden:
using mmap_source = mio::basic_mmap_source<std::byte>;
using mmap_sink = mio::basic_mmap_sink<std::byte>;
Obwohl dies im Allgemeinen nicht erforderlich ist, da mio von Benutzern angeforderte Offsets den Seitengrenzen zuordnet, können Sie die Granularität der Seitenzuordnung des zugrunde liegenden Systems abfragen, indem Sie mio::page_size()
aufrufen, das sich in mio/page.hpp
befindet.
Mio kann Ihrem Projekt als einzelne Header-Datei hinzugefügt werden, indem Sie einfach single_includemiomio.hpp
einbinden. Einzelne Header-Dateien können jederzeit neu generiert werden, indem das Skript amalgamate.py
in third_party
ausgeführt wird.
python amalgamate.py -c config.json -s ../include
Als reine Header-Bibliothek verfügt mio über keine kompilierten Komponenten. Dennoch wird ein CMake-Build-System bereitgestellt, das ein einfaches Testen, Installieren und Zusammenstellen von Teilprojekten auf vielen Plattformen und Betriebssystemen ermöglicht.
Mio wird mit einer kleinen Reihe von Tests und Beispielen vertrieben. Wenn mio als CMake-Projekt der höchsten Ebene konfiguriert ist, wird diese Suite ausführbarer Dateien standardmäßig erstellt. Die ausführbaren Testdateien von Mio sind in das CMake-Testtreiberprogramm CTest integriert.
CMake unterstützt eine Reihe von Backends zum Kompilieren und Verknüpfen.
So verwenden Sie ein Tool zum Erstellen statischer Konfigurationen wie GNU Make oder Ninja:
cd < mio source directory >
mkdir build
cd build
# Configure the build
cmake -D CMAKE_BUILD_TYPE= < Debug | Release >
-G < " Unix Makefiles " | " Ninja " > ..
# build the tests
< make | ninja | cmake --build . >
# run the tests
< make test | ninja test | cmake --build . --target test | ctest >
So verwenden Sie ein dynamisches Konfigurations-Build-Tool wie Visual Studio oder Xcode:
cd < mio source directory >
mkdir build
cd build
# Configure the build
cmake -G < " Visual Studio 14 2015 Win64 " | " Xcode " > ..
# build the tests
cmake --build . --config < Debug | Release >
# run the tests via ctest...
ctest --build-config < Debug | Release >
# ... or via CMake build tool mode...
cmake --build . --config < Debug | Release > --target test
Natürlich können die Build- und Testschritte auch über die Ziele all bzw. test aus der IDE heraus ausgeführt werden, nachdem die im Konfigurationsschritt generierte Projektdatei geöffnet wurde.
Mios Tests sind auch so konfiguriert, dass sie als Client für die Software-Qualitäts-Dashboard-Anwendung CDash fungieren. Weitere Informationen zu diesem Betriebsmodus finden Sie in der Kitware-Dokumentation.
Das Build-System von Mio bietet ein Installationsziel und Unterstützung für die nachgelagerte Nutzung über die intrinsische Funktion find_package
von CMake. CMake ermöglicht die Installation an einem beliebigen Ort, der durch die Definition von CMAKE_INSTALL_PREFIX
zum Zeitpunkt der Konfiguration angegeben werden kann. Wenn keine Benutzerangabe vorliegt, installiert CMake Mio an einem herkömmlichen Speicherort basierend auf dem Betriebssystem der Plattform.
So verwenden Sie ein Tool zum Erstellen statischer Konfigurationen wie GNU Make oder Ninja:
cd < mio source directory >
mkdir build
cd build
# Configure the build
cmake [-D CMAKE_INSTALL_PREFIX = " path/to/installation " ]
[-D BUILD_TESTING = False]
-D CMAKE_BUILD_TYPE=Release
-G < " Unix Makefiles " | " Ninja " > ..
# install mio
< make install | ninja install | cmake --build . --target install >
So verwenden Sie ein dynamisches Konfigurations-Build-Tool wie Visual Studio oder Xcode:
cd < mio source directory >
mkdir build
cd build
# Configure the project
cmake [-D CMAKE_INSTALL_PREFIX = " path/to/installation " ]
[-D BUILD_TESTING = False]
-G < " Visual Studio 14 2015 Win64 " | " Xcode " > ..
# install mio
cmake --build . --config Release --target install
Beachten Sie, dass für den letzten Befehl der Installationssequenz möglicherweise Administratorrechte erforderlich sind (z. B. sudo
), wenn das Installationsstammverzeichnis außerhalb Ihres Home-Verzeichnisses liegt.
Diese Installation
include/mio
des Installationsstammverzeichnissesshare/cmake/mio
des Installationsstammverzeichnisses Dieser letzte Schritt ermöglicht es nachgelagerten CMake-Projekten, Mio über find_package
zu verbrauchen, z
find_package ( mio REQUIRED )
target_link_libraries ( MyTarget PUBLIC mio::mio )
WINDOWS-BENUTZER : Das mio::mio
-Ziel #define
ist WIN32_LEAN_AND_MEAN
und NOMINMAX
. Ersteres stellt sicher, dass die importierte Oberfläche der Win-API minimal ist, und letzteres deaktiviert die min
und max
-Makros von Windows, damit sie std::min
und std::max
nicht beeinträchtigen. Da es sich bei mio um eine reine Header-Bibliothek handelt, werden diese Definitionen in nachgelagerte CMake-Builds eindringen. Wenn ihre Anwesenheit Probleme mit Ihrem Build verursacht, können Sie das alternative Ziel mio::mio_full_winapi
verwenden, das keine dieser Definitionen hinzufügt.
Wenn mio an einem nicht herkömmlichen Speicherort installiert wurde, kann es für Downstream-Projekte erforderlich sein, das Stammverzeichnis der mio-Installation über einen der beiden Speicherorte anzugeben
CMAKE_PREFIX_PATH
Konfigurationsoption,CMAKE_PREFIX_PATH
odermio_DIR
Umgebungsvariable.Weitere Informationen finden Sie in der Kitware-Dokumentation.
Darüber hinaus unterstützt mio verpackte verschiebbare Installationen über CPack. Rufen Sie nach der Konfiguration aus dem Build-Verzeichnis cpack wie folgt auf, um eine gepackte Installation zu generieren:
cpack -G < generator name > -C Release
Die Liste der unterstützten Generatoren variiert von Plattform zu Plattform. Eine vollständige Liste der unterstützten Generatoren auf Ihrer Plattform finden Sie in der Ausgabe von cpack --help
.
Um mio als Unterprojekt zu verwenden, kopieren Sie das mio-Repository in den Ordner dependencies/externals Ihres Projekts. Wenn die Version Ihres Projekts mithilfe von Git kontrolliert wird, kann ein Git-Submodul oder ein Git-Subtree zur Synchronisierung mit dem Updstream-Repository verwendet werden. Die Verwendung und die relativen Vorteile dieser Git-Funktionen gehen über den Rahmen dieses Dokuments hinaus, aber kurz gesagt, jede kann wie folgt ermittelt werden:
# via git submodule
cd < my project ' s dependencies directory>
git submodule add -b master https://github.com/mandreyel/mio.git
# via git subtree
cd <my project ' s root directory >
git subtree add --prefix < path/to/dependencies > /mio
https://github.com/mandreyel/mio.git master --squash
Wenn in einem Projekt ein Mio-Unterverzeichnis vorhanden ist, fügen Sie einfach die folgenden Zeilen zu Ihrem Projekt hinzu, um Mio-Include-Verzeichnisse zum Include-Pfad Ihres Ziels hinzuzufügen.
add_subdirectory ( path /to/mio/ )
target_link_libraries ( MyTarget PUBLIC <mio::mio | mio> )
Beachten Sie, dass Mios Tests und Beispiele als Unterprojekt nicht erstellt werden und die CPack-Integration auf das Hostprojekt verschoben wird.