1 /*
2  * Copyright (c) 2023 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #ifndef UTIL_SLAB_HPP
17 #define UTIL_SLAB_HPP
18 
19 #include <new>
20 #include <vector>
21 #include <mutex>
22 #ifdef FFRT_BBOX_ENABLE
23 #include <unordered_set>
24 #endif
25 #include <sys/mman.h>
26 #include "dfx/log/ffrt_log_api.h"
27 #include "spmc_queue.h"
28 #include "sync/sync.h"
29 
30 namespace ffrt {
31 const std::size_t BatchAllocSize = 32 * 1024;
32 #ifdef FFRT_BBOX_ENABLE
33 constexpr uint32_t ALLOCATOR_DESTRUCT_TIMESOUT = 1000;
34 #endif
35 
36 #ifndef FFRT_ALLOCATOR_MMAP_SIZE
37 #define FFRT_ALLOCATOR_MMAP_SIZE (8 * 1024 * 1024)
38 #endif
39 
40 template <typename T, size_t MmapSz = BatchAllocSize>
41 class SimpleAllocator {
42 public:
43     SimpleAllocator(const SimpleAllocator&) = delete;
44     SimpleAllocator(SimpleAllocator&&) = delete;
45     SimpleAllocator& operator=(const SimpleAllocator&) = delete;
46     SimpleAllocator& operator=(SimpleAllocator&&) = delete;
47     fast_mutex lock;
48 
Instance(std::size_t size = sizeof(T))49     static SimpleAllocator<T>* Instance(std::size_t size = sizeof(T))
50     {
51         static SimpleAllocator<T> ins(size);
52         return &ins;
53     }
54 
55     // NOTE: call constructor after AllocMem
AllocMem()56     static T* AllocMem()
57     {
58         return Instance()->Alloc();
59     }
60 
61     // NOTE: call destructor before FreeMem
FreeMem(T* t)62     static void FreeMem(T* t)
63     {
64         t->~T();
65         // unlock()内部lck记录锁的状态为非持有状态,析构时访问状态变量为非持有状态,则不访问实际持有的mutex
66         // return之前的lck析构不产生UAF问题,因为return之前随着root析构,锁的内存被释放
67         Instance()->free(t);
68     }
69 
70     // only used for BBOX
getUnfreedMem()71     static std::vector<void *> getUnfreedMem()
72     {
73         return Instance()->getUnfreed();
74     }
75 
LockMem()76     static void LockMem()
77     {
78         return Instance()->SimpleAllocatorLock();
79     }
80 
UnlockMem()81     static void UnlockMem()
82     {
83         return Instance()->SimpleAllocatorUnLock();
84     }
85 private:
86     SpmcQueue primaryCache;
87 #ifdef FFRT_BBOX_ENABLE
88     std::unordered_set<T*> secondaryCache;
89 #endif
90     std::size_t TSize;
91     T* basePtr = nullptr;
92     std::size_t count = 0;
93 
getUnfreed()94     std::vector<void *> getUnfreed()
95     {
96         std::vector<void *> ret;
97 #ifdef FFRT_BBOX_ENABLE
98         ret.reserve(MmapSz / TSize + secondaryCache.size());
99         char* p = reinterpret_cast<char*>(basePtr);
100         for (std::size_t i = 0; i + TSize <= MmapSz; i += TSize) {
101             if (basePtr != nullptr &&
102                 primaryCache.FindElement(reinterpret_cast<void *>(p + i)) == false) {
103                 ret.push_back(reinterpret_cast<void *>(p + i));
104             }
105         }
106         for (auto ite = secondaryCache.cbegin(); ite != secondaryCache.cend(); ite++) {
107             ret.push_back(reinterpret_cast<void *>(*ite));
108         }
109 #endif
110         return ret;
111     }
112 
SimpleAllocatorLock()113     void SimpleAllocatorLock()
114     {
115         lock.lock();
116     }
117 
SimpleAllocatorUnLock()118     void SimpleAllocatorUnLock()
119     {
120         lock.unlock();
121     }
122 
init()123     void init()
124     {
125         lock.lock();
126         if (basePtr) {
127             lock.unlock();
128             return;
129         }
130 
131         char* p = reinterpret_cast<char*>(operator new(MmapSz));
132         count = MmapSz / TSize;
133         primaryCache.Init(count);
134         for (std::size_t i = 0; i + TSize <= MmapSz; i += TSize) {
135             primaryCache.PushTail(p + i);
136         }
137         basePtr = reinterpret_cast<T*>(p);
138         lock.unlock();
139     }
140 
Alloc()141     T* Alloc()
142     {
143         // 未初始化
144         if (primaryCache.GetCapacity() == 0) {
145             init();
146         }
147         // 一级cache已耗尽
148         if (primaryCache.GetLength()) {
149             T* t = reinterpret_cast<T*>(::operator new(TSize));
150 #ifdef FFRT_BBOX_ENABLE
151             lock.lock();
152             secondaryCache.insert(t);
153             lock.unlock();
154 #endif
155             return t;
156         }
157         return static_cast<T*>(primaryCache.PopHead());
158     }
159 
free(T* t)160     void free(T* t)
161     {
162         if (basePtr && basePtr <= t &&
163             static_cast<size_t>(reinterpret_cast<uintptr_t>(t)) <
164             static_cast<size_t>(reinterpret_cast<uintptr_t>(basePtr)) + MmapSz) {
165             primaryCache.PushTail(t);
166             return;
167         }
168 
169 #ifdef FFRT_BBOX_ENABLE
170         lock.lock();
171         secondaryCache.erase(t);
172         lock.unlock();
173 #endif
174         ::operator delete(t);
175     }
176 
SimpleAllocator(std::size_t size = sizeof(T))177     SimpleAllocator(std::size_t size = sizeof(T)) : TSize(size)
178     {
179     }
~SimpleAllocator()180     ~SimpleAllocator()
181     {
182         std::unique_lock<decltype(lock)> lck(lock);
183         if (basePtr == nullptr) {
184             return;
185         }
186 #ifdef FFRT_BBOX_ENABLE
187         uint32_t try_cnt = ALLOCATOR_DESTRUCT_TIMESOUT;
188         std::size_t reserved = MmapSz / TSize;
189         while (try_cnt > 0) {
190             if (primaryCache.GetLength() == reserved && secondaryCache.size() == 0) {
191                 break;
192             }
193             lck.unlock();
194             usleep(1000);
195             try_cnt--;
196             lck.lock();
197         }
198         if (try_cnt == 0) {
199             FFRT_LOGE("clear allocator failed");
200         }
201         for (auto ite = secondaryCache.cbegin(); ite != secondaryCache.cend(); ite++) {
202             ::operator delete(*ite);
203         }
204 #endif
205         ::operator delete(basePtr);
206         FFRT_LOGI("destruct SimpleAllocator");
207     }
208 };
209 
210 template <typename T, std::size_t MmapSz = FFRT_ALLOCATOR_MMAP_SIZE>
211 class QSimpleAllocator {
212     std::size_t TSize;
213     std::size_t curAllocated;
214     std::size_t maxAllocated;
215     std::mutex lock;
216     std::vector<T*> cache;
217     uint32_t flags = MAP_ANONYMOUS | MAP_PRIVATE;
218 
expand()219     bool expand()
220     {
221         const int prot = PROT_READ | PROT_WRITE;
222         char* p = reinterpret_cast<char*>(mmap(nullptr, MmapSz, prot, flags, -1, 0));
223         if (p == (char*)MAP_FAILED) {
224             if ((flags & MAP_HUGETLB) != 0) {
225                 flags = MAP_ANONYMOUS | MAP_PRIVATE;
226                 p = reinterpret_cast<char*>(mmap(nullptr, MmapSz, prot, flags, -1, 0));
227             }
228             if (p == (char*)MAP_FAILED) {
229                 perror("mmap");
230                 return false;
231             }
232         }
233         for (std::size_t i = 0; i + TSize <= MmapSz; i += TSize) {
234             cache.push_back(reinterpret_cast<T*>(p + i));
235         }
236         return true;
237     }
238 
Alloc()239     T* Alloc()
240     {
241         T* p = nullptr;
242         lock.lock();
243         if (cache.empty()) {
244             if (!expand()) {
245                 lock.unlock();
246                 return nullptr;
247             }
248         }
249         p = cache.back();
250         ++curAllocated;
251         maxAllocated = std::max(curAllocated, maxAllocated);
252         cache.pop_back();
253         lock.unlock();
254         return p;
255     }
256 
free(T* p)257     void free(T* p)
258     {
259         lock.lock();
260         --curAllocated;
261         cache.push_back(p);
262         lock.unlock();
263     }
264 
release()265     void release()
266     {
267         T* p = nullptr;
268         lock.lock();
269         FFRT_LOGD("coroutine release with waterline %d, cur occupied %d, cached size %d",
270             maxAllocated, curAllocated, cache.size());
271         size_t reservedCnt = maxAllocated - curAllocated + 1; // reserve additional one for robustness
272         maxAllocated = curAllocated;
273         while (cache.size() > reservedCnt) {
274             p = cache.back();
275             cache.pop_back();
276             int ret = munmap(p, TSize);
277             if (ret != 0) {
278                 FFRT_LOGE("munmap failed with errno: %d", errno);
279             }
280         }
281         lock.unlock();
282     }
283 
QSimpleAllocator()284     QSimpleAllocator()
285     {
286     }
287 
288 public:
QSimpleAllocator(std::size_t size = sizeof(T))289     explicit QSimpleAllocator(std::size_t size = sizeof(T)) : curAllocated(0), maxAllocated(0)
290     {
291         std::size_t p_size = static_cast<std::size_t>(getpagesize());
292         // manually align the size to the page size
293         TSize = (size - 1 + p_size) & -p_size;
294         if (MmapSz % TSize != 0) {
295             FFRT_LOGE("MmapSz is not divisible by TSize which may cause memory leak!");
296         }
297     }
298     QSimpleAllocator(QSimpleAllocator const&) = delete;
299     void operator=(QSimpleAllocator const&) = delete;
300 
Instance(std::size_t size)301     static QSimpleAllocator<T, MmapSz>* Instance(std::size_t size)
302     {
303         static QSimpleAllocator<T, MmapSz> ins(size);
304         return &ins;
305     }
306 
AllocMem(std::size_t size = sizeof(T))307     static T* AllocMem(std::size_t size = sizeof(T))
308     {
309         return Instance(size)->Alloc();
310     }
311 
FreeMem(T* p, std::size_t size = sizeof(T))312     static void FreeMem(T* p, std::size_t size = sizeof(T))
313     {
314         Instance(size)->free(p);
315     }
316 
releaseMem(std::size_t size = sizeof(T))317     static void releaseMem(std::size_t size = sizeof(T))
318     {
319         Instance(size)->release();
320     }
321 };
322 } // namespace ffrt
323 #endif /* UTIL_SLAB_H */
324