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