• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
HasBeenFreed(T * t)70     static bool HasBeenFreed(T* t)
71     {
72         return Instance()->BeenFreed(t);
73     }
74 
LockMem()75     static void LockMem()
76     {
77         return Instance()->SimpleAllocatorLock();
78     }
79 
UnlockMem()80     static void UnlockMem()
81     {
82         return Instance()->SimpleAllocatorUnLock();
83     }
84 private:
85     std::vector<T*> primaryCache;
86 #ifdef FFRT_BBOX_ENABLE
87     std::unordered_set<T*> secondaryCache;
88 #endif
89     std::size_t TSize;
90     T* basePtr = nullptr;
91     std::size_t count = 0;
92 
getUnfreed()93     std::vector<void *> getUnfreed()
94     {
95         std::vector<void *> ret;
96 #ifdef FFRT_BBOX_ENABLE
97         ret.reserve(MmapSz / TSize + secondaryCache.size());
98         char* p = reinterpret_cast<char*>(basePtr);
99         for (std::size_t i = 0; i + TSize <= MmapSz; i += TSize) {
100             if (basePtr != nullptr &&
101                 std::find(primaryCache.begin(), primaryCache.end(),
102                     reinterpret_cast<T*>(p + i)) == primaryCache.end()) {
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 
BeenFreed(T * t)118     bool BeenFreed(T* t)
119     {
120 #ifdef FFRT_BBOX_ENABLE
121         if (t == nullptr) {
122             return true;
123         }
124 
125         if (basePtr != nullptr &&
126             basePtr <= t &&
127             static_cast<size_t>(reinterpret_cast<uintptr_t>(t)) <
128             (static_cast<size_t>(reinterpret_cast<uintptr_t>(basePtr)) + MmapSz)) {
129                 return std::find(primaryCache.begin(), primaryCache.end(), t) != primaryCache.end();
130             } else {
131                 return secondaryCache.find(t) == secondaryCache.end();
132             }
133 #endif
134         return true;
135     }
136 
SimpleAllocatorUnLock()137     void SimpleAllocatorUnLock()
138     {
139         lock.unlock();
140     }
141 
init()142     void init()
143     {
144         char* p = reinterpret_cast<char*>(std::calloc(1, MmapSz));
145         if (p == nullptr) {
146             FFRT_LOGE("calloc failed");
147             std::terminate();
148         }
149         count = MmapSz / TSize;
150         primaryCache.reserve(count);
151         for (std::size_t i = 0; i + TSize <= MmapSz; i += TSize) {
152             primaryCache.push_back(reinterpret_cast<T*>(p + i));
153         }
154         basePtr = reinterpret_cast<T*>(p);
155     }
156 
Alloc()157     T* Alloc()
158     {
159         lock.lock();
160         T* t = nullptr;
161         if (count == 0) {
162             if (basePtr != nullptr) {
163                 t = reinterpret_cast<T*>(std::calloc(1, TSize));
164                 if (t == nullptr) {
165                     FFRT_LOGE("calloc failed");
166                     std::terminate();
167                 }
168 #ifdef FFRT_BBOX_ENABLE
169                 secondaryCache.insert(t);
170 #endif
171                 lock.unlock();
172                 return t;
173             }
174             init();
175         }
176         t = primaryCache.back();
177         primaryCache.pop_back();
178         count--;
179         lock.unlock();
180         return t;
181     }
182 
free(T * t)183     void free(T* t)
184     {
185         lock.lock();
186         t->~T();
187         if (basePtr != nullptr &&
188             basePtr <= t &&
189             static_cast<size_t>(reinterpret_cast<uintptr_t>(t)) <
190             static_cast<size_t>(reinterpret_cast<uintptr_t>(basePtr)) + MmapSz) {
191             primaryCache.push_back(t);
192             count++;
193         } else {
194 #ifdef FFRT_BBOX_ENABLE
195             secondaryCache.erase(t);
196 #endif
197             std::free(t);
198         }
199         lock.unlock();
200     }
201 
TSize(size)202     SimpleAllocator(std::size_t size = sizeof(T)) : TSize(size)
203     {
204     }
~SimpleAllocator()205     ~SimpleAllocator()
206     {
207         std::unique_lock<decltype(lock)> lck(lock);
208         if (basePtr == nullptr) {
209             return;
210         }
211 #ifdef FFRT_BBOX_ENABLE
212         uint32_t try_cnt = ALLOCATOR_DESTRUCT_TIMESOUT;
213         std::size_t reserved = MmapSz / TSize;
214         while (try_cnt > 0) {
215             if (primaryCache.size() == reserved && secondaryCache.size() == 0) {
216                 break;
217             }
218             lck.unlock();
219             usleep(1000);
220             try_cnt--;
221             lck.lock();
222         }
223         if (try_cnt == 0) {
224             FFRT_LOGE("clear allocator failed");
225         }
226         for (auto ite = secondaryCache.cbegin(); ite != secondaryCache.cend(); ite++) {
227             std::free(*ite);
228         }
229 #endif
230         std::free(basePtr);
231         FFRT_LOGI("destruct SimpleAllocator");
232     }
233 };
234 
235 template <typename T, std::size_t MmapSz = 8 * 1024 * 1024>
236 class QSimpleAllocator {
237     std::size_t TSize;
238     std::size_t curAllocated;
239     std::size_t maxAllocated;
240     std::mutex lock;
241     std::vector<T*> cache;
242     uint32_t flags = MAP_ANONYMOUS | MAP_PRIVATE;
243 
expand()244     bool expand()
245     {
246         const int prot = PROT_READ | PROT_WRITE;
247         char* p = reinterpret_cast<char*>(mmap(nullptr, MmapSz, prot, flags, -1, 0));
248         if (p == (char*)MAP_FAILED) {
249             if ((flags & MAP_HUGETLB) != 0) {
250                 flags = MAP_ANONYMOUS | MAP_PRIVATE;
251                 p = reinterpret_cast<char*>(mmap(nullptr, MmapSz, prot, flags, -1, 0));
252             }
253             if (p == (char*)MAP_FAILED) {
254                 perror("mmap");
255                 return false;
256             }
257         }
258         for (std::size_t i = 0; i + TSize <= MmapSz; i += TSize) {
259             cache.push_back(reinterpret_cast<T*>(p + i));
260         }
261         return true;
262     }
263 
Alloc()264     T* Alloc()
265     {
266         T* p = nullptr;
267         lock.lock();
268         if (cache.empty()) {
269             if (!expand()) {
270                 lock.unlock();
271                 return nullptr;
272             }
273         }
274         p = cache.back();
275         ++curAllocated;
276         maxAllocated = std::max(curAllocated, maxAllocated);
277         cache.pop_back();
278         lock.unlock();
279         return p;
280     }
281 
free(T * p)282     void free(T* p)
283     {
284         lock.lock();
285         --curAllocated;
286         cache.push_back(p);
287         lock.unlock();
288     }
289 
release()290     void release()
291     {
292         T* p = nullptr;
293         lock.lock();
294         FFRT_LOGD("coroutine release with waterline %d, cur occupied %d, cached size %d",
295             maxAllocated, curAllocated, cache.size());
296         size_t reservedCnt = maxAllocated - curAllocated + 1; // reserve additional one for robustness
297         maxAllocated = curAllocated;
298         while (cache.size() > reservedCnt) {
299             p = cache.back();
300             cache.pop_back();
301             int ret = munmap(p, TSize);
302             if (ret != 0) {
303                 FFRT_LOGE("munmap failed with errno: %d", errno);
304             }
305         }
306         lock.unlock();
307     }
308 
QSimpleAllocator()309     QSimpleAllocator()
310     {
311     }
312 
313 public:
314     explicit QSimpleAllocator(std::size_t size = sizeof(T)) : curAllocated(0), maxAllocated(0)
315     {
316         std::size_t p_size = static_cast<std::size_t>(getpagesize());
317         // manually align the size to the page size
318         TSize = (size - 1 + p_size) & -p_size;
319         if (MmapSz % TSize != 0) {
320             FFRT_LOGE("MmapSz is not divisible by TSize which may cause memory leak!");
321         }
322     }
323     QSimpleAllocator(QSimpleAllocator const&) = delete;
324     void operator=(QSimpleAllocator const&) = delete;
325 
Instance(std::size_t size)326     static QSimpleAllocator<T, MmapSz>* Instance(std::size_t size)
327     {
328         static QSimpleAllocator<T, MmapSz> ins(size);
329         return &ins;
330     }
331 
332     static T* AllocMem(std::size_t size = sizeof(T))
333     {
334         return Instance(size)->Alloc();
335     }
336 
337     static void FreeMem(T* p, std::size_t size = sizeof(T))
338     {
339         Instance(size)->free(p);
340     }
341 
342     static void releaseMem(std::size_t size = sizeof(T))
343     {
344         Instance(size)->release();
345     }
346 };
347 } // namespace ffrt
348 #endif /* UTIL_SLAB_H */
349