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 = 128 * 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 instance()43 static SimpleAllocator<T>* instance() 44 { 45 static SimpleAllocator<T> ins; 46 return &ins; 47 } 48 49 // NOTE: call constructor after allocMem allocMem()50 static T* allocMem() 51 { 52 return instance()->alloc(); 53 } 54 55 // NOTE: call destructor before freeMem freeMem(T * t)56 static void freeMem(T* t) 57 { 58 t->~T(); 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<T*> getUnfreedMem() 66 { 67 return instance()->getUnfreed(); 68 } 69 private: 70 fast_mutex lock; 71 std::vector<T*> primaryCache; 72 #ifdef FFRT_BBOX_ENABLE 73 std::unordered_set<T*> secondaryCache; 74 #endif 75 T* basePtr = nullptr; 76 std::size_t count = 0; 77 getUnfreed()78 std::vector<T*> getUnfreed() 79 { 80 lock.lock(); 81 std::vector<T*> ret; 82 #ifdef FFRT_BBOX_ENABLE 83 ret.reserve(MmapSz / sizeof(T) + secondaryCache.size()); 84 for (std::size_t i = 0; i < MmapSz / sizeof(T); ++i) { 85 if (basePtr != nullptr && 86 std::find(primaryCache.begin(), primaryCache.end(), &basePtr[i]) == primaryCache.end()) { 87 ret.push_back(&basePtr[i]); 88 } 89 } 90 for (auto ite = secondaryCache.cbegin(); ite != secondaryCache.cend(); ite++) { 91 ret.push_back(*ite); 92 } 93 #endif 94 lock.unlock(); 95 return ret; 96 } 97 init()98 void init() 99 { 100 basePtr = reinterpret_cast<T*>(::operator new(MmapSz)); 101 count = MmapSz / sizeof(T); 102 primaryCache.reserve(count); 103 for (std::size_t i = 0; i < count; ++i) { 104 primaryCache.push_back(&basePtr[i]); 105 } 106 } 107 alloc()108 T* alloc() 109 { 110 lock.lock(); 111 T* t = nullptr; 112 if (count == 0) { 113 if (basePtr != nullptr) { 114 t = reinterpret_cast<T*>(::operator new(sizeof(T))); 115 #ifdef FFRT_BBOX_ENABLE 116 secondaryCache.insert(t); 117 #endif 118 lock.unlock(); 119 return t; 120 } 121 init(); 122 } 123 t = primaryCache.back(); 124 primaryCache.pop_back(); 125 count--; 126 lock.unlock(); 127 return t; 128 } 129 free(T * t)130 void free(T* t) 131 { 132 lock.lock(); 133 if (basePtr != nullptr && 134 basePtr <= t && 135 static_cast<size_t>(reinterpret_cast<uintptr_t>(t)) < 136 static_cast<size_t>(reinterpret_cast<uintptr_t>(basePtr)) + MmapSz) { 137 primaryCache.push_back(t); 138 count++; 139 } else { 140 ::operator delete(t); 141 #ifdef FFRT_BBOX_ENABLE 142 secondaryCache.erase(t); 143 #endif 144 } 145 lock.unlock(); 146 } 147 SimpleAllocator()148 SimpleAllocator() 149 { 150 } ~SimpleAllocator()151 ~SimpleAllocator() 152 { 153 std::unique_lock<decltype(lock)> lck(lock); 154 if (basePtr == nullptr) { 155 return; 156 } 157 #ifdef FFRT_BBOX_ENABLE 158 uint32_t try_cnt = ALLOCATOR_DESTRUCT_TIMESOUT; 159 std::size_t reserved = MmapSz / sizeof(T); 160 while (try_cnt > 0) { 161 if (primaryCache.size() == reserved && secondaryCache.size() == 0) { 162 break; 163 } 164 lck.unlock(); 165 usleep(1000); 166 try_cnt--; 167 lck.lock(); 168 } 169 if (try_cnt == 0) { 170 FFRT_LOGE("clear allocator failed"); 171 } 172 for (auto ite = secondaryCache.cbegin(); ite != secondaryCache.cend(); ite++) { 173 ::operator delete(*ite); 174 } 175 #endif 176 ::operator delete(basePtr); 177 } 178 }; 179 180 constexpr uint32_t RESERVED_COROUTINE_COUNT = 0; 181 template <typename T, std::size_t MmapSz = 16 * 1024 * 1024> 182 class QSimpleAllocator { 183 std::size_t TSize; 184 std::mutex lock; 185 std::vector<T*> cache; 186 uint32_t flags = MAP_ANONYMOUS | MAP_PRIVATE; 187 expand()188 bool expand() 189 { 190 const int prot = PROT_READ | PROT_WRITE; 191 char* p = reinterpret_cast<char*>(mmap(nullptr, MmapSz, prot, flags, -1, 0)); 192 if (p == (char*)MAP_FAILED) { 193 if ((flags & MAP_HUGETLB) != 0) { 194 flags = MAP_ANONYMOUS | MAP_PRIVATE; 195 p = reinterpret_cast<char*>(mmap(nullptr, MmapSz, prot, flags, -1, 0)); 196 } 197 if (p == (char*)MAP_FAILED) { 198 perror("mmap"); 199 return false; 200 } 201 } 202 for (std::size_t i = 0; i + TSize <= MmapSz; i += TSize) { 203 cache.push_back(reinterpret_cast<T*>(p + i)); 204 } 205 return true; 206 } 207 alloc()208 T* alloc() 209 { 210 T* p = nullptr; 211 lock.lock(); 212 if (cache.empty()) { 213 if (!expand()) { 214 lock.unlock(); 215 return nullptr; 216 } 217 } 218 p = cache.back(); 219 cache.pop_back(); 220 lock.unlock(); 221 return p; 222 } 223 free(T * p)224 void free(T* p) 225 { 226 lock.lock(); 227 cache.push_back(p); 228 lock.unlock(); 229 } 230 release()231 void release() 232 { 233 T* p = nullptr; 234 lock.lock(); 235 while (cache.size() > RESERVED_COROUTINE_COUNT) { 236 p = cache.back(); 237 cache.pop_back(); 238 int ret = munmap(p, TSize); 239 if (ret != 0) { 240 FFRT_LOGE("munmap failed with errno: %d", errno); 241 } 242 } 243 lock.unlock(); 244 } 245 QSimpleAllocator()246 QSimpleAllocator() 247 { 248 } 249 250 public: 251 explicit QSimpleAllocator(std::size_t size = sizeof(T)) 252 { 253 std::size_t p_size = static_cast<std::size_t>(getpagesize()); 254 // manually align the size to the page size 255 TSize = (size - 1 + p_size) & -p_size; 256 if (MmapSz % TSize != 0) { 257 FFRT_LOGE("MmapSz is not divisible by TSize which may cause memory leak!"); 258 } 259 } 260 QSimpleAllocator(QSimpleAllocator const&) = delete; 261 void operator=(QSimpleAllocator const&) = delete; 262 instance(std::size_t size)263 static QSimpleAllocator<T, MmapSz>* instance(std::size_t size) 264 { 265 static QSimpleAllocator<T, MmapSz> ins(size); 266 return &ins; 267 } 268 269 static T* allocMem(std::size_t size = sizeof(T)) 270 { 271 return instance(size)->alloc(); 272 } 273 274 static void freeMem(T* p, std::size_t size = sizeof(T)) 275 { 276 instance(size)->free(p); 277 } 278 279 static void releaseMem(std::size_t size = sizeof(T)) 280 { 281 instance(size)->release(); 282 } 283 }; 284 } // namespace ffrt 285 #endif /* UTIL_SLAB_H */ 286