mutable
A Database System for Research and Fast Prototyping
Loading...
Searching...
No Matches
memory.cpp
Go to the documentation of this file.
2
4#include <cerrno>
5#include <climits>
6#include <cstring>
7#include <exception>
8#include <stdexcept>
9
10#if __linux
11#include <sys/mman.h>
12#include <sys/stat.h>
13#include <sys/types.h>
14#include <unistd.h>
15#elif __APPLE__
16#include <fcntl.h>
17#include <sys/mman.h>
18#include <sys/stat.h>
19#include <unistd.h>
20#endif
21
22
23using namespace m;
24using namespace m::memory;
25
26
27/*======================================================================================================================
28 * Allocator
29 *====================================================================================================================*/
30
32{
33#if __linux
34 fd_ = memfd_create("rewire_allocator", MFD_CLOEXEC);
35#elif __APPLE__
36 auto name = std::to_string(getpid());
37 fd_ = shm_open(name.c_str(), O_RDWR | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
38 shm_unlink(name.c_str());
39 ftruncate(fd_, 1UL << 44); // preallocate memory because resizing with `ftruncate()` is not supported on macOS
40#endif
41 if (fd_ == -1)
42 throw std::runtime_error(strerror(errno));
43}
44
46{
47 close(fd_);
48}
49
50Memory Allocator::create_memory(void *addr, std::size_t size, std::size_t offset)
51{
52 return Memory(*this, addr, size, offset);
53}
54
55
56/*======================================================================================================================
57 * AddressSpace
58 *====================================================================================================================*/
59
61{
62 if (size != 0) {
63 auto aligned_size = Ceil_To_Next_Page(size);
64 addr_ = mmap(nullptr, aligned_size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, /* fd= */ -1, /* offset= */ 0);
65 if (addr_ == MAP_FAILED)
66 throw std::runtime_error(strerror(errno));
67 size_ = aligned_size;
68 }
69}
70
72
73
74/*======================================================================================================================
75 * Memory
76 *====================================================================================================================*/
77
78Memory::Memory(Allocator &allocator, void *addr, std::size_t size, std::size_t offset)
79 : allocator_(&allocator)
80 , addr_(addr)
81 , size_(size)
82 , offset_(offset)
83{ }
84
85void Memory::map(std::size_t size, std::size_t offset_src, const AddressSpace &vm, std::size_t offset_dst) const
86{
87 M_insist(size <= this->size(), "size exceeds memory size");
88 M_insist(offset_src < this->size(), "source offset out of bounds");
89 M_insist(Is_Page_Aligned(offset_src), "source offset is not page aligned");
90 M_insist(offset_src + size <= this->size(), "source range out of bounds");
91
92 M_insist(size <= vm.size(), "size exceeds address space");
93 M_insist(offset_dst < vm.size(), "destination offset out of bounds");
94 M_insist(Is_Page_Aligned(offset_dst), "destination offset is not page aligned");
95 M_insist(offset_dst + size <= vm.size(), "destination range out of bounds");
96
97 void *dst_addr = vm.as<uint8_t*>() + offset_dst;
98 void *addr = mmap(dst_addr, size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_FIXED, allocator().fd(),
99 this->offset() + offset_src);
100 if (addr == MAP_FAILED)
101 throw std::runtime_error(strerror(errno));
102 if (addr != dst_addr)
103 throw std::runtime_error("MAP_FIXED failed");
104}
105
107void Memory::dump(std::ostream &out) const
108{
109 out << "Memory at virtual address " << addr() << " of size " << size() << " bytes mapped to offset " << offset()
110 << " of file descriptor " << allocator().fd() << std::endl;
111}
112void Memory::dump() const { dump(std::cerr); }
114
115
116/*======================================================================================================================
117 * LinearAllocator
118 *====================================================================================================================*/
119
121{
122 if (size == 0) return Memory();
123
124 const std::size_t aligned_size = Ceil_To_Next_Page(size);
125 M_insist(aligned_size >= size, "size must be ceiled");
126 M_insist(Is_Page_Aligned(aligned_size), "not page aligned");
127#if __linux
128 if (ftruncate(fd(), offset_ + aligned_size))
129 throw std::runtime_error(strerror(errno));
130#elif __APPLE__
131 /* Nothing to be done.
132 * Memory has been preallocated because resizing with `ftruncate()` is not supported on macOS. */
133#endif
134
135 void *addr = mmap(nullptr, aligned_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd(), offset_);
136 if (addr == MAP_FAILED)
137 throw std::runtime_error(strerror(errno));
138
139 auto mem = create_memory(addr, aligned_size, offset_);
140 allocations_.push_back(offset_);
141 offset_ += aligned_size;
142 return mem;
143}
144
145namespace {
146
148constexpr std::size_t MSB = std::size_t(1UL) << (sizeof(MSB) * CHAR_BIT - 1U);
149
152std::size_t mark_for_deallocation(std::size_t offset) { return offset | MSB; }
153
155bool is_marked_for_deallocation(uintptr_t offset) { return offset & MSB; }
156
158std::size_t unmarked(std::size_t offset) { return offset & ~MSB; }
159
160}
161
163{
164 if (&mem.allocator() != this)
165 throw std::invalid_argument("memory has not been allocated by this allocator");
166
167 /* Find the allocation. */
168 auto it = std::find(allocations_.rbegin(), allocations_.rend(), mem.offset());
169 if (it == allocations_.rend())
170 throw std::invalid_argument("memory has not been allocated by this allocator or has already been deallocated");
171
172 /* Unmap the mapped memory. */
173 munmap(mem.addr(), mem.size());
174
175#if __linux
176 *it = mark_for_deallocation(mem.offset());
177
178 /* Reclaim memory if this is the last allocation. */
179 if (it == allocations_.rbegin()) {
180 /* This is the last allocation. Check how much memory we can reclaim. */
181 std::size_t new_size_of_file;
182 do {
183 new_size_of_file = unmarked(*it);
184 ++it;
185 } while (it != allocations_.rend() and is_marked_for_deallocation(*it));
186 M_insist(it == allocations_.rend() or not is_marked_for_deallocation(*it));
187
188 /* Truncate file to reclaim memory. */
189 if (ftruncate(fd(), new_size_of_file))
190 throw std::runtime_error(strerror(errno));
191 offset_ = new_size_of_file;
192
193 /* Remove reclaimed allocations. */
194 allocations_.resize(std::distance(it, allocations_.rend()));
195 }
196#elif __APPLE__
197 /* Nothing to be done.
198 * Memory has been preallocated because resizing with `ftruncate()` is not supported on macOS. */
199#endif
200}
#define M_insist(...)
Definition: macro.hpp:129
‍mutable namespace
Definition: Backend.hpp:10
std::size_t Is_Page_Aligned(std::size_t n)
Returns true iff n is a integral multiple of the page size (in bytes).
Definition: fn.hpp:700
and
Definition: enum_ops.hpp:12
std::size_t Ceil_To_Next_Page(std::size_t n)
Returns the smallest integral multiple of the page size (in bytes) greater than or equals to n.
Definition: fn.hpp:703
This class represents a reserved address space in virtual memory.
Definition: memory.hpp:45
void * addr_
pointer to the beginning of the virtual address space
Definition: memory.hpp:53
std::size_t size_
size in bytes of the address space
Definition: memory.hpp:54
T as() const
Get a pointer to the beginning of the virtual address space, converted to type T.
Definition: memory.hpp:71
std::size_t size() const
Returns the size in bytes of the virtual address space.
Definition: memory.hpp:67
This is the common interface for all memory allocators that support rewiring.
Definition: memory.hpp:18
int fd_
file descriptor of the underlying memory file
Definition: memory.hpp:22
Memory create_memory(void *addr, std::size_t size, std::size_t offset)
Helper method to inherit the friend ability to construct a Memory object.
Definition: memory.cpp:50
friend struct Memory
Definition: memory.hpp:19
int fd() const
Return the file descriptor of the underlying memory file.
Definition: memory.hpp:29
virtual ~Allocator()
Definition: memory.cpp:45
std::size_t offset_
the offset from the start of the memory file of the next allocation
Definition: memory.hpp:141
std::vector< std::size_t > allocations_
‍stack of allocations; allocations can be marked deallocated for later reclaiming
Definition: memory.hpp:144
void deallocate(Memory &&mem) override
Deallocates a memory object.
Definition: memory.cpp:162
Memory allocate(std::size_t size) override
Creates a new memory object with size bytes of freshly allocated memory.
Definition: memory.cpp:120
Represents a mapping created by a memory::Allocator.
Definition: memory.hpp:78
void * addr() const
Returns a pointer to the beginning of the virtual address space where this allocation is mapped to.
Definition: memory.hpp:109
void dump() const
Definition: memory.cpp:112
std::size_t offset() const
Returns the offset in bytes of this allocation within its allocator.
Definition: memory.hpp:113
Allocator & allocator() const
Returns a reference to the allocator that created this allocation.
Definition: memory.hpp:107
std::size_t size() const
Returns the size in bytes of this allocation.
Definition: memory.hpp:111
void map(std::size_t size, std::size_t offset_src, const AddressSpace &vm, std::size_t offset_dst) const
Map size bytes starting at offset_src into the address space of vm at offset offset_dst.
Definition: memory.cpp:85