Coverage Report

Created: 2024-10-21 15:10

/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