Branch data Line data Source code
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 : 0 : class LockedPageAllocator 20 : : { 21 : : public: 22 : 2 : virtual ~LockedPageAllocator() {} 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 [ - + ]: 3 : 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 : 0 : class LockedPoolManager : public LockedPool 219 : : { 220 : : public: 221 : : /** Return the current instance, or create it once */ 222 : 6 : static LockedPoolManager& Instance() 223 : : { 224 : : static std::once_flag init_flag; 225 : 6 : std::call_once(init_flag, LockedPoolManager::CreateInstance); 226 : 6 : return *LockedPoolManager::_instance; 227 : : } 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