From 0d94b7449184a2dfa71f300b0a06921259045aea Mon Sep 17 00:00:00 2001 From: mandreyel Date: Fri, 6 Oct 2017 12:57:31 +0200 Subject: [PATCH] added shared_mmap, slight API changes --- include/mio/detail/basic_mmap.hpp | 13 +- include/mio/detail/basic_mmap.ipp | 36 ++- include/mio/mmap.hpp | 104 +++++--- include/mio/shared_mmap.hpp | 389 ++++++++++++++++++++++++++++++ test/test.cpp | 114 +++++---- 5 files changed, 562 insertions(+), 94 deletions(-) create mode 100644 include/mio/shared_mmap.hpp diff --git a/include/mio/detail/basic_mmap.hpp b/include/mio/detail/basic_mmap.hpp index cd60660..e1e05c2 100644 --- a/include/mio/detail/basic_mmap.hpp +++ b/include/mio/detail/basic_mmap.hpp @@ -41,8 +41,8 @@ enum { map_entire_file = 0 }; enum class access_mode { - read_only, - read_write + read, + write }; template struct basic_mmap @@ -105,9 +105,9 @@ public: bool is_mapped() const noexcept; bool empty() const noexcept { return length() == 0; } - size_type length() const noexcept { return length_ >> (sizeof(CharT) - 1); } - size_type mapped_length() const noexcept - { return mapped_length_ >> (sizeof(CharT) - 1); } + 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_; } @@ -131,6 +131,9 @@ 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 length, access_mode mode, std::error_code& error); diff --git a/include/mio/detail/basic_mmap.ipp b/include/mio/detail/basic_mmap.ipp index 582ac28..a0d4a87 100644 --- a/include/mio/detail/basic_mmap.ipp +++ b/include/mio/detail/basic_mmap.ipp @@ -75,7 +75,7 @@ handle_type open_file(const Path& path, const access_mode mode, std::error_code& } #ifdef _WIN32 const auto handle = ::CreateFile(c_str(path), - mode == access_mode::read_only ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE, + mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, @@ -83,7 +83,7 @@ handle_type open_file(const Path& path, const access_mode mode, std::error_code& 0); #else const auto handle = ::open(c_str(path), - mode == access_mode::read_only ? O_RDONLY : O_RDWR); + mode == access_mode::read ? O_RDONLY : O_RDWR); #endif if(handle == INVALID_HANDLE_VALUE) { @@ -171,8 +171,7 @@ basic_mmap& basic_mmap::operator=(basic_mmap&& other) } template -typename basic_mmap::handle_type -basic_mmap::mapping_handle() const noexcept +typename basic_mmap::handle_type basic_mmap::mapping_handle() const noexcept { #ifdef _WIN32 return file_mapping_handle_; @@ -242,7 +241,7 @@ void basic_mmap::map(const size_type offset, const size_type length, file_mapping_handle_ = ::CreateFileMapping( file_handle_, 0, - mode == access_mode::read_only ? PAGE_READONLY : PAGE_READWRITE, + mode == access_mode::read ? PAGE_READONLY : PAGE_READWRITE, int64_high(max_file_size), int64_low(max_file_size), 0); @@ -254,7 +253,7 @@ void basic_mmap::map(const size_type offset, const size_type length, const pointer mapping_start = static_cast(::MapViewOfFile( file_mapping_handle_, - mode == access_mode::read_only ? FILE_MAP_READ : FILE_MAP_WRITE, + mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE, int64_high(aligned_offset), int64_low(aligned_offset), length_to_map)); @@ -267,8 +266,8 @@ void basic_mmap::map(const size_type offset, const size_type length, const pointer mapping_start = static_cast(::mmap( 0, // Don't give hint as to where to map. length_to_map, - mode == access_mode::read_only ? PROT_READ : PROT_WRITE, - MAP_SHARED, // TODO do we want to share it? + mode == access_mode::read ? PROT_READ : PROT_WRITE, + MAP_SHARED, file_handle_, aligned_offset)); if(mapping_start == MAP_FAILED) @@ -354,9 +353,8 @@ template typename basic_mmap::pointer basic_mmap::get_mapping_start() noexcept { - if(!data_) { return nullptr; } - const auto offset = mapped_length_ - length_; - return data_ - offset; + if(!data()) { return nullptr; } + return data() - offset(); } template @@ -375,6 +373,22 @@ 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(""); } + length_ = 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; + length_ += -1 * diff; +} + template void basic_mmap::swap(basic_mmap& other) { diff --git a/include/mio/mmap.hpp b/include/mio/mmap.hpp index a34c1ef..b1357fe 100644 --- a/include/mio/mmap.hpp +++ b/include/mio/mmap.hpp @@ -24,15 +24,14 @@ #include "detail/basic_mmap.hpp" #include -#include // std::char_traits namespace mio { // This is used by basic_mmap to determine whether to create a read-only or a read-write -// memory mapping. The two possible values are `read_only` and `read_write`. +// memory mapping. The two possible values are `read` and `write`. using detail::access_mode; -// This value may be provided as the `length` parameter to the constructor or +// This value may be provided as the `num_bytes` parameter to the constructor or // `map`, in which case a memory mapping of the entire file is created. using detail::map_entire_file; @@ -41,9 +40,9 @@ template< typename CharT > class basic_mmap { - static_assert(AccessMode == access_mode::read_only - || AccessMode == access_mode::read_write, - "AccessMode must be either read_only or read_write"); + static_assert(AccessMode == access_mode::read + || AccessMode == access_mode::write, + "AccessMode must be either read or write"); using impl_type = detail::basic_mmap; impl_type impl_; @@ -72,7 +71,7 @@ public: basic_mmap() = default; /** - * `handle` must be a valid file handle, which is then used to memory map the + * `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. * @@ -83,10 +82,10 @@ public: * from the start of the file. */ template - basic_mmap(const String& path, const size_type offset, const size_type length) + basic_mmap(const String& path, const size_type offset, const size_type num_bytes) { std::error_code error; - map(path, offset, length, error); + map(path, offset, num_bytes, error); if(error) { throw error; } } @@ -101,7 +100,7 @@ public: * returned by `data` or `begin`), so long as `offset` is valid, will be at `offset` * from the start of the file. */ - basic_mmap(const handle_type handle, const size_type offset, const size_type length) + basic_mmap(const handle_type handle, const size_type offset, const size_type num_bytes) { std::error_code error; map(handle, offset, length, error); @@ -142,9 +141,16 @@ public: * 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. */ - size_type size() const noexcept { return impl_.length(); } - size_type length() const noexcept { return impl_.length(); } - size_type mapped_length() const noexcept { return impl_.mapped_length(); } + size_type size() const noexcept { return char_size(impl_.length()); } + size_type length() const noexcept { return char_size(impl_.length()); } + size_type mapped_length() const noexcept { return char_size(impl_.mapped_length()); } + + /** + * 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. + */ + size_type offset() const noexcept { return char_size(impl_.offset()); } /** * Returns a pointer to the first requested byte, or `nullptr` if no memory mapping @@ -152,7 +158,7 @@ public: */ template< access_mode A = AccessMode, - typename = typename std::enable_if::type + typename = typename std::enable_if::type > pointer data() noexcept { return impl_.data(); } const_pointer data() const noexcept { return impl_.data(); } @@ -167,21 +173,21 @@ public: /** Returns an iterator one past the last requested byte. */ template< access_mode A = AccessMode, - typename = typename std::enable_if::type + typename = typename std::enable_if::type > iterator end() noexcept { return impl_.end(); } const_iterator end() const noexcept { return impl_.end(); } const_iterator cend() const noexcept { return impl_.cend(); } template< access_mode A = AccessMode, - typename = typename std::enable_if::type + typename = typename std::enable_if::type > reverse_iterator rbegin() noexcept { return impl_.rbegin(); } const_reverse_iterator rbegin() const noexcept { return impl_.rbegin(); } const_reverse_iterator crbegin() const noexcept { return impl_.crbegin(); } template< access_mode A = AccessMode, - typename = typename std::enable_if::type + typename = typename std::enable_if::type > reverse_iterator rend() noexcept { return impl_.rend(); } const_reverse_iterator rend() const noexcept { return impl_.rend(); } const_reverse_iterator crend() const noexcept { return impl_.crend(); } @@ -194,6 +200,24 @@ 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(byte_size(length)); } + void set_offset(const size_type offset) noexcept { impl_.set_offset(byte_size(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 @@ -210,17 +234,17 @@ public: * returned by `data` or `begin`), so long as `offset` is valid, will be at `offset` * from the start of the file. * - * `length` must be the number of bytes to map, regardless of the underlying + * `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 `length` value (TODO + * 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. */ template void map(const String& path, const size_type offset, - const size_type length, std::error_code& error) + const size_type num_bytes, std::error_code& error) { - impl_.map(path, offset, length, AccessMode, error); + impl_.map(path, offset, num_bytes, AccessMode, error); } /** @@ -238,16 +262,16 @@ public: * returned by `data` or `begin`), so long as `offset` is valid, will be at `offset` * from the start of the file. * - * `length` must be the number of bytes to map, regardless of the underlying + * `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 `length` value (TODO + * 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. */ void map(const handle_type handle, const size_type offset, - const size_type length, std::error_code& error) + const size_type num_bytes, std::error_code& error) { - impl_.map(handle, offset, length, AccessMode, error); + impl_.map(handle, offset, num_bytes, AccessMode, error); } /** @@ -266,7 +290,7 @@ public: /** Flushes the memory mapped page to disk. Errors are reported via `error`. */ template< access_mode A = AccessMode, - typename = typename std::enable_if::type + typename = typename std::enable_if::type > void sync(std::error_code& error) { impl_.sync(error); } /** @@ -303,6 +327,18 @@ public: { return a.impl_ >= b.impl_; } + +private: + + static size_type char_size(const size_type num_bytes) noexcept + { + return num_bytes >> (sizeof(CharT) - 1); + } + + static size_type byte_size(const size_type num_bytes) noexcept + { + return num_bytes << (sizeof(CharT) - 1); + } }; /** @@ -310,14 +346,14 @@ public: * directly using basic_mmap. */ template -using basic_mmap_source = basic_mmap; +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; +using basic_mmap_sink = basic_mmap; /** * These aliases cover the most common use cases, both representing a raw byte stream @@ -334,10 +370,10 @@ template< typename MMap, typename MappingToken > MMap make_mmap(const MappingToken& token, - int64_t offset, int64_t length, std::error_code& error) + int64_t offset, int64_t num_bytes, std::error_code& error) { MMap mmap; - mmap.map(token, offset, length, error); + mmap.map(token, offset, num_bytes, error); return mmap; } @@ -350,9 +386,9 @@ template< */ template mmap_source make_mmap_source(const MappingToken& token, mmap_source::size_type offset, - mmap_source::size_type length, std::error_code& error) + mmap_source::size_type num_bytes, std::error_code& error) { - return make_mmap(token, offset, length, error); + return make_mmap(token, offset, num_bytes, error); } /** @@ -364,9 +400,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 length, std::error_code& error) + mmap_sink::size_type num_bytes, std::error_code& error) { - return make_mmap(token, offset, length, error); + return make_mmap(token, offset, num_bytes, error); } } // namespace mio diff --git a/include/mio/shared_mmap.hpp b/include/mio/shared_mmap.hpp new file mode 100644 index 0000000..ce88201 --- /dev/null +++ b/include/mio/shared_mmap.hpp @@ -0,0 +1,389 @@ +/* Copyright 2017 https://github.com/mandreyel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included in all copies + * or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef MIO_SHARED_MMAP_HEADER +#define MIO_SHARED_MMAP_HEADER + +#include "mmap.hpp" + +#include // std::error_code +#include // std::shared_ptr + +namespace mio { + +/** + * Exposes (nearly) the same interface as `basic_mmap`, but endowes it with + * `std::shared_ptr` semantics. + * + * This is not the default behaviour of `basic_mmap` to avoid allocating on the heap if + * shared semantics are not required. + * + * If basic_shared_mmap is not a valid mappig (i.e. owns no `basic_mmap`), using its + * methods is undefined behaviour. + */ +template< + access_mode AccessMode, + typename CharT +> class basic_shared_mmap +{ + using impl_type = basic_mmap; + std::shared_ptr pimpl_; + +public: + + using value_type = typename impl_type::value_type; + using size_type = typename impl_type::size_type; + using reference = typename impl_type::reference; + using const_reference = typename impl_type::const_reference; + using pointer = typename impl_type::pointer; + using const_pointer = typename impl_type::const_pointer; + using difference_type = typename impl_type::difference_type; + using iterator = typename impl_type::iterator; + using const_iterator = typename impl_type::const_iterator; + using reverse_iterator = typename impl_type::reverse_iterator; + using const_reverse_iterator = typename impl_type::const_reverse_iterator; + using iterator_category = typename impl_type::iterator_category; + using handle_type = typename impl_type::handle_type; + using mmap_type = impl_type; + + basic_shared_mmap() = default; + basic_shared_mmap(const basic_shared_mmap&) = default; + basic_shared_mmap& operator=(const basic_shared_mmap&) = default; + basic_shared_mmap(basic_shared_mmap&&) = default; + basic_shared_mmap& operator=(basic_shared_mmap&&) = default; + + /** Takes ownership of an existing mmap object. */ + basic_shared_mmap(mmap_type&& mmap) + : pimpl_(std::make_shared(std::move(mmap))) + {} + + /** Takes ownership of an existing mmap object. */ + basic_shared_mmap& operator=(mmap_type&& mmap) + { + pimpl_ = std::make_shared(std::move(mmap)); + return *this; + } + + /** Initializes this object with an already established shared mmap. */ + basic_shared_mmap(std::shared_ptr mmap) : pimpl_(mmap) {} + + /** Initializes this object with an already established shared mmap. */ + basic_shared_mmap& operator=(std::shared_ptr mmap) + { + pimpl_ = mmap; + return *this; + } + + /** + * `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. + */ + template + basic_shared_mmap(const String& path, const size_type offset, const size_type length) + { + std::error_code 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. + */ + basic_shared_mmap(const handle_type handle, const size_type offset, const size_type length) + { + std::error_code error; + map(handle, offset, length, error); + if(error) { throw error; } + } + + ~basic_shared_mmap() = default; + + /** Returns the underlying `std::shared_ptr` instance that holds the mmap. */ + std::shared_ptr get_shared_ptr() { return pimpl_; } + + /** + * On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows, + * however, a mapped region of a file gets its own handle, which is returned by + * 'mapping_handle'. + */ + handle_type file_handle() const noexcept { return pimpl_->file_handle(); } + handle_type mapping_handle() const noexcept { return pimpl_->mapping_handle(); } + + /** Returns whether a valid memory mapping has been created. */ + bool is_open() const noexcept { return pimpl_->is_open(); } + + /** + * Returns if the length that was mapped was 0, in which case no mapping was + * established, i.e. `is_open` returns false. This function is provided so that + * this class has some Container semantics. + */ + bool empty() const noexcept { return pimpl_->empty(); } + + /** + * `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. + */ + size_type size() const noexcept { return pimpl_->length(); } + size_type length() const noexcept { return pimpl_->length(); } + size_type mapped_length() const noexcept { return pimpl_->mapped_length(); } + + /** + * 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. + */ + size_type offset() const noexcept { return pimpl_->offset(); } + + /** + * Returns a pointer to the first requested byte, or `nullptr` if no memory mapping + * exists. + */ + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > pointer data() noexcept { return pimpl_->data(); } + const_pointer data() const noexcept { return pimpl_->data(); } + + /** + * Returns an iterator to the first requested byte, if a valid memory mapping + * exists, otherwise this function call is equivalent to invoking `end`. + */ + iterator begin() noexcept { return pimpl_->begin(); } + const_iterator begin() const noexcept { return pimpl_->begin(); } + const_iterator cbegin() const noexcept { return pimpl_->cbegin(); } + + /** Returns an iterator one past the last requested byte. */ + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > iterator end() noexcept { return pimpl_->end(); } + const_iterator end() const noexcept { return pimpl_->end(); } + const_iterator cend() const noexcept { return pimpl_->cend(); } + + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > reverse_iterator rbegin() noexcept { return pimpl_->rbegin(); } + const_reverse_iterator rbegin() const noexcept { return pimpl_->rbegin(); } + const_reverse_iterator crbegin() const noexcept { return pimpl_->crbegin(); } + + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > reverse_iterator rend() noexcept { return pimpl_->rend(); } + const_reverse_iterator rend() const noexcept { return pimpl_->rend(); } + const_reverse_iterator crend() const noexcept { return pimpl_->crend(); } + + /** + * Returns a reference to the `i`th byte from the first requested byte (as returned + * by `data`). If this is invoked when no valid memory mapping has been created + * prior to this call, undefined behaviour ensues. + */ + 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 + * 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. + * + * 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. + * + * `length` 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 `length` value (TODO + * this can be confusing). + * If it is `map_entire_file`, a mapping of the entire file is created. + */ + template + void map(const String& path, const size_type offset, + const size_type length, std::error_code& error) + { + map_impl(path, offset, length, error); + } + + /** + * 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 + * function hadn't been called. + * + * `handle` must be a valid file handle, 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. + * + * 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. + * + * `length` 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 `length` value (TODO + * this can be confusing). + * If it is `map_entire_file`, 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) + { + map_impl(handle, offset, length, error); + } + + /** + * If a valid memory mapping has been created prior to this call, this call + * instructs the kernel to unmap the memory region and disassociate this object + * from the file. + * + * The file handle associated with the file that is mapped is only closed if the + * mapping was created using a file path. If, on the other hand, an existing + * file handle was used to create the mapping, the file handle is not closed. + */ + void unmap() { if(pimpl_) pimpl_->unmap(); } + + void swap(basic_shared_mmap& other) { pimpl_.swap(other.pimpl_); } + + /** Flushes the memory mapped page to disk. Errors are reported via `error`. */ + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > void sync(std::error_code& error) { if(pimpl_) pimpl_->sync(error); } + + /** All operators compare the underlying `basic_mmap`'s addresses. */ + + friend bool operator==(const basic_shared_mmap& a, const basic_shared_mmap& b) + { + return a.pimpl_ == b.pimpl_; + } + + friend bool operator!=(const basic_shared_mmap& a, const basic_shared_mmap& b) + { + return !(a == b); + } + + friend bool operator<(const basic_shared_mmap& a, const basic_shared_mmap& b) + { + return a.pimpl_ < b.pimpl_; + } + + friend bool operator<=(const basic_shared_mmap& a, const basic_shared_mmap& b) + { + return a.pimpl_ <= b.pimpl_; + } + + friend bool operator>(const basic_shared_mmap& a, const basic_shared_mmap& b) + { + return a.pimpl_ > b.pimpl_; + } + + friend bool operator>=(const basic_shared_mmap& a, const basic_shared_mmap& b) + { + return a.pimpl_ >= b.pimpl_; + } + +private: + + template + void map_impl(const MappingToken& token, const size_type offset, + const size_type length, std::error_code& error) + { + if(!pimpl_) + { + mmap_type mmap = make_mmap(token, offset, length, error); + if(error) { return; } + pimpl_ = std::make_shared(std::move(mmap)); + } + else + { + pimpl_->map(token, offset, length, AccessMode, error); + } + } +}; + +/** + * 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; + +/** + * 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; + +/** + * These aliases cover the most common use cases, both representing a raw byte stream + * (either with a char or an unsigned char/uint8_t). + */ +using shared_mmap_source = basic_shared_mmap_source; +using shared_ummap_source = basic_shared_mmap_source; + +using shared_mmap_sink = basic_shared_mmap_sink; +using shared_ummap_sink = basic_shared_mmap_sink; + +} // namespace mio + +#endif // MIO_SHARED_MMAP_HEADER diff --git a/test/test.cpp b/test/test.cpp index 747008f..5008349 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -1,4 +1,5 @@ #include "../include/mio/mmap.hpp" +#include "../include/mio/shared_mmap.hpp" #include #include @@ -16,71 +17,96 @@ int handle_error(const std::error_code& error) int main() { const char* path = "test-file"; - + std::error_code error; // Fill buffer, then write it to file. std::string buffer(0x4000 - 250, 'M'); std::ofstream file(path); file << buffer; file.close(); - // Map the region of the file to which buffer was written. - std::error_code error; - mio::mmap_source file_view = mio::make_mmap_source( - path, 0, mio::map_entire_file, error); - if(error) { return handle_error(error); } - - assert(file_view.is_open()); - assert(file_view.size() == buffer.size()); - - // Then verify that mmap's bytes correspond to that of buffer. - for(auto i = 0; i < buffer.size(); ++i) { - if(file_view[i] != buffer[i]) + // Map the region of the file to which buffer was written. + mio::mmap_source file_view = mio::make_mmap_source( + path, 0, mio::map_entire_file, error); + if(error) { return handle_error(error); } + + assert(file_view.is_open()); + assert(file_view.size() == buffer.size()); + + // Then verify that mmap's bytes correspond to that of buffer. + for(auto i = 0; i < buffer.size(); ++i) { - std::printf("%ith byte mismatch: expected(%i) <> actual(%i)", - i, buffer[i], file_view[i]); - assert(0); + if(file_view[i] != buffer[i]) + { + std::printf("%ith byte mismatch: expected(%i) <> actual(%i)", + i, buffer[i], file_view[i]); + assert(0); + } + } + + // Turn file_view into a shared mmap. + mio::shared_mmap_source shared_file_view(std::move(file_view)); + + assert(!file_view.is_open()); + assert(shared_file_view.is_open()); + assert(shared_file_view.size() == buffer.size()); + + // Then verify that mmap's bytes correspond to that of buffer. + for(auto i = 0; i < buffer.size(); ++i) + { + if(shared_file_view[i] != buffer[i]) + { + std::printf("%ith byte mismatch: expected(%i) <> actual(%i)", + i, buffer[i], shared_file_view[i]); + assert(0); + } } } + { #define CHECK_INVALID_MMAP(m) do { \ - assert(error); \ - assert(m.empty()); \ - assert(!m.is_open()); \ - error.clear(); } while(0) + assert(error); \ + assert(m.empty()); \ + assert(!m.is_open()); \ + error.clear(); } while(0) - mio::mmap_source m; + mio::mmap_source m; - // See if mapping an invalid file results in an error. - m = mio::make_mmap_source("garbage-that-hopefully-doesnt-exist", 0, 0, error); - CHECK_INVALID_MMAP(m); + // See if mapping an invalid file results in an error. + m = mio::make_mmap_source("garbage-that-hopefully-doesnt-exist", 0, 0, error); + CHECK_INVALID_MMAP(m); - // Empty path? - m = mio::make_mmap_source(static_cast(0), 0, 0, error); - CHECK_INVALID_MMAP(m); - m = mio::make_mmap_source(std::string(), 0, 0, error); - CHECK_INVALID_MMAP(m); + // Empty path? + m = mio::make_mmap_source(static_cast(0), 0, 0, error); + CHECK_INVALID_MMAP(m); + m = mio::make_mmap_source(std::string(), 0, 0, error); + CHECK_INVALID_MMAP(m); - // Invalid handle? - m = mio::make_mmap_source( - INVALID_HANDLE_VALUE/*Psst... This is an implementation detail!*/, 0, 0, error); - CHECK_INVALID_MMAP(m); + // Invalid handle? + m = mio::make_mmap_source( + INVALID_HANDLE_VALUE/*Psst... This is an implementation detail!*/, 0, 0, error); + CHECK_INVALID_MMAP(m); - // Invalid offset? - m = mio::make_mmap_source(path, 100 * buffer.size(), buffer.size(), error); - CHECK_INVALID_MMAP(m); + // Invalid offset? + m = mio::make_mmap_source(path, 100 * buffer.size(), buffer.size(), error); + CHECK_INVALID_MMAP(m); + } - // Just making sure custom types compile. - mio::ummap_source ummap; + { + // Make sure custom types compile. + mio::ummap_source _1; + 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); } - // 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); + assert(wide_mmap.size() == page_size / 2); + } std::printf("all tests passed!\n"); }