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