mirror of
https://github.com/vimpunk/mio.git
synced 2025-12-06 16:57:01 +08:00
added shared_mmap, slight API changes
This commit is contained in:
parent
c52e35b2e8
commit
0d94b74491
@ -41,8 +41,8 @@ enum { map_entire_file = 0 };
|
||||
|
||||
enum class access_mode
|
||||
{
|
||||
read_only,
|
||||
read_write
|
||||
read,
|
||||
write
|
||||
};
|
||||
|
||||
template<typename CharT> 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<typename String>
|
||||
void map(String& path, size_type offset, size_type length,
|
||||
access_mode mode, std::error_code& error);
|
||||
|
||||
@ -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<CharT>& basic_mmap<CharT>::operator=(basic_mmap<CharT>&& other)
|
||||
}
|
||||
|
||||
template<typename CharT>
|
||||
typename basic_mmap<CharT>::handle_type
|
||||
basic_mmap<CharT>::mapping_handle() const noexcept
|
||||
typename basic_mmap<CharT>::handle_type basic_mmap<CharT>::mapping_handle() const noexcept
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return file_mapping_handle_;
|
||||
@ -242,7 +241,7 @@ void basic_mmap<CharT>::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<CharT>::map(const size_type offset, const size_type length,
|
||||
|
||||
const pointer mapping_start = static_cast<pointer>(::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<CharT>::map(const size_type offset, const size_type length,
|
||||
const pointer mapping_start = static_cast<pointer>(::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 CharT>
|
||||
typename basic_mmap<CharT>::pointer
|
||||
basic_mmap<CharT>::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<typename CharT>
|
||||
@ -375,6 +373,22 @@ bool basic_mmap<CharT>::is_mapped() const noexcept
|
||||
#endif
|
||||
}
|
||||
|
||||
template<typename CharT>
|
||||
void basic_mmap<CharT>::set_length(const size_type length) noexcept
|
||||
{
|
||||
if(length > this->length() - offset()) { throw std::invalid_argument(""); }
|
||||
length_ = length;
|
||||
}
|
||||
|
||||
template<typename CharT>
|
||||
void basic_mmap<CharT>::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<typename CharT>
|
||||
void basic_mmap<CharT>::swap(basic_mmap<CharT>& other)
|
||||
{
|
||||
|
||||
@ -24,15 +24,14 @@
|
||||
#include "detail/basic_mmap.hpp"
|
||||
|
||||
#include <system_error>
|
||||
#include <string> // 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<CharT>;
|
||||
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<typename String>
|
||||
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<A == access_mode::read_write>::type
|
||||
typename = typename std::enable_if<A == access_mode::write>::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<A == access_mode::read_write>::type
|
||||
typename = typename std::enable_if<A == access_mode::write>::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<A == access_mode::read_write>::type
|
||||
typename = typename std::enable_if<A == access_mode::write>::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<A == access_mode::read_write>::type
|
||||
typename = typename std::enable_if<A == access_mode::write>::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<typename String>
|
||||
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<A == access_mode::read_write>::type
|
||||
typename = typename std::enable_if<A == access_mode::write>::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<typename CharT>
|
||||
using basic_mmap_source = basic_mmap<access_mode::read_only, CharT>;
|
||||
using basic_mmap_source = basic_mmap<access_mode::read, CharT>;
|
||||
|
||||
/**
|
||||
* This is the basis for all read-write mmap objects and should be preferred over
|
||||
* directly using basic_mmap.
|
||||
*/
|
||||
template<typename CharT>
|
||||
using basic_mmap_sink = basic_mmap<access_mode::read_write, CharT>;
|
||||
using basic_mmap_sink = basic_mmap<access_mode::write, CharT>;
|
||||
|
||||
/**
|
||||
* 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<typename MappingToken>
|
||||
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<mmap_source>(token, offset, length, error);
|
||||
return make_mmap<mmap_source>(token, offset, num_bytes, error);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -364,9 +400,9 @@ mmap_source make_mmap_source(const MappingToken& token, mmap_source::size_type o
|
||||
*/
|
||||
template<typename MappingToken>
|
||||
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<mmap_sink>(token, offset, length, error);
|
||||
return make_mmap<mmap_sink>(token, offset, num_bytes, error);
|
||||
}
|
||||
|
||||
} // namespace mio
|
||||
|
||||
389
include/mio/shared_mmap.hpp
Normal file
389
include/mio/shared_mmap.hpp
Normal file
@ -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 <system_error> // std::error_code
|
||||
#include <memory> // 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<AccessMode, CharT>;
|
||||
std::shared_ptr<impl_type> 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<mmap_type>(std::move(mmap)))
|
||||
{}
|
||||
|
||||
/** Takes ownership of an existing mmap object. */
|
||||
basic_shared_mmap& operator=(mmap_type&& mmap)
|
||||
{
|
||||
pimpl_ = std::make_shared<mmap_type>(std::move(mmap));
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Initializes this object with an already established shared mmap. */
|
||||
basic_shared_mmap(std::shared_ptr<mmap_type> mmap) : pimpl_(mmap) {}
|
||||
|
||||
/** Initializes this object with an already established shared mmap. */
|
||||
basic_shared_mmap& operator=(std::shared_ptr<mmap_type> 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<typename String>
|
||||
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<mmap_type> 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<A == access_mode::write>::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<A == access_mode::write>::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<A == access_mode::write>::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<A == access_mode::write>::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<typename String>
|
||||
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<A == access_mode::write>::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<typename MappingToken>
|
||||
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<mmap_type>(token, offset, length, error);
|
||||
if(error) { return; }
|
||||
pimpl_ = std::make_shared<mmap_type>(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<typename CharT>
|
||||
using basic_shared_mmap_source = basic_shared_mmap<access_mode::read, CharT>;
|
||||
|
||||
/**
|
||||
* This is the basis for all read-write mmap objects and should be preferred over
|
||||
* directly using basic_shared_mmap.
|
||||
*/
|
||||
template<typename CharT>
|
||||
using basic_shared_mmap_sink = basic_shared_mmap<access_mode::write, CharT>;
|
||||
|
||||
/**
|
||||
* 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<char>;
|
||||
using shared_ummap_source = basic_shared_mmap_source<unsigned char>;
|
||||
|
||||
using shared_mmap_sink = basic_shared_mmap_sink<char>;
|
||||
using shared_ummap_sink = basic_shared_mmap_sink<unsigned char>;
|
||||
|
||||
} // namespace mio
|
||||
|
||||
#endif // MIO_SHARED_MMAP_HEADER
|
||||
114
test/test.cpp
114
test/test.cpp
@ -1,4 +1,5 @@
|
||||
#include "../include/mio/mmap.hpp"
|
||||
#include "../include/mio/shared_mmap.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
@ -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<const char*>(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<const char*>(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<char16_t>;
|
||||
u16mmap_source wide_mmap = mio::make_mmap<u16mmap_source>(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<char16_t>;
|
||||
u16mmap_source wide_mmap = mio::make_mmap<u16mmap_source>(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");
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user