From 61470daa0897a0c93d6f837a550a71fa9c092ccf Mon Sep 17 00:00:00 2001 From: mandreyel Date: Sun, 8 Oct 2017 23:14:43 +0200 Subject: [PATCH] updated example, removed multi-byte support, bugfix --- .gitignore | 1 + README.md | 53 +++--- include/mio/detail/basic_mmap.hpp | 73 +++----- include/mio/detail/basic_mmap.ipp | 275 +++++++++++++++--------------- include/mio/mmap.hpp | 110 ++++-------- include/mio/shared_mmap.hpp | 82 +++------ test/example.cpp | 49 ++++++ test/test.cpp | 16 +- 8 files changed, 296 insertions(+), 363 deletions(-) create mode 100644 test/example.cpp diff --git a/.gitignore b/.gitignore index 823240e..2b513ca 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ test/** !test/test.cpp +!test/example.cpp diff --git a/README.md b/README.md index 8da0d42..ffcea7d 100644 --- a/README.md +++ b/README.md @@ -43,10 +43,7 @@ General usage: #include #include // for std::error_code #include // for std::printf - -// This is just to clarify which is which when using the various mapping methods. -using offset_type = mio::mmap_sink::size_type; -using length_type = mio::mmap_sink::size_type; +#include int handle_error(const std::error_code& error) { @@ -60,8 +57,8 @@ int main() // 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("file.txt", - offset_type(0), length_type(mio::map_entire_file), error); + mio::mmap_sink rw_mmap = mio::make_mmap_sink( + "file.txt", 0, mio::map_entire_file, error); if(error) { return handle_error(error); } // Iterate through the mapped region just as if it were any other container, and @@ -70,39 +67,31 @@ int main() b += 10; } - const int answer_index = rw_mmap.size() / 2; - // Or just change one value with the subscript operator. + const int answer_index = rw_mmap.size() / 2; rw_mmap[answer_index] = 42; - // You can also create a mapping using the constructor, which throws a - // std::error_code upon failure. - const auto page_size = mio::page_size(); - try { - mio::mmap_sink mmap3("another/path/to/file", - offset_type(page_size), length_type(page_size)); - // ... - } catch(const std::error_code& error) { - return handle_error(error); - } - // Don't forget to flush changes to disk, which is NOT done by the destructor for // more explicit control of this potentially expensive operation. - rw_mmap.sync(); + rw_mmap.sync(error); + if(error) { handle_error(error); } + // We can then remove the mapping, after which rw_mmap will be in a default - // constructed state. - rw_mmap.close(); + // constructed state, i.e. this has the same effect as if the destructor had been + // invoked. + rw_mmap.unmap(); // Now create the same mapping, but in read-only mode. - mio::mmap_source ro_mmap = mio::make_mmap_source("file.txt", - offset_type(0), length_type(mio::map_entire_file), error); + mio::mmap_source ro_mmap = mio::make_mmap_source( + "file.txt", 0, mio::map_entire_file, error); if(error) { return handle_error(error); } const int the_answer_to_everything = ro_mmap[answer_index]; + assert(the_answer_to_everything == 42); } ``` -`mio::basic_mmap` has move-only semantics, but if multiple copies to the same mapping is required, use `mio::basic_shared_mmap`, which has `std::shared_ptr` semantics, which has the same interface as `mio::basic_mmap`. +`mio::basic_mmap` has move-only semantics, but if multiple copies to the same mapping is required, use `mio::basic_shared_mmap` which has `std::shared_ptr` semantics and has the same interface as `mio::basic_mmap`. ```c++ #include @@ -113,13 +102,15 @@ mio::shared_mmap_source shared_mmap4; shared_mmap4.map("path", offset, size_to_map, error); ``` -It's possible to change the character type of the mmap object, which is useful -for wide strings or reading fixed width integers (although endianness is not -accounted for by mio, so appropriate conversions need be done by the user). +It's possible to define the character type of a byte, though aliases for the most commonly used types are provided: ```c++ -using wmmap_source = mio::basic_mmap_source; -using i32mmap_source = mio::basic_mmap_source; -using u16shared_mmap_sink = mio::basic_shared_mmap_sink; +using mmap_source = basic_mmap_source; +using ummap_source = basic_mmap_source; +``` +But it may be useful to define your own types, say when using the new `std::byte` type in C++17: +```c++ +using mmap_source = mio::basic_mmap_source; +using mmap_sink = mio::basic_mmap_sink; ``` You can query the underlying system's page allocation granularity by invoking `mio::page_size()`, which is located in `mio/page.hpp`. diff --git a/include/mio/detail/basic_mmap.hpp b/include/mio/detail/basic_mmap.hpp index 744882d..7488782 100644 --- a/include/mio/detail/basic_mmap.hpp +++ b/include/mio/detail/basic_mmap.hpp @@ -51,9 +51,9 @@ enum class access_mode using file_handle_type = int; #endif -template struct basic_mmap +template struct basic_mmap { - using value_type = CharT; + using value_type = ByteT; using size_type = int64_t; using reference = value_type&; using const_reference = const value_type&; @@ -67,6 +67,8 @@ template struct basic_mmap using iterator_category = std::random_access_iterator_tag; using handle_type = file_handle_type; + static_assert(sizeof(ByteT) == sizeof(char), "ByteT must be the same size as char."); + private: // Points to the first requested byte, and not to the actual start of the mapping. @@ -82,8 +84,8 @@ private: // Length, in bytes, requested by user, which may not be the length of the full // mapping, and the entire length of the full mapping. - size_type num_bytes_ = 0; - size_type num_mapped_bytes_ = 0; + size_type length_ = 0; + size_type mapped_length_ = 0; // Letting user map a file using both an existing file handle and a path introcudes // some complexity in that we must not close the file handle if user provided it, @@ -103,15 +105,13 @@ public: handle_type file_handle() const noexcept { return file_handle_; } handle_type mapping_handle() const noexcept; - bool is_open() const noexcept; + bool is_open() const noexcept { return file_handle_ != INVALID_HANDLE_VALUE; } bool is_mapped() const noexcept; bool empty() const noexcept { return length() == 0; } - size_type offset() const noexcept { return to_char_size(num_mapped_bytes_ - num_bytes_); } - size_type length() const noexcept { return to_char_size(num_bytes_); } - size_type mapped_length() const noexcept { return to_char_size(num_mapped_bytes_); } - size_type length_in_bytes() const noexcept { return num_bytes_; } - size_type mapped_length_in_bytes() const noexcept { return num_mapped_bytes_; } + size_type offset() const noexcept { return mapped_length_ - length_; } + size_type length() const noexcept { return length_; } + size_type mapped_length() const noexcept { return mapped_length_; } pointer data() noexcept { return data_; } const_pointer data() const noexcept { return data_; } @@ -135,56 +135,33 @@ public: reference operator[](const size_type i) noexcept { return data_[i]; } const_reference operator[](const size_type i) const noexcept { return data_[i]; } - void set_length(const size_type length) noexcept; - void set_offset(const size_type offset) noexcept; - template - void map(String& path, size_type offset, size_type num_bytes, + void map(String& path, size_type offset, size_type length, access_mode mode, std::error_code& error); - void map(handle_type handle, size_type offset, size_type num_bytes, + void map(handle_type handle, size_type offset, size_type length, access_mode mode, std::error_code& error); void unmap(); - void close(); - void sync(std::error_code& error); void swap(basic_mmap& other); private: - pointer get_mapping_start() noexcept; - - void map(const size_type offset, size_type num_bytes, - const access_mode mode, std::error_code& error); - - static size_type to_char_size(const size_type num_bytes) noexcept - { - return num_bytes >> (sizeof(CharT) - 1); - } - - static size_type to_byte_size(const size_type num_chars) noexcept - { - return num_chars << (sizeof(CharT) - 1); - } + pointer get_mapping_start() noexcept { return !data() ? nullptr : data() - offset(); } }; -template -bool operator==(const basic_mmap& a, const basic_mmap& b); - -template -bool operator!=(const basic_mmap& a, const basic_mmap& b); - -template -bool operator<(const basic_mmap& a, const basic_mmap& b); - -template -bool operator<=(const basic_mmap& a, const basic_mmap& b); - -template -bool operator>(const basic_mmap& a, const basic_mmap& b); - -template -bool operator>=(const basic_mmap& a, const basic_mmap& b); +template +bool operator==(const basic_mmap& a, const basic_mmap& b); +template +bool operator!=(const basic_mmap& a, const basic_mmap& b); +template +bool operator<(const basic_mmap& a, const basic_mmap& b); +template +bool operator<=(const basic_mmap& a, const basic_mmap& b); +template +bool operator>(const basic_mmap& a, const basic_mmap& b); +template +bool operator>=(const basic_mmap& a, const basic_mmap& b); } // namespace detail } // namespace mio diff --git a/include/mio/detail/basic_mmap.ipp b/include/mio/detail/basic_mmap.ipp index 68241d1..99ceaa2 100644 --- a/include/mio/detail/basic_mmap.ipp +++ b/include/mio/detail/basic_mmap.ipp @@ -61,8 +61,9 @@ inline std::error_code last_error() noexcept return error; } -template -file_handle_type open_file(const Path& path, const access_mode mode, std::error_code& error) +template +file_handle_type open_file(const String& path, + const access_mode mode, std::error_code& error) { error.clear(); if(detail::empty(path)) @@ -111,19 +112,83 @@ inline int64_t query_file_size(file_handle_type handle, std::error_code& error) #endif } +struct mmap_context +{ + char* data; + int64_t length; + int64_t mapped_length; +#ifdef _WIN32 + file_handle_type file_mapping_handle; +#endif +}; + +mmap_context memory_map(const file_handle_type file_handle, const int64_t offset, + const int64_t length, const access_mode mode, std::error_code& error) +{ + const int64_t aligned_offset = make_offset_page_aligned(offset); + const int64_t length_to_map = offset - aligned_offset + length; +#ifdef _WIN32 + const int64_t max_file_size = offset + length; + const auto file_mapping_handle = ::CreateFileMapping( + file_handle, + 0, + mode == access_mode::read ? PAGE_READONLY : PAGE_READWRITE, + int64_high(max_file_size), + int64_low(max_file_size), + 0); + if(file_mapping_handle == INVALID_HANDLE_VALUE) + { + error = last_error(); + return {}; + } + char* mapping_start = static_cast(::MapViewOfFile( + file_mapping_handle, + mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE, + int64_high(aligned_offset), + int64_low(aligned_offset), + length_to_map)); + if(mapping_start == nullptr) + { + error = last_error(); + return {}; + } +#else + char* mapping_start = static_cast(::mmap( + 0, // Don't give hint as to where to map. + length_to_map, + mode == access_mode::read ? PROT_READ : PROT_WRITE, + MAP_SHARED, + file_handle, + aligned_offset)); + if(mapping_start == MAP_FAILED) + { + error = last_error(); + return {}; + } +#endif + mmap_context ctx; + ctx.data = mapping_start + offset - aligned_offset; + ctx.length = length; + ctx.mapped_length = length_to_map; +#ifdef _WIN32 + ctx.file_mapping_handle = file_mapping_handle; +#endif + return ctx; +} + // -- basic_mmap -- -template -basic_mmap::~basic_mmap() +template +basic_mmap::~basic_mmap() { unmap(); } -template -basic_mmap::basic_mmap(basic_mmap&& other) +template +basic_mmap::basic_mmap(basic_mmap&& other) : data_(std::move(other.data_)) - , num_bytes_(std::move(other.num_bytes_)) - , num_mapped_bytes_(std::move(other.num_mapped_bytes_)) + , length_(std::move(other.length_)) + , mapped_length_(std::move(other.mapped_length_)) , file_handle_(std::move(other.file_handle_)) #ifdef _WIN32 , file_mapping_handle_(std::move(other.file_mapping_handle_)) @@ -131,23 +196,23 @@ basic_mmap::basic_mmap(basic_mmap&& other) , is_handle_internal_(std::move(other.is_handle_internal_)) { other.data_ = nullptr; - other.num_bytes_ = other.num_mapped_bytes_ = 0; + other.length_ = other.mapped_length_ = 0; other.file_handle_ = INVALID_HANDLE_VALUE; #ifdef _WIN32 other.file_mapping_handle_ = INVALID_HANDLE_VALUE; #endif } -template -basic_mmap& basic_mmap::operator=(basic_mmap&& other) +template +basic_mmap& basic_mmap::operator=(basic_mmap&& other) { if(this != &other) { // First the existing mapping needs to be removed. unmap(); data_ = std::move(other.data_); - num_bytes_ = std::move(other.num_bytes_); - num_mapped_bytes_ = std::move(other.num_mapped_bytes_); + length_ = std::move(other.length_); + mapped_length_ = std::move(other.mapped_length_); file_handle_ = std::move(other.file_handle_); #ifdef _WIN32 file_mapping_handle_ = std::move(other.file_mapping_handle_); @@ -157,7 +222,7 @@ basic_mmap& basic_mmap::operator=(basic_mmap&& other) // The moved from basic_mmap's fields need to be reset, because otherwise other's // destructor will unmap the same mapping that was just moved into this. other.data_ = nullptr; - other.num_bytes_ = other.num_mapped_bytes_ = 0; + other.length_ = other.mapped_length_ = 0; other.file_handle_ = INVALID_HANDLE_VALUE; #ifdef _WIN32 other.file_mapping_handle_ = INVALID_HANDLE_VALUE; @@ -167,8 +232,8 @@ basic_mmap& basic_mmap::operator=(basic_mmap&& other) return *this; } -template -typename basic_mmap::handle_type basic_mmap::mapping_handle() const noexcept +template +typename basic_mmap::handle_type basic_mmap::mapping_handle() const noexcept { #ifdef _WIN32 return file_mapping_handle_; @@ -177,10 +242,10 @@ typename basic_mmap::handle_type basic_mmap::mapping_handle() cons #endif } -template +template template -void basic_mmap::map(String& path, size_type offset, - size_type num_bytes, access_mode mode, std::error_code& error) +void basic_mmap::map(String& path, size_type offset, + size_type length, access_mode mode, std::error_code& error) { error.clear(); if(detail::empty(path)) @@ -188,19 +253,19 @@ void basic_mmap::map(String& path, size_type offset, error = std::make_error_code(std::errc::invalid_argument); return; } - if(!is_open()) + const auto handle = open_file(path, mode, error); + if(error) { return; } + map(handle, offset, length, mode, error); + // This MUST be after the call to map, as that sets this to true. + if(!error) { - const auto handle = open_file(path, mode, error); - if(error) { return; } - map(handle, offset, num_bytes, mode, error); - // MUST be after the call to map, as that sets this to true. is_handle_internal_ = true; } } -template -void basic_mmap::map(handle_type handle, size_type offset, - size_type num_bytes, access_mode mode, std::error_code& error) +template +void basic_mmap::map(handle_type handle, size_type offset, + size_type length, access_mode mode, std::error_code& error) { error.clear(); if(handle == INVALID_HANDLE_VALUE) @@ -212,74 +277,38 @@ void basic_mmap::map(handle_type handle, size_type offset, const auto file_size = query_file_size(handle, error); if(error) { return; } - if(num_bytes <= map_entire_file) + if(length <= map_entire_file) { - num_bytes = file_size; + length = file_size; } - else if(offset + num_bytes > file_size) + else if(offset + length > file_size) { error = std::make_error_code(std::errc::invalid_argument); return; } - file_handle_ = handle; - is_handle_internal_ = false; - map(offset, num_bytes, mode, error); -} - -template -void basic_mmap::map(const size_type offset, const size_type num_bytes, - const access_mode mode, std::error_code& error) -{ - const size_type aligned_offset = make_offset_page_aligned(offset); - const size_type num_bytes_to_map = offset - aligned_offset + num_bytes; + const mmap_context ctx = memory_map(handle, offset, length, mode, error); + if(!error) + { + // We must unmap any previous mapping that may have existed prior to this call. + // Note that this must only be invoked after the new mapping has been created + // in order to provide the strong guarantee that, should a mapping fail, the + // `map` function leaves the this instance in a state as though the function + // had never been called. + unmap(); + file_handle_ = handle; + is_handle_internal_ = false; + data_ = reinterpret_cast(ctx.data); + length_ = ctx.length; + mapped_length_ = ctx.mapped_length; #ifdef _WIN32 - const size_type max_file_size = offset + num_bytes; - file_mapping_handle_ = ::CreateFileMapping( - file_handle_, - 0, - mode == access_mode::read ? PAGE_READONLY : PAGE_READWRITE, - int64_high(max_file_size), - int64_low(max_file_size), - 0); - if(file_mapping_handle_ == INVALID_HANDLE_VALUE) - { - error = last_error(); - return; - } - - const pointer mapping_start = static_cast(::MapViewOfFile( - file_mapping_handle_, - mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE, - int64_high(aligned_offset), - int64_low(aligned_offset), - num_bytes_to_map)); - if(mapping_start == nullptr) - { - error = last_error(); - return; - } -#else - const pointer mapping_start = static_cast(::mmap( - 0, // Don't give hint as to where to map. - num_bytes_to_map, - mode == access_mode::read ? PROT_READ : PROT_WRITE, - MAP_SHARED, - file_handle_, - aligned_offset)); - if(mapping_start == MAP_FAILED) - { - error = last_error(); - return; - } + file_mapping_handle_ = ctx.file_mapping_handle; #endif - data_ = mapping_start + offset - aligned_offset; - num_bytes_ = num_bytes; - num_mapped_bytes_ = num_bytes_to_map; + } } -template -void basic_mmap::sync(std::error_code& error) +template +void basic_mmap::sync(std::error_code& error) { error.clear(); if(!is_open()) @@ -291,10 +320,10 @@ void basic_mmap::sync(std::error_code& error) if(data() != nullptr) { #ifdef _WIN32 - if(::FlushViewOfFile(get_mapping_start(), num_mapped_bytes_) == 0 + if(::FlushViewOfFile(get_mapping_start(), mapped_length_) == 0 || ::FlushFileBuffers(file_handle_) == 0) #else - if(::msync(get_mapping_start(), num_mapped_bytes_, MS_SYNC) != 0) + if(::msync(get_mapping_start(), mapped_length_, MS_SYNC) != 0) #endif { error = last_error(); @@ -309,8 +338,8 @@ void basic_mmap::sync(std::error_code& error) #endif } -template -void basic_mmap::unmap() +template +void basic_mmap::unmap() { if(!is_open()) { return; } // TODO do we care about errors here? @@ -322,7 +351,7 @@ void basic_mmap::unmap() file_mapping_handle_ = INVALID_HANDLE_VALUE; } #else - if(data_) { ::munmap(const_cast(get_mapping_start()), num_mapped_bytes_); } + if(data_) { ::munmap(const_cast(get_mapping_start()), mapped_length_); } #endif // If file handle was obtained by our opening it (when map is called with a path, @@ -339,29 +368,15 @@ void basic_mmap::unmap() // Reset fields to their default values. data_ = nullptr; - num_bytes_ = num_mapped_bytes_ = 0; + length_ = mapped_length_ = 0; file_handle_ = INVALID_HANDLE_VALUE; #ifdef _WIN32 file_mapping_handle_ = INVALID_HANDLE_VALUE; #endif } -template -typename basic_mmap::pointer -basic_mmap::get_mapping_start() noexcept -{ - if(!data()) { return nullptr; } - return data() - offset(); -} - -template -bool basic_mmap::is_open() const noexcept -{ - return file_handle_ != INVALID_HANDLE_VALUE; -} - -template -bool basic_mmap::is_mapped() const noexcept +template +bool basic_mmap::is_mapped() const noexcept { #ifdef _WIN32 return file_mapping_handle_ != INVALID_HANDLE_VALUE; @@ -370,24 +385,8 @@ bool basic_mmap::is_mapped() const noexcept #endif } -template -void basic_mmap::set_length(const size_type length) noexcept -{ - if(length > this->length() - offset()) { throw std::invalid_argument(""); } - num_bytes_ = to_byte_size(length); -} - -template -void basic_mmap::set_offset(const size_type offset) noexcept -{ - if(offset >= mapped_length()) { throw std::invalid_argument(""); } - const auto diff = offset - this->offset(); - data_ += diff; - num_bytes_ += -1 * to_byte_size(diff); -} - -template -void basic_mmap::swap(basic_mmap& other) +template +void basic_mmap::swap(basic_mmap& other) { if(this != &other) { @@ -397,47 +396,47 @@ void basic_mmap::swap(basic_mmap& other) #ifdef _WIN32 swap(file_mapping_handle_, other.file_mapping_handle_); #endif - swap(num_bytes_, other.num_bytes_); - swap(num_mapped_bytes_, other.num_mapped_bytes_); + swap(length_, other.length_); + swap(mapped_length_, other.mapped_length_); swap(is_handle_internal_, other.is_handle_internal_); } } -template -bool operator==(const basic_mmap& a, const basic_mmap& b) +template +bool operator==(const basic_mmap& a, const basic_mmap& b) { return a.data() == b.data() && a.size() == b.size(); } -template -bool operator!=(const basic_mmap& a, const basic_mmap& b) +template +bool operator!=(const basic_mmap& a, const basic_mmap& b) { return !(a == b); } -template -bool operator<(const basic_mmap& a, const basic_mmap& b) +template +bool operator<(const basic_mmap& a, const basic_mmap& b) { if(a.data() == b.data()) { return a.size() < b.size(); } return a.data() < b.data(); } -template -bool operator<=(const basic_mmap& a, const basic_mmap& b) +template +bool operator<=(const basic_mmap& a, const basic_mmap& b) { return !(a > b); } -template -bool operator>(const basic_mmap& a, const basic_mmap& b) +template +bool operator>(const basic_mmap& a, const basic_mmap& b) { if(a.data() == b.data()) { return a.size() > b.size(); } return a.data() > b.data(); } -template -bool operator>=(const basic_mmap& a, const basic_mmap& b) +template +bool operator>=(const basic_mmap& a, const basic_mmap& b) { return !(a < b); } diff --git a/include/mio/mmap.hpp b/include/mio/mmap.hpp index 5ad84cc..d7d8879 100644 --- a/include/mio/mmap.hpp +++ b/include/mio/mmap.hpp @@ -31,20 +31,20 @@ namespace mio { // memory mapping. The two possible values are `read` and `write`. using detail::access_mode; -// This value may be provided as the `num_bytes` parameter to the constructor or +// This value may be provided as the `length` parameter to the constructor or // `map`, in which case a memory mapping of the entire file is created. using detail::map_entire_file; template< access_mode AccessMode, - typename CharT + typename ByteT > class basic_mmap { static_assert(AccessMode == access_mode::read || AccessMode == access_mode::write, "AccessMode must be either read or write"); - using impl_type = detail::basic_mmap; + using impl_type = detail::basic_mmap; impl_type impl_; public: @@ -71,36 +71,22 @@ public: basic_mmap() = default; /** - * `path` must be a path to an existing file, which is then used to memory map the - * requested region. Upon failure a `std::error_code` is thrown, detailing the - * cause of the error, and the object remains in an unmapped state. - * - * When specifying `offset`, there is no need to worry about providing - * a value that is aligned with the operating system's page allocation granularity. - * This is adjusted by the implementation such that the first requested byte (as - * returned by `data` or `begin`), so long as `offset` is valid, will be at `offset` - * from the start of the file. + * The same as invoking the `map` function, except any error that may occur while + * establishing the mapping is thrown. */ template - basic_mmap(const String& path, const size_type offset, const size_type num_bytes) + basic_mmap(const String& path, const size_type offset, const size_type length) { std::error_code error; - map(path, offset, num_bytes, error); + map(path, offset, length, error); if(error) { throw error; } } /** - * `handle` must be a valid file handle, which is then used to memory map the - * requested region. Upon failure a `std::error_code` is thrown, detailing the - * cause of the error, and the object remains in an unmapped state. - * - * When specifying `offset`, there is no need to worry about providing - * a value that is aligned with the operating system's page allocation granularity. - * This is adjusted by the implementation such that the first requested byte (as - * returned by `data` or `begin`), so long as `offset` is valid, will be at `offset` - * from the start of the file. + * The same as invoking the `map` function, except any error that may occur while + * establishing the mapping is thrown. */ - basic_mmap(const handle_type handle, const size_type offset, const size_type num_bytes) + basic_mmap(const handle_type handle, const size_type offset, const size_type length) { std::error_code error; map(handle, offset, length, error); @@ -137,9 +123,9 @@ public: /** * `size` and `length` both return the logical length, i.e. the number of bytes - * user requested divided by `sizeof(CharT)`, while `mapped_length` returns the - * actual number of bytes that were mapped, also divided by `sizeof(CharT)`, which - * is a multiple of the underlying operating system's page allocation granularity. + * user requested to be mapped, while `mapped_length` returns the actual number of + * bytes that were mapped which is a multiple of the underlying operating system's + * page allocation granularity. */ size_type size() const noexcept { return impl_.length(); } size_type length() const noexcept { return impl_.length(); } @@ -147,8 +133,7 @@ public: /** * Returns the offset, relative to the file's start, at which the mapping was - * requested to be created, expressed in multiples of `sizeof(CharT)` rather than - * in bytes. + * requested to be created. */ size_type offset() const noexcept { return impl_.offset(); } @@ -200,24 +185,6 @@ public: reference operator[](const size_type i) noexcept { return impl_[i]; } const_reference operator[](const size_type i) const noexcept { return impl_[i]; } - /** - * These functions can be used to alter the _conceptual_ size of the mapping. - * That is, the actual mapped memory region is not affected, just the conceptual - * range of memory on which the accessor methods ('data', 'begin', `operator[]` etc) - * operate. - * - * If `length` is larger than the number of bytes mapped minus the offset, an - * std::invalid_argument exception is thrown. - * - * `length` must be the conceptual length of the mapping, that is, the number of - * bytes mapped divided by `sizeof(CharT)`, i.e. the Container's size. - * - * If `offset` is larger than the number of bytes mapped, an std::invalid_argument - * exception is thrown. - */ - void set_length(const size_type length) noexcept { impl_.set_length(length); } - void set_offset(const size_type offset) noexcept { impl_.set_offset(offset); } - /** * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the * reason is reported via `error` and the object remains in a state as if this @@ -235,17 +202,14 @@ public: * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at * `offset` from the start of the file. * - * `num_bytes` must be the number of bytes to map, regardless of the underlying - * value_type's size. That is, if CharT is a wide char, the value returned by - * the `size` and `length` methods is not the same as this `num_bytes` value (TODO - * this can be confusing). - * If it is `map_entire_file`, a mapping of the entire file is created. + * `length` is the number of bytes to map. It may be `map_entire_file`, in which + * case a mapping of the entire file is created. */ template void map(const String& path, const size_type offset, - const size_type num_bytes, std::error_code& error) + const size_type length, std::error_code& error) { - impl_.map(path, offset, num_bytes, AccessMode, error); + impl_.map(path, offset, length, AccessMode, error); } /** @@ -253,10 +217,9 @@ public: * reason is reported via `error` and the object remains in a state as if this * function hadn't been called. * - * `path`, which must be a path to an existing file, is used to retrieve a file - * handle (which is closed when the object destructs or `unmap` is called), which is - * then used to memory map the requested region. Upon failure, `error` is set to - * indicate the reason and the object remains in an unmapped state. + * `handle`, which must be a valid file handle, which is used to memory map the + * requested region. Upon failure, `error` is set to indicate the reason and the + * object remains in an unmapped state. * * `offset` is the number of bytes, relative to the start of the file, where the * mapping should begin. When specifying it, there is no need to worry about @@ -265,16 +228,13 @@ public: * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at * `offset` from the start of the file. * - * `num_bytes` must be the number of bytes to map, regardless of the underlying - * value_type's size. That is, if CharT is a wide char, the value returned by - * the `size` and `length` methods is not the same as this `num_bytes` value (TODO - * this can be confusing). - * If it is `map_entire_file`, a mapping of the entire file is created. + * `length` is the number of bytes to map. It may be `map_entire_file`, in which + * case a mapping of the entire file is created. */ void map(const handle_type handle, const size_type offset, - const size_type num_bytes, std::error_code& error) + const size_type length, std::error_code& error) { - impl_.map(handle, offset, num_bytes, AccessMode, error); + impl_.map(handle, offset, length, AccessMode, error); } /** @@ -336,15 +296,15 @@ public: * This is the basis for all read-only mmap objects and should be preferred over * directly using basic_mmap. */ -template -using basic_mmap_source = basic_mmap; +template +using basic_mmap_source = basic_mmap; /** * This is the basis for all read-write mmap objects and should be preferred over * directly using basic_mmap. */ -template -using basic_mmap_sink = basic_mmap; +template +using basic_mmap_sink = basic_mmap; /** * These aliases cover the most common use cases, both representing a raw byte stream @@ -361,10 +321,10 @@ template< typename MMap, typename MappingToken > MMap make_mmap(const MappingToken& token, - int64_t offset, int64_t num_bytes, std::error_code& error) + int64_t offset, int64_t length, std::error_code& error) { MMap mmap; - mmap.map(token, offset, num_bytes, error); + mmap.map(token, offset, length, error); return mmap; } @@ -377,9 +337,9 @@ template< */ template mmap_source make_mmap_source(const MappingToken& token, mmap_source::size_type offset, - mmap_source::size_type num_bytes, std::error_code& error) + mmap_source::size_type length, std::error_code& error) { - return make_mmap(token, offset, num_bytes, error); + return make_mmap(token, offset, length, error); } /** @@ -391,9 +351,9 @@ mmap_source make_mmap_source(const MappingToken& token, mmap_source::size_type o */ template mmap_sink make_mmap_sink(const MappingToken& token, mmap_sink::size_type offset, - mmap_sink::size_type num_bytes, std::error_code& error) + mmap_sink::size_type length, std::error_code& error) { - return make_mmap(token, offset, num_bytes, error); + return make_mmap(token, offset, length, error); } } // namespace mio diff --git a/include/mio/shared_mmap.hpp b/include/mio/shared_mmap.hpp index 9414ab7..ba1730c 100644 --- a/include/mio/shared_mmap.hpp +++ b/include/mio/shared_mmap.hpp @@ -40,10 +40,10 @@ namespace mio { */ template< access_mode AccessMode, - typename CharT + typename ByteT > class basic_shared_mmap { - using impl_type = basic_mmap; + using impl_type = basic_mmap; std::shared_ptr pimpl_; public: @@ -92,15 +92,8 @@ public: } /** - * `path` must be a path to an existing file, which is then used to memory map the - * requested region. Upon failure a `std::error_code` is thrown, detailing the - * cause of the error, and the object remains in an unmapped state. - * - * When specifying `offset`, there is no need to worry about providing - * a value that is aligned with the operating system's page allocation granularity. - * This is adjusted by the implementation such that the first requested byte (as - * returned by `data` or `begin`), so long as `offset` is valid, will be at `offset` - * from the start of the file. + * The same as invoking the `map` function, except any error that may occur while + * establishing the mapping is thrown. */ template basic_shared_mmap(const String& path, const size_type offset, const size_type length) @@ -111,15 +104,8 @@ public: } /** - * `handle` must be a valid file handle, which is then used to memory map the - * requested region. Upon failure a `std::error_code` is thrown, detailing the - * cause of the error, and the object remains in an unmapped state. - * - * When specifying `offset`, there is no need to worry about providing - * a value that is aligned with the operating system's page allocation granularity. - * This is adjusted by the implementation such that the first requested byte (as - * returned by `data` or `begin`), so long as `offset` is valid, will be at `offset` - * from the start of the file. + * The same as invoking the `map` function, except any error that may occur while + * establishing the mapping is thrown. */ basic_shared_mmap(const handle_type handle, const size_type offset, const size_type length) { @@ -153,9 +139,9 @@ public: /** * `size` and `length` both return the logical length, i.e. the number of bytes - * user requested divided by `sizeof(CharT)`, while `mapped_length` returns the - * actual number of bytes that were mapped, also divided by `sizeof(CharT)`, which - * is a multiple of the underlying operating system's page allocation granularity. + * user requested to be mapped, while `mapped_length` returns the actual number of + * bytes that were mapped which is a multiple of the underlying operating system's + * page allocation granularity. */ size_type size() const noexcept { return pimpl_->length(); } size_type length() const noexcept { return pimpl_->length(); } @@ -163,8 +149,7 @@ public: /** * Returns the offset, relative to the file's start, at which the mapping was - * requested to be created, expressed in multiples of `sizeof(CharT)` rather than - * in bytes. + * requested to be created. */ size_type offset() const noexcept { return pimpl_->offset(); } @@ -216,24 +201,6 @@ public: reference operator[](const size_type i) noexcept { return (*pimpl_)[i]; } const_reference operator[](const size_type i) const noexcept { return (*pimpl_)[i]; } - /** - * These functions can be used to alter the _conceptual_ size of the mapping. - * That is, the actual mapped memory region is not affected, just the conceptual - * range of memory on which the accessor methods ('data', 'begin', `operator[]` etc) - * operate. - * - * If `length` is larger than the number of bytes mapped minus the offset, an - * std::invalid_argument exception is thrown. - * - * `length` must be the conceptual length of the mapping, that is, the number of - * bytes mapped divided by `sizeof(CharT)`, i.e. the Container's size. - * - * If `offset` is larger than the number of bytes mapped, an std::invalid_argument - * exception is thrown. - */ - void set_length(const size_type length) noexcept { pimpl_->set_length(length); } - void set_offset(const size_type offset) noexcept { pimpl_->set_offset(offset); } - /** * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the * reason is reported via `error` and the object remains in a state as if this @@ -251,11 +218,8 @@ public: * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at * `offset` from the start of the file. * - * `num_bytes` must be the number of bytes to map, regardless of the underlying - * value_type's size. That is, if CharT is a wide char, the value returned by - * the `size` and `length` methods is not the same as this `num_bytes` value (TODO - * this can be confusing). - * If it is `map_entire_file`, a mapping of the entire file is created. + * `length` is the number of bytes to map. It may be `map_entire_file`, in which + * case a mapping of the entire file is created. */ template void map(const String& path, const size_type offset, @@ -269,10 +233,9 @@ public: * reason is reported via `error` and the object remains in a state as if this * function hadn't been called. * - * `path`, which must be a path to an existing file, is used to retrieve a file - * handle (which is closed when the object destructs or `unmap` is called), which is - * then used to memory map the requested region. Upon failure, `error` is set to - * indicate the reason and the object remains in an unmapped state. + * `handle`, which must be a valid file handle, which is used to memory map the + * requested region. Upon failure, `error` is set to indicate the reason and the + * object remains in an unmapped state. * * `offset` is the number of bytes, relative to the start of the file, where the * mapping should begin. When specifying it, there is no need to worry about @@ -281,11 +244,8 @@ public: * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at * `offset` from the start of the file. * - * `num_bytes` must be the number of bytes to map, regardless of the underlying - * value_type's size. That is, if CharT is a wide char, the value returned by - * the `size` and `length` methods is not the same as this `num_bytes` value (TODO - * this can be confusing). - * If it is `map_entire_file`, a mapping of the entire file is created. + * `length` is the number of bytes to map. It may be `map_entire_file`, in which + * case a mapping of the entire file is created. */ void map(const handle_type handle, const size_type offset, const size_type length, std::error_code& error) @@ -367,15 +327,15 @@ private: * This is the basis for all read-only mmap objects and should be preferred over * directly using basic_shared_mmap. */ -template -using basic_shared_mmap_source = basic_shared_mmap; +template +using basic_shared_mmap_source = basic_shared_mmap; /** * This is the basis for all read-write mmap objects and should be preferred over * directly using basic_shared_mmap. */ -template -using basic_shared_mmap_sink = basic_shared_mmap; +template +using basic_shared_mmap_sink = basic_shared_mmap; /** * These aliases cover the most common use cases, both representing a raw byte stream diff --git a/test/example.cpp b/test/example.cpp new file mode 100644 index 0000000..5e39093 --- /dev/null +++ b/test/example.cpp @@ -0,0 +1,49 @@ +#include +#include // for std::error_code +#include // for std::printf +#include + +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(); +} + +int main() +{ + // 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( + "file.txt", 0, mio::map_entire_file, error); + if(error) { return handle_error(error); } + + // 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, which is NOT done by the destructor for + // more explicit control of this potentially expensive operation. + rw_mmap.sync(error); + if(error) { handle_error(error); } + + // We can then remove the mapping, after which rw_mmap will be in a default + // constructed state, i.e. this has the same effect as if the destructor had been + // invoked. + rw_mmap.unmap(); + + // Now create the same mapping, but in read-only mode. + mio::mmap_source ro_mmap = mio::make_mmap_source( + "file.txt", 0, mio::map_entire_file, error); + if(error) { return handle_error(error); } + + const int the_answer_to_everything = ro_mmap[answer_index]; + assert(the_answer_to_everything == 42); +} diff --git a/test/test.cpp b/test/test.cpp index 5008349..4284cd0 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -14,6 +14,12 @@ int handle_error(const std::error_code& error) return error.value(); } +// Just make sure this compiles. +#ifdef CXX17 +# include +using mmap_source = mio::basic_mmap_source; +#endif + int main() { const char* path = "test-file"; @@ -98,15 +104,5 @@ int main() mio::shared_ummap_source _2; } - const auto page_size = mio::page_size(); - { - // Now check if an mmap with a wider char type reports the correct size. - using u16mmap_source = mio::basic_mmap_source; - u16mmap_source wide_mmap = mio::make_mmap(path, 0, page_size, error); - if(error) { return handle_error(error); } - - assert(wide_mmap.size() == page_size / 2); - } - std::printf("all tests passed!\n"); }