lockedpool.h raw

   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
 236