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