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