memory.hpp 9.26 KB
Newer Older
Steffen Vogel's avatar
Steffen Vogel committed
1
2
3
4
/** Memory management.
 *
 * @file
 * @author Daniel Krebs <github@daniel-krebs.net>
Steffen Vogel's avatar
Steffen Vogel committed
5
 * @copyright 2014-2021, Institute for Automation of Complex Power Systems, EONERC
Steffen Vogel's avatar
Steffen Vogel committed
6
7
 * @license GNU General Public License (version 3)
 *
8
 * VILLAScommon
Steffen Vogel's avatar
Steffen Vogel committed
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *********************************************************************************/

#pragma once

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

#include <villas/log.hpp>
#include <villas/memory_manager.hpp>

namespace villas {

/**
 * @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.
 */
class MemoryBlock {
public:
	using deallocator_fn = std::function<void(MemoryBlock*)>;

Steffen Vogel's avatar
Steffen Vogel committed
44
45
46
	using Ptr = std::shared_ptr<MemoryBlock>;

	// cppcheck-suppress passedByValue
Steffen Vogel's avatar
Steffen Vogel committed
47
48
49
50
51
52
53
54
55
56
57
58
59
	MemoryBlock(size_t offset, size_t size, MemoryManager::AddressSpaceId addrSpaceId) :
	    offset(offset), size(size), addrSpaceId(addrSpaceId) {}

	MemoryManager::AddressSpaceId getAddrSpaceId() const
	{ return addrSpaceId; }

	size_t getSize() const
	{ return size; }

