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