/root/bitcoin/src/support/lockedpool.h
Line | Count | Source |
1 | | // Copyright (c) 2016-2020 The Bitcoin Core developers |
2 | | // Distributed under the MIT software license, see the accompanying |
3 | | // file COPYING or http://www.opensource.org/licenses/mit-license.php. |
4 | | |
5 | | #ifndef BITCOIN_SUPPORT_LOCKEDPOOL_H |
6 | | #define BITCOIN_SUPPORT_LOCKEDPOOL_H |
7 | | |
8 | | #include <cstddef> |
9 | | #include <list> |
10 | | #include <map> |
11 | | #include <memory> |
12 | | #include <mutex> |
13 | | #include <unordered_map> |
14 | | |
15 | | /** |
16 | | * OS-dependent allocation and deallocation of locked/pinned memory pages. |
17 | | * Abstract base class. |
18 | | */ |
19 | | class LockedPageAllocator |
20 | | { |
21 | | public: |
22 | 1 | virtual ~LockedPageAllocator() = default; |
23 | | /** Allocate and lock memory pages. |
24 | | * If len is not a multiple of the system page size, it is rounded up. |
25 | | * Returns nullptr in case of allocation failure. |
26 | | * |
27 | | * If locking the memory pages could not be accomplished it will still |
28 | | * return the memory, however the lockingSuccess flag will be false. |
29 | | * lockingSuccess is undefined if the allocation fails. |
30 | | */ |
31 | | virtual void* AllocateLocked(size_t len, bool *lockingSuccess) = 0; |
32 | | |
33 | | /** Unlock and free memory pages. |
34 | | * Clear the memory before unlocking. |
35 | | */ |
36 | | virtual void FreeLocked(void* addr, size_t len) = 0; |
37 | | |
38 | | /** Get the total limit on the amount of memory that may be locked by this |
39 | | * process, in bytes. Return size_t max if there is no limit or the limit |
40 | | * is unknown. Return 0 if no memory can be locked at all. |
41 | | */ |
42 | | virtual size_t GetLimit() = 0; |
43 | | }; |
44 | | |
45 | | /* An arena manages a contiguous region of memory by dividing it into |
46 | | * chunks. |
47 | | */ |
48 | | class Arena |
49 | | { |
50 | | public: |
51 | | Arena(void *base, size_t size, size_t alignment); |
52 | | virtual ~Arena(); |
53 | | |
54 | | Arena(const Arena& other) = delete; // non construction-copyable |
55 | | Arena& operator=(const Arena&) = delete; // non copyable |
56 | | |
57 | | /** Memory statistics. */ |
58 | | struct Stats |
59 | | { |
60 | | size_t used; |
61 | | size_t free; |
62 | | size_t total; |
63 | | size_t chunks_used; |
64 | | size_t chunks_free; |
65 | | }; |
66 | | |
67 | | /** Allocate size bytes from this arena. |
68 | | * Returns pointer on success, or 0 if memory is full or |
69 | | * the application tried to allocate 0 bytes. |
70 | | */ |
71 | | void* alloc(size_t size); |
72 | | |
73 | | /** Free a previously allocated chunk of memory. |
74 | | * Freeing the zero pointer has no effect. |
75 | | * Raises std::runtime_error in case of error. |
76 | | */ |
77 | | void free(void *ptr); |
78 | | |
79 | | /** Get arena usage statistics */ |
80 | | Stats stats() const; |
81 | | |
82 | | #ifdef ARENA_DEBUG |
83 | | void walk() const; |
84 | | #endif |
85 | | |
86 | | /** Return whether a pointer points inside this arena. |
87 | | * This returns base <= ptr < (base+size) so only use it for (inclusive) |
88 | | * chunk starting addresses. |
89 | | */ |
90 | 1 | bool addressInArena(void *ptr) const { return ptr >= base && ptr < end; } |
91 | | private: |
92 | | typedef std::multimap<size_t, void*> SizeToChunkSortedMap; |
93 | | /** Map to enable O(log(n)) best-fit allocation, as it's sorted by size */ |
94 | | SizeToChunkSortedMap size_to_free_chunk; |
95 | | |
96 | | typedef std::unordered_map<void*, SizeToChunkSortedMap::const_iterator> ChunkToSizeMap; |
97 | | /** Map from begin of free chunk to its node in size_to_free_chunk */ |
98 | | ChunkToSizeMap chunks_free; |
99 | | /** Map from end of free chunk to its node in size_to_free_chunk */ |
100 | | ChunkToSizeMap chunks_free_end; |
101 | | |
102 | | /** Map from begin of used chunk to its size */ |
103 | | std::unordered_map<void*, size_t> chunks_used; |
104 | | |
105 | | /** Base address of arena */ |
106 | | void* base; |
107 | | /** End address of arena */ |
108 | | void* end; |
109 | | /** Minimum chunk alignment */ |
110 | | size_t alignment; |
111 | | }; |
112 | | |
113 | | /** Pool for locked memory chunks. |
114 | | * |
115 | | * To avoid sensitive key data from being swapped to disk, the memory in this pool |
116 | | * is locked/pinned. |
117 | | * |
118 | | * An arena manages a contiguous region of memory. The pool starts out with one arena |
119 | | * but can grow to multiple arenas if the need arises. |
120 | | * |
121 | | * Unlike a normal C heap, the administrative structures are separate from the managed |
122 | | * memory. This has been done as the sizes and bases of objects are not in themselves sensitive |
123 | | * information, as to conserve precious locked memory. In some operating systems |
124 | | * the amount of memory that can be locked is small. |
125 | | */ |
126 | | class LockedPool |
127 | | { |
128 | | public: |
129 | | /** Size of one arena of locked memory. This is a compromise. |
130 | | * Do not set this too low, as managing many arenas will increase |
131 | | * allocation and deallocation overhead. Setting it too high allocates |
132 | | * more locked memory from the OS than strictly necessary. |
133 | | */ |
134 | | static const size_t ARENA_SIZE = 256*1024; |
135 | | /** Chunk alignment. Another compromise. Setting this too high will waste |
136 | | * memory, setting it too low will facilitate fragmentation. |
137 | | */ |
138 | | static const size_t ARENA_ALIGN = 16; |
139 | | |
140 | | /** Callback when allocation succeeds but locking fails. |
141 | | */ |
142 | | typedef bool (*LockingFailed_Callback)(); |
143 | | |
144 | | /** Memory statistics. */ |
145 | | struct Stats |
146 | | { |
147 | | size_t used; |
148 | | size_t free; |
149 | | size_t total; |
150 | | size_t locked; |
151 | | size_t chunks_used; |
152 | | size_t chunks_free; |
153 | | }; |
154 | | |
155 | | /** Create a new LockedPool. This takes ownership of the MemoryPageLocker, |
156 | | * you can only instantiate this with LockedPool(std::move(...)). |
157 | | * |
158 | | * The second argument is an optional callback when locking a newly allocated arena failed. |
159 | | * If this callback is provided and returns false, the allocation fails (hard fail), if |
160 | | * it returns true the allocation proceeds, but it could warn. |
161 | | */ |
162 | | explicit LockedPool(std::unique_ptr<LockedPageAllocator> allocator, LockingFailed_Callback lf_cb_in = nullptr); |
163 | | ~LockedPool(); |
164 | | |
165 | | LockedPool(const LockedPool& other) = delete; // non construction-copyable |
166 | | LockedPool& operator=(const LockedPool&) = delete; // non copyable |
167 | | |
168 | | /** Allocate size bytes from this arena. |
169 | | * Returns pointer on success, or 0 if memory is full or |
170 | | * the application tried to allocate 0 bytes. |
171 | | */ |
172 | | void* alloc(size_t size); |
173 | | |
174 | | /** Free a previously allocated chunk of memory. |
175 | | * Freeing the zero pointer has no effect. |
176 | | * Raises std::runtime_error in case of error. |
177 | | */ |
178 | | void free(void *ptr); |
179 | | |
180 | | /** Get pool usage statistics */ |
181 | | Stats stats() const; |
182 | | private: |
183 | | std::unique_ptr<LockedPageAllocator> allocator; |
184 | | |
185 | | /** Create an arena from locked pages */ |
186 | | class LockedPageArena: public Arena |
187 | | { |
188 | | public: |
189 | | LockedPageArena(LockedPageAllocator *alloc_in, void *base_in, size_t size, size_t align); |
190 | | ~LockedPageArena(); |
191 | | private: |
192 | | void *base; |
193 | | size_t size; |
194 | | LockedPageAllocator *allocator; |
195 | | }; |
196 | | |
197 | | bool new_arena(size_t size, size_t align); |
198 | | |
199 | | std::list<LockedPageArena> arenas; |
200 | | LockingFailed_Callback lf_cb; |
201 | | size_t cumulative_bytes_locked{0}; |
202 | | /** Mutex protects access to this pool's data structures, including arenas. |
203 | | */ |
204 | | mutable std::mutex mutex; |
205 | | }; |
206 | | |
207 | | /** |
208 | | * Singleton class to keep track of locked (ie, non-swappable) memory, for use in |
209 | | * std::allocator templates. |
210 | | * |
211 | | * Some implementations of the STL allocate memory in some constructors (i.e., see |
212 | | * MSVC's vector<T> implementation where it allocates 1 byte of memory in the allocator.) |
213 | | * Due to the unpredictable order of static initializers, we have to make sure the |
214 | | * LockedPoolManager instance exists before any other STL-based objects that use |
215 | | * secure_allocator are created. So instead of having LockedPoolManager also be |
216 | | * static-initialized, it is created on demand. |
217 | | */ |
218 | | class LockedPoolManager : public LockedPool |
219 | | { |
220 | | public: |
221 | | /** Return the current instance, or create it once */ |
222 | | static LockedPoolManager& Instance() |
223 | 1 | { |
224 | 1 | static std::once_flag init_flag; |
225 | 1 | std::call_once(init_flag, LockedPoolManager::CreateInstance); |
226 | 1 | return *LockedPoolManager::_instance; |
227 | 1 | } |
228 | | |
229 | | private: |
230 | | explicit LockedPoolManager(std::unique_ptr<LockedPageAllocator> allocator); |
231 | | |
232 | | /** Create a new LockedPoolManager specialized to the OS */ |
233 | | static void CreateInstance(); |
234 | | /** Called when locking fails, warn the user here */ |
235 | | static bool LockingFailed(); |
236 | | |
237 | | static LockedPoolManager* _instance; |
238 | | }; |
239 | | |
240 | | #endif // BITCOIN_SUPPORT_LOCKEDPOOL_H |