memory.hpp 8.16 KB
Newer Older
1
2
3
4
5
#pragma once

#include <string>
#include <unistd.h>

6
7
#include <villas/log.hpp>
#include <villas/memory_manager.hpp>
8
9
10

namespace villas {

11
12
13
14
15
16
/**
 * @brief Basic memory block backed by an address space in the memory graph
 *
 * This is a generic representation of a chunk of memory in the system. It can
 * reside anywhere and represent different types of memory.
 */
17
18
class MemoryBlock {
public:
19
20
21
22
23
	using deallocator_fn = std::function<void(MemoryBlock*)>;

	MemoryBlock(size_t offset, size_t size, MemoryManager::AddressSpaceId addrSpaceId) :
	    offset(offset), size(size), addrSpaceId(addrSpaceId) {}

24
25
26
27
28
29
	MemoryManager::AddressSpaceId getAddrSpaceId() const
	{ return addrSpaceId; }

	size_t getSize() const
	{ return size; }

30
31
	size_t getOffset() const
	{ return offset; }
32

33
34
35
36
protected:
	size_t offset;								///< Offset (or address) inside address space
	size_t size;								///< Size in bytes of this block
	MemoryManager::AddressSpaceId addrSpaceId;	///< Identifier in memory graph
37
38
39
};


40
41
42
43
44
45
46
47
48
49
50
51
52
/**
 * @brief Wrapper for a MemoryBlock to access the underlying memory directly
 *
 * The underlying memory block has to be accessible for the current process,
 * that means it has to be mapped accordingly and registered to the global
 * memory graph.
 * Furthermore, this wrapper can be owning the memory block when initialized
 * with a moved unique pointer. Otherwise, it just stores a reference to the
 * memory block and it's the users responsibility to take care that the memory
 * block is valid.
 */
template<typename T>
class MemoryAccessor {
53
public:
54
	using Type = T;
55

56
57
58
59
	// take ownership of the MemoryBlock
	MemoryAccessor(std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn> mem) :
	    translation(MemoryManager::get().getTranslationFromProcess(mem->getAddrSpaceId())),
	    memoryBlock(std::move(mem)) {}
60

61
62
63
	// just act as an accessor, do not take ownership of MemoryBlock
	MemoryAccessor(const MemoryBlock& mem) :
	    translation(MemoryManager::get().getTranslationFromProcess(mem.getAddrSpaceId())) {}
64

65
66
	MemoryAccessor(const MemoryTranslation& translation) :
	    translation(translation) {}
67

68
69
70
	T& operator*() const {
		return *reinterpret_cast<T*>(translation.getLocalAddr(0));
	}
71

72
73
74
75
	T& operator[](size_t idx) const {
		const size_t offset = sizeof(T) * idx;
		return *reinterpret_cast<T*>(translation.getLocalAddr(offset));
	}
76

77
78
79
	T* operator&() const {
		return reinterpret_cast<T*>(translation.getLocalAddr(0));
	}
80

81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
	T* operator->() const {
		return reinterpret_cast<T*>(translation.getLocalAddr(0));
	}

	const MemoryBlock&
	getMemoryBlock() const
	{ if(not memoryBlock) throw std::bad_alloc(); else return *memoryBlock; }

private:
	/// cached memory translation for fast access
	MemoryTranslation translation;

	/// take the unique pointer in case user wants this class to have ownership
	std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn> memoryBlock;
};


/**
 * @brief Base memory allocator
 *
 * Note the usage of CRTP idiom here to access methods of derived allocators.
 * The concept is explained here at [1].
 *
 * [1] https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
 */
template<typename DerivedAllocator>
class BaseAllocator {
public:
	/// memoryAddrSpaceId: memory that is managed by this allocator
	BaseAllocator(MemoryManager::AddressSpaceId memoryAddrSpaceId) :
	    memoryAddrSpaceId(memoryAddrSpaceId)
	{
		// CRTP
		derivedAlloc = static_cast<DerivedAllocator*>(this);
		logger = loggerGetOrCreate(derivedAlloc->getName());

		// default deallocation callback
		free = [&](MemoryBlock* mem) {
			logger->warn("no free callback defined for addr space {}, not freeing",
			             mem->getAddrSpaceId());
121
122

			removeMemoryBlock(*mem);
123
124
125
		};
	}

126
127
128
129
130
131
	BaseAllocator(std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn> mem) :
	    BaseAllocator(mem->getAddrSpaceId())
	{
		memoryBlock = std::move(mem);
	}

132
133
	virtual std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn>
	allocateBlock(size_t size) = 0;
134
135

