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