• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #ifndef BERBERIS_BASE_MMAP_POOL_H_
18 #define BERBERIS_BASE_MMAP_POOL_H_
19 
20 #include <atomic>
21 #include <cstddef>
22 
23 #include "berberis/base/lock_free_stack.h"
24 #include "berberis/base/logging.h"
25 #include "berberis/base/mmap.h"
26 
27 namespace berberis {
28 
29 // Pool of memory mappings to be used by berberis runtime.
30 // Thread-safe, signal-safe, reentrant.
31 // Singleton interface (no non-static members).
32 template <size_t kBlockSize, size_t kSizeLimit>
33 class MmapPool {
34   static_assert(kBlockSize % kPageSize == 0);
35   static_assert(kBlockSize <= kSizeLimit);
36 
37  public:
Alloc()38   static void* Alloc() {
39     Node* node = g_free_list_.Pop();
40     if (node) {
41       ReleaseFreeListBlock();
42       return node;
43     }
44     return MmapOrDie(kBlockSize);
45   }
46 
Free(void * node)47   static void Free(void* node) {
48     if (AcquireFreeListBlock()) {
49       g_free_list_.Push(static_cast<Node*>(node));
50       return;
51     }
52     return MunmapOrDie(node, kBlockSize);
53   }
54 
55  private:
56   // When a block is freed we reinterpret it as Node, so that
57   // it can be linked to LockFreeStack.
58   struct Node {
59     Node* next;
60   };
61 
ReleaseFreeListBlock()62   static void ReleaseFreeListBlock() {
63     // Memory order release to ensure list pop isn't observed by another
64     // thread after size decrement.
65     g_size_.fetch_sub(kBlockSize, std::memory_order_release);
66     // There must be no more releases than acquires.
67     // On underflow g_size may become close to size_t max.
68     CHECK_LE(g_size_.load(std::memory_order_relaxed), kSizeLimit);
69   }
70 
AcquireFreeListBlock()71   static bool AcquireFreeListBlock() {
72     // We need acquire semantics so that list push isn't observed by another
73     // thread before the size reservation.
74     size_t cmp = g_size_.load(std::memory_order_acquire);
75     while (true) {
76       size_t xch = cmp + kBlockSize;
77       // This guarantees that g_size_ is never set to a value above kSizeLimit.
78       if (xch > kSizeLimit) {
79         return false;
80       }
81       // Updates cmp!
82       if (g_size_.compare_exchange_weak(cmp, xch, std::memory_order_acquire)) {
83         return true;
84       }
85     }
86   }
87 
88   static LockFreeStack<Node> g_free_list_;
89   // Note, the size is not strictly synchronized with g_free_list_ updates,
90   // but we err on the side of a greater size everywhere to make sure kSizeLimit
91   // isn't overflown. We increase the size *before* pushing a node to list, and
92   // decrease size *after* removing a node.
93   static std::atomic_size_t g_size_;
94 
95   MmapPool() = delete;
96 
97   friend class MmapPoolTest;
98 };
99 
100 template <size_t kBlockSize, size_t kSizeLimit>
101 std::atomic_size_t MmapPool<kBlockSize, kSizeLimit>::g_size_{0};
102 
103 template <size_t kBlockSize, size_t kSizeLimit>
104 LockFreeStack<typename MmapPool<kBlockSize, kSizeLimit>::Node>
105     MmapPool<kBlockSize, kSizeLimit>::g_free_list_;
106 
107 }  // namespace berberis
108 
109 #endif  // BERBERIS_BASE_MMAP_POOL_H_
110