	template<typename T>
136
	MemoryAccessor<T>
137
138
	allocate(size_t num)
	{
139
140
141
142
143
144
		if(num == 0) {
			// doesn't make sense to allocate an empty block
			logger->error("Trying to allocate empty memory");
			throw std::bad_alloc();
		}

145
146
		const size_t size = num * sizeof(T);
		auto mem = allocateBlock(size);
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161

		// Check if the allocated memory is really accessible by writing to the
		// allocated memory and reading back. Exponentially increase offset to
		// speed up testing.
		MemoryAccessor<volatile uint8_t> byteAccessor(*mem);
		size_t idx = 0;
		for(int i = 0; idx < mem->getSize(); i++, idx = (1 << i)) {
			auto val = static_cast<uint8_t>(i);
			byteAccessor[idx] = val;
			if(byteAccessor[idx] != val) {
				logger->error("Cannot access allocated memory");
				throw std::bad_alloc();
			}
		}

162
163
		return MemoryAccessor<T>(std::move(mem));
	}
164

165
166
167
protected:
	void insertMemoryBlock(const MemoryBlock& mem)
	{
168
		auto& mm = MemoryManager::get();
169
170
171
172
173
		mm.createMapping(mem.getOffset(), 0, mem.getSize(),
		                 derivedAlloc->getName(),
		                 memoryAddrSpaceId,
		                 mem.getAddrSpaceId());
	}
174

175
176
177
178
179
180
	void removeMemoryBlock(const MemoryBlock& mem)
	{
		// this will also remove any mapping to and from the memory block
		auto& mm = MemoryManager::get();
		mm.removeAddressSpace(mem.getAddrSpaceId());
	}
181

182
183
	MemoryManager::AddressSpaceId getAddrSpaceId() const
	{ return memoryAddrSpaceId; }
184

185
186
187
protected:
	MemoryBlock::deallocator_fn free;
	SpdLogger logger;
188

189
190
191
	// optional, if allocator should own the memory block
	std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn> memoryBlock;

192
193
194
195
private:
	MemoryManager::AddressSpaceId memoryAddrSpaceId;
	DerivedAllocator* derivedAlloc;
};
196

197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212

/**
 * @brief Linear memory allocator
 *
 * This is the simplest kind of allocator. The idea is to keep a pointer at the
 * first memory address of your memory chunk and move it every time an
 * allocation is done. Due to its simplicity, this allocator doesn't allow
 * specific positions of memory to be freed. Usually, all memory is freed
 * together.
 */
class LinearAllocator : public BaseAllocator<LinearAllocator> {
public:
	LinearAllocator(MemoryManager::AddressSpaceId memoryAddrSpaceId,
	                size_t memorySize,
	                size_t internalOffset = 0);

213
214
215
216
217
218
	LinearAllocator(std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn> mem) :
	    LinearAllocator(mem->getAddrSpaceId(), mem->getSize())
	{
		memoryBlock = std::move(mem);
	}

219
220
221
	size_t getAvailableMemory() const
	{ return memorySize - nextFreeAddress; }

222
223
224
	size_t getSize() const
	{ return memorySize; }

225
226
227
228
229
230
231
232
233
234
235
	std::string getName() const;

	std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn>
	allocateBlock(size_t size);

private:
	static constexpr size_t alignBytes = sizeof(uintptr_t);
	static constexpr size_t alignMask = alignBytes - 1;

	size_t getAlignmentPadding(uintptr_t addr) const
	{ return (alignBytes - (addr & alignMask)) & alignMask; }
236
237

private:
238
239
240
	size_t nextFreeAddress;	///< next chunk will be allocated here
	size_t memorySize;		///< total size of managed memory
	size_t internalOffset;	///< offset in address space (usually 0)
241
	size_t allocationCount;	///< Number of individual allocations present
242
243
244
245
246
247
248
249
250
251
252
253
254
255
};


/**
 * @brief Wrapper around mmap() to create villas memory blocks
 *
 * This class simply wraps around mmap() and munmap() to allocate memory in the
 * host memory via the OS.
 */
class HostRam {
public:
	class HostRamAllocator : public BaseAllocator<HostRamAllocator> {
	public:
		HostRamAllocator();
256

257
258
259
260
261
262
263
264
265
266
267
268
269
		std::string getName() const
		{ return "HostRamAlloc"; }

		std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn>
		allocateBlock(size_t size);
	};

	static HostRamAllocator&
	getAllocator()
	{ return allocator; }

private:
	static HostRamAllocator allocator;
270
271
};

272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308

class HostDmaRam {
private:

	static std::string
	getUdmaBufName(int num);

	static std::string
	getUdmaBufBasePath(int num);

	static size_t
	getUdmaBufBufSize(int num);

	static uintptr_t
	getUdmaBufPhysAddr(int num);

public:
	class HostDmaRamAllocator : public LinearAllocator {
	public:
		HostDmaRamAllocator(int num);

		virtual ~HostDmaRamAllocator();

		std::string getName() const
		{ return getUdmaBufName(num); }

	private:
		int num;
	};

	static HostDmaRamAllocator&
	getAllocator(int num = 0);

private:
	static std::map<int, std::unique_ptr<HostDmaRamAllocator>> allocators;
};

309
} // namespace villas