lockedpool.h
1 // Copyright (c) 2016-present 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 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 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 224 private: 225 explicit LockedPoolManager(std::unique_ptr<LockedPageAllocator> allocator); 226 227 /** Create a new LockedPoolManager specialized to the OS */ 228 static void CreateInstance(); 229 /** Called when locking fails, warn the user here */ 230 static bool LockingFailed(); 231 232 static LockedPoolManager* _instance; 233 }; 234 235 #endif // BITCOIN_SUPPORT_LOCKEDPOOL_H