-
Notifications
You must be signed in to change notification settings - Fork 7
shared_memory.hpp
Not Enough Standards' shared memory utilities are defined in header <nes/shared_memory.hpp>
Shared memory is a segment of memory that can be shared among multiple processes, using a literal identifier. Once mapped, a shared memory segment can be used as any type of memory, i.e. with pointers.
Shared memory is the lowest level of inter-process communication: a read or write operation on a shared memory segment are the same as on a private memory segment, only the operating system is aware of the fact that this memory is shared between multiple processes.
As any type of memory, shared memory access must be synchronized if multiple process can read and write at the same time, in order to prevent memory corruptions.
On mapping or opening, you may ask the system to mark the mapped part as read-only segment for your process. It is a good practice if you don't need to write on this segment. A write operation on a read-only segment will be cancelled and your process will receive an access violation error (segmentation fault). This is useful to prevent from corruption of a memory segment that is not yours.
nes::shared_memory
is the main class of this header. It represents a shared memory segment. On construction, it will create a new, or open an existing, shared memory object. A shared memory object must be mapped into the virtual space of the process in order to be used.
nes::shared_memory_options
is a bitmask type that specifies additional options for nes::shared_memory
.
Smart pointers that represent a mapped region of a shared memory object.
namespace nes
{
static constexpr const char shared_memory_root[] = /*implementation-defined*/;
enum class shared_memory_options : std::uint32_t
{
none = 0x00,
constant = 0x01
};
constexpr shared_memory_options operator&(shared_memory_options left, shared_memory_options right) noexcept;
constexpr shared_memory_options& operator&=(shared_memory_options& left, shared_memory_options right) noexcept;
constexpr shared_memory_options operator|(shared_memory_options left, shared_memory_options right) noexcept;
constexpr shared_memory_options& operator|=(shared_memory_options& left, shared_memory_options right) noexcept;
constexpr shared_memory_options operator^(shared_memory_options left, shared_memory_options right) noexcept;
constexpr shared_memory_options& operator^=(shared_memory_options& left, shared_memory_options right) noexcept;
constexpr shared_memory_options operator~(shared_memory_options value) noexcept;
template<typename T>
struct map_deleter
{
void operator()(T* ptr) const noexcept;
};
template<typename T>
struct map_deleter<T[]>
{
void operator()(T* ptr) const noexcept;
};
template<typename T>
using unique_map_t = std::unique_ptr<T, map_deleter<T>>;
template<typename T>
using shared_map_t = std::shared_ptr<T>;
template<typename T>
using weak_map_t = std::weak_ptr<T>;
class shared_memory
{
public:
using native_handle_type = /*implementation-defined*/;
public:
constexpr shared_memory() noexcept = default;
explicit shared_memory(const std::string& name, std::uint64_t size);
explicit shared_memory(const std::string& name, shared_memory_options options = shared_memory_option::none);
~shared_memory();
shared_memory(const shared_memory&) = delete;
shared_memory& operator=(const shared_memory&) = delete;
shared_memory(shared_memory&& other) noexcept;
shared_memory& operator=(shared_memory&& other) noexcept;
template<typename T>
unique_map_t<T> map(std::uint64_t offset, shared_memory_options options = (std::is_const<T>::value ? shared_memory_options::constant : shared_memory_options::none)) const;
template<typename T, typename ValueType = typename std::remove_extent<T>::type>
unique_map_t<T> map(std::uint64_t offset, std::size_t count, shared_memory_options options = (std::is_const<ValueType>::value ? shared_memory_options::constant : shared_memory_options::none)) const;
template<typename T>
shared_map_t<T> shared_map(std::uint64_t offset, shared_memory_options options = (std::is_const<T>::value ? shared_memory_options::constant : shared_memory_options::none)) const;
template<typename T, typename ValueType = typename std::remove_extent<T>::type>
shared_map_t<T> shared_map(std::uint64_t offset, std::size_t count, shared_memory_options options = (std::is_const<ValueType>::value ? shared_memory_options::constant : shared_memory_options::none)) const;
native_handle_type native_handle() const noexcept;
};
}
Here is an example in which we share a variable between two processes.
main.cpp
is the main file of the parent process.
other.cpp
is the main file of the child process.
Output
is the standard output of the parent process.
#include <iostream>
#include <nes/shared_memory.hpp>
#include <nes/process.hpp>
int main()
{
//Create a new shared memory object which size is 8 octets
nes::shared_memory memory{"nes_example_shared_memory", sizeof(std::uint64_t)};
//Map 8 octets starting at offset 0
//nes::unique_map_t is a std::unique_ptr with a special deleter
nes::unique_map_t<std::uint64_t> value{memory.map<std::uint64_t>(0)};
//Use the map as any kind of pointer
*value = 42;
//Create the other process
nes::process other{other_path, nes::process_options::grab_stdout};
//Read the entire standard output of the child process. (nes::process_options::grab_stdout must be specified on process creation)
std::cout << other.stdout_stream().rdbuf() << std::endl;
if(other.joinable())
other.join();
//Read value after child execution
std::cout << "The value in shared memory is: " << *value << std::endl;
}
#include <iostream>
#include <nes/shared_memory.hpp>
int main()
{
//Open the shared memory object. nes::shared_memory_option::constant prevent from read-write mapping
nes::shared_memory memory{"nes_example_shared_memory", nes::shared_memory_options::constant};
//Read the value written by the parent process
std::cout << "Value in shared memory is: " << *memory.map<const std::uint64_t>(0) << std::endl;
std::cout << "Modifying value in shared memory to 2^24..." << std::endl;
//Reopen the shared memory object with read-write access
nes::shared_memory new_memory{"nes_example_shared_memory"};
//Change the value of the variable
*new_memory.map<std::uint64_t>(0) = 16777216;
}
Value in shared memory is: 42
Modifying value in shared memory to 2^24...
The value in shared memory is: 16777216