	size_t getOffset() const
	{ return offset; }

protected:
Steffen Vogel's avatar
Steffen Vogel committed
60
61
	size_t offset;					///< Offset (or address) inside address space
	size_t size;					///< Size in bytes of this block
Steffen Vogel's avatar
Steffen Vogel committed
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
	MemoryManager::AddressSpaceId addrSpaceId;	///< Identifier in memory graph
};


/**
 * @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 {
public:
	using Type = T;

	// take ownership of the MemoryBlock
	MemoryAccessor(std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn> mem) :
	    translation(MemoryManager::get().getTranslationFromProcess(mem->getAddrSpaceId())),
Steffen Vogel's avatar
Steffen Vogel committed
85
86
	    memoryBlock(std::move(mem))
	{}
Steffen Vogel's avatar
Steffen Vogel committed
87
88

	// just act as an accessor, do not take ownership of MemoryBlock
89
	MemoryAccessor(const MemoryBlock &mem) :
Steffen Vogel's avatar
Steffen Vogel committed
90
91
	    translation(MemoryManager::get().getTranslationFromProcess(mem.getAddrSpaceId()))
	{}
Steffen Vogel's avatar
Steffen Vogel committed
92

93
	MemoryAccessor(const MemoryTranslation &translation) :
Steffen Vogel's avatar
Steffen Vogel committed
94
95
	    translation(translation)
	{}
Steffen Vogel's avatar
Steffen Vogel committed
96

97
	T & operator*() const {
Steffen Vogel's avatar
Steffen Vogel committed
98
99
100
		return *reinterpret_cast<T*>(translation.getLocalAddr(0));
	}

101
	T & operator[](size_t idx) const {
Steffen Vogel's avatar
Steffen Vogel committed
102
103
104
105
106
107
108
109
110
111
112
113
114
115
		const size_t offset = sizeof(T) * idx;
		return *reinterpret_cast<T*>(translation.getLocalAddr(offset));
	}

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

	T* operator->() const {
		return reinterpret_cast<T*>(translation.getLocalAddr(0));
	}

	const MemoryBlock&
	getMemoryBlock() const
Steffen Vogel's avatar
Steffen Vogel committed
116
	{ if (not memoryBlock) throw std::bad_alloc(); else return *memoryBlock; }
Steffen Vogel's avatar
Steffen Vogel committed
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138

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) :
139
		memoryAddrSpaceId(memoryAddrSpaceId)
Steffen Vogel's avatar
Steffen Vogel committed
140
141
142
	{
		// CRTP
		derivedAlloc = static_cast<DerivedAllocator*>(this);
Steffen Vogel's avatar
Steffen Vogel committed
143
144
		std::string loggerName = fmt::format("memory:", derivedAlloc->getName());
		logger = logging.get(loggerName);
Steffen Vogel's avatar
Steffen Vogel committed
145
146
147
148
149
150
151
152
153
154

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

			removeMemoryBlock(*mem);
		};
	}

155
	BaseAllocator(std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn> mem) :
156
		BaseAllocator(mem->getAddrSpaceId())
157
	{
158
		// cppcheck-suppress useInitializationList
159
		memoryBlock = std::move(mem);
160
		derivedAlloc = nullptr;
161
162
	}

Steffen Vogel's avatar
Steffen Vogel committed
163
164
165
166
167
168
169
	virtual std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn>
	allocateBlock(size_t size) = 0;

	template<typename T>
	MemoryAccessor<T>
	allocate(size_t num)
	{
Steffen Vogel's avatar
Steffen Vogel committed
170
		if (num == 0) {
171
172
173
174
175
			// doesn't make sense to allocate an empty block
			logger->error("Trying to allocate empty memory");
			throw std::bad_alloc();
		}

Steffen Vogel's avatar
Steffen Vogel committed
176
177
178
179
180
181
182
183
		const size_t size = num * sizeof(T);
		auto mem = allocateBlock(size);

		// 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;
Steffen Vogel's avatar
Steffen Vogel committed
184
		for (int i = 0; idx < mem->getSize(); i++, idx = (1 << i)) {
Steffen Vogel's avatar
Steffen Vogel committed
185
186
			auto val = static_cast<uint8_t>(i);
			byteAccessor[idx] = val;
Steffen Vogel's avatar
Steffen Vogel committed
187
			if (byteAccessor[idx] != val) {
Steffen Vogel's avatar
Steffen Vogel committed
188
189
190
191
192
193
194
195
196
				logger->error("Cannot access allocated memory");
				throw std::bad_alloc();
			}
		}

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

protected:
197
	void insertMemoryBlock(const MemoryBlock &mem)
Steffen Vogel's avatar
Steffen Vogel committed
198
	{
199
		auto & mm = MemoryManager::get();
Steffen Vogel's avatar
Steffen Vogel committed
200
201
202
203
204
205
		mm.createMapping(mem.getOffset(), 0, mem.getSize(),
		                 derivedAlloc->getName(),
		                 memoryAddrSpaceId,
		                 mem.getAddrSpaceId());
	}

206
	void removeMemoryBlock(const MemoryBlock &mem)
Steffen Vogel's avatar
Steffen Vogel committed
207
208
	{
		// this will also remove any mapping to and from the memory block
209
		auto & mm = MemoryManager::get();
Steffen Vogel's avatar
Steffen Vogel committed
210
211
212
213
214
215
216
		mm.removeAddressSpace(mem.getAddrSpaceId());
	}

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

	MemoryBlock::deallocator_fn free;
217
	Logger logger;
Steffen Vogel's avatar
Steffen Vogel committed
218

219
220
221
	// optional, if allocator should own the memory block
	std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn> memoryBlock;

Steffen Vogel's avatar
Steffen Vogel committed
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
private:
	MemoryManager::AddressSpaceId memoryAddrSpaceId;
	DerivedAllocator* derivedAlloc;
};


/**
 * @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);

243
244
245
246
247
248
	LinearAllocator(std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn> mem) :
	    LinearAllocator(mem->getAddrSpaceId(), mem->getSize())
	{
		memoryBlock = std::move(mem);
	}

Steffen Vogel's avatar
Steffen Vogel committed
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
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
309
310
311
312
313
314
315
316
317
318
319
320
321
	size_t getAvailableMemory() const
	{ return memorySize - nextFreeAddress; }

	size_t getSize() const
	{ return memorySize; }

	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; }

	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)
	size_t allocationCount;	///< Number of individual allocations present
};


/**
 * @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();

		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;
};


class HostDmaRam {
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;
Steffen Vogel's avatar
Steffen Vogel committed
322
323
324
325
326
327
328
329
330
331
332
333

	static std::string
	getUdmaBufName(int num);

	static std::string
	getUdmaBufBasePath(int num);

	static size_t
	getUdmaBufBufSize(int num);

	static uintptr_t
	getUdmaBufPhysAddr(int num);
Steffen Vogel's avatar
Steffen Vogel committed
334
335
};

336
} /* namespace villas */