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