1 /* 2 * Copyright 2007, Ingo Weinhold, ingo_weinhold@gmx.de. 3 * Copyright 2019-2024, Haiku, Inc. All rights reserved. 4 * Distributed under the terms of the MIT license. 5 */ 6 #include "DataContainer.h" 7 8 #include <StackOrHeapArray.h> 9 #include <util/AutoLock.h> 10 #include <util/BitUtils.h> 11 #include <slab/Slab.h> 12 13 #include <vfs.h> 14 #include <vm/VMCache.h> 15 #include <vm/vm_page.h> 16 #include "VMAnonymousNoSwapCache.h" 17 #include "vnode_store.h" 18 19 #include "AllocationInfo.h" 20 #include "DebugSupport.h" 21 #include "Misc.h" 22 #include "Volume.h" 23 #include "cache_support.h" 24 25 26 // Initial size of the DataContainer's small buffer. If it contains data up to 27 // this size, nothing is allocated, but the small buffer is used instead. 28 // 16 bytes are for free, since they are shared with the block list. 29 // (actually even more, since the list has an initial size). 30 // I ran a test analyzing what sizes the attributes in my system have: 31 // size percentage bytes used in average 32 // <= 0 0.00 93.45 33 // <= 4 25.46 75.48 34 // <= 8 30.54 73.02 35 // <= 16 52.98 60.37 36 // <= 32 80.19 51.74 37 // <= 64 94.38 70.54 38 // <= 126 96.90 128.23 39 // 40 // For average memory usage it is assumed, that attributes larger than 126 41 // bytes have size 127, that the list has an initial capacity of 10 entries 42 // (40 bytes), that the block reference consumes 4 bytes and the block header 43 // 12 bytes. The optimal length is actually 35, with 51.05 bytes per 44 // attribute, but I conservatively rounded to 32. 45 static const off_t kMinimumSmallBufferSize = 32; 46 static const off_t kMaximumSmallBufferSize = (B_PAGE_SIZE / 4); 47 48 49 // We don't use VMVnodeCache because it's for caching pages that exist on disk. 50 // All we need is an AnonymousCache that tracks when the vnode is referenced. 51 class VMForVnodeCache final : public VMAnonymousNoSwapCache { 52 public: 53 status_t Init() 54 { 55 fVnode = NULL; 56 return VMAnonymousNoSwapCache::Init(false, 0, 0, 0); 57 } 58 59 status_t AcquireUnreferencedStoreRef() override 60 { 61 return B_NOT_SUPPORTED; 62 } 63 64 void AcquireStoreRef() override 65 { 66 vfs_acquire_vnode(fVnode); 67 } 68 69 void ReleaseStoreRef() override 70 { 71 vfs_put_vnode(fVnode); 72 } 73 74 protected: 75 virtual void DeleteObject() 76 { 77 static_assert(sizeof(VMForVnodeCache) <= sizeof(VMVnodeCache), "cache too large"); 78 object_cache_delete(gVnodeCacheObjectCache, this); 79 } 80 81 private: 82 friend class DataContainer; 83 struct vnode* fVnode; 84 }; 85 86 87 DataContainer::DataContainer(Volume *volume) 88 : fVolume(volume), 89 fSize(0), 90 fCache(NULL), 91 fSmallBuffer(NULL), 92 fSmallBufferSize(0) 93 { 94 } 95 96 97 DataContainer::~DataContainer() 98 { 99 if (fCache != NULL) { 100 fCache->Lock(); 101 fCache->ReleaseRefAndUnlock(); 102 fCache = NULL; 103 } 104 if (fSmallBuffer != NULL) { 105 free(fSmallBuffer); 106 fSmallBuffer = NULL; 107 } 108 } 109 110 111 status_t 112 DataContainer::InitCheck() const 113 { 114 return (fVolume != NULL ? B_OK : B_ERROR); 115 } 116 117 118 VMCache* 119 DataContainer::GetCache(struct vnode* vnode) 120 { 121 // TODO: Because we always get the cache for files on creation vs. on demand, 122 // this means files (no matter how small) always use cache mode at present. 123 if (!_IsCacheMode()) 124 _SwitchToCacheMode(); 125 ((VMForVnodeCache*)fCache)->fVnode = vnode; 126 return fCache; 127 } 128 129 130 status_t 131 DataContainer::Resize(off_t newSize) 132 { 133 // PRINT("DataContainer::Resize(%lld), fSize: %lld\n", newSize, fSize); 134 135 status_t error = B_OK; 136 if (_RequiresCacheMode(newSize)) { 137 if (newSize < fSize) { 138 // shrink 139 // resize the VMCache, which will automatically free pages 140 AutoLocker<VMCache> _(fCache); 141 error = fCache->Resize(newSize, VM_PRIORITY_USER); 142 if (error != B_OK) 143 return error; 144 } else { 145 // grow 146 if (!_IsCacheMode()) 147 error = _SwitchToCacheMode(); 148 if (error != B_OK) 149 return error; 150 151 AutoLocker<VMCache> _(fCache); 152 fCache->Resize(newSize, VM_PRIORITY_USER); 153 154 // pages will be added as they are written to; so nothing else 155 // needs to be done here. 156 } 157 } else if (fSmallBufferSize < newSize 158 || (fSmallBufferSize - newSize) > (kMaximumSmallBufferSize / 2)) { 159 const size_t newBufferSize = max_c(next_power_of_2(newSize), 160 kMinimumSmallBufferSize); 161 void* newBuffer = realloc(fSmallBuffer, newBufferSize); 162 if (newBuffer == NULL) 163 return B_NO_MEMORY; 164 165 fSmallBufferSize = newBufferSize; 166 fSmallBuffer = (uint8*)newBuffer; 167 } 168 169 fSize = newSize; 170 171 // PRINT("DataContainer::Resize() done: %lx, fSize: %lld\n", error, fSize); 172 return error; 173 } 174 175 176 status_t 177 DataContainer::ReadAt(off_t offset, void *_buffer, size_t size, 178 size_t *bytesRead) 179 { 180 uint8 *buffer = (uint8*)_buffer; 181 status_t error = (buffer && offset >= 0 && 182 bytesRead ? B_OK : B_BAD_VALUE); 183 if (error != B_OK) 184 return error; 185 186 // read not more than we have to offer 187 offset = min(offset, fSize); 188 size = min(size, size_t(fSize - offset)); 189 190 if (!_IsCacheMode()) { 191 // in non-cache mode, use the "small buffer" 192 if (IS_USER_ADDRESS(buffer)) { 193 error = user_memcpy(buffer, fSmallBuffer + offset, size); 194 if (error != B_OK) 195 size = 0; 196 } else { 197 memcpy(buffer, fSmallBuffer + offset, size); 198 } 199 200 if (bytesRead != NULL) 201 *bytesRead = size; 202 return error; 203 } 204 205 // cache mode 206 error = _DoCacheIO(offset, buffer, size, bytesRead, false); 207 208 return error; 209 } 210 211 212 status_t 213 DataContainer::WriteAt(off_t offset, const void *_buffer, size_t size, 214 size_t *bytesWritten) 215 { 216 PRINT("DataContainer::WriteAt(%lld, %p, %lu, %p), fSize: %lld\n", offset, _buffer, size, bytesWritten, fSize); 217 218 const uint8 *buffer = (const uint8*)_buffer; 219 status_t error = (buffer && offset >= 0 && bytesWritten 220 ? B_OK : B_BAD_VALUE); 221 if (error != B_OK) 222 return error; 223 224 // resize the container, if necessary 225 if ((offset + (off_t)size) > fSize) 226 error = Resize(offset + size); 227 if (error != B_OK) 228 return error; 229 230 if (!_IsCacheMode()) { 231 // in non-cache mode, use the "small buffer" 232 if (IS_USER_ADDRESS(buffer)) { 233 error = user_memcpy(fSmallBuffer + offset, buffer, size); 234 if (error != B_OK) 235 size = 0; 236 } else { 237 memcpy(fSmallBuffer + offset, buffer, size); 238 } 239 240 if (bytesWritten != NULL) 241 *bytesWritten = size; 242 return error; 243 } 244 245 // cache mode 246 error = _DoCacheIO(offset, (uint8*)buffer, size, bytesWritten, true); 247 248 PRINT("DataContainer::WriteAt() done: %lx, fSize: %lld\n", error, fSize); 249 return error; 250 } 251 252 253 void 254 DataContainer::GetAllocationInfo(AllocationInfo &info) 255 { 256 if (_IsCacheMode()) { 257 info.AddAreaAllocation(fCache->committed_size); 258 } else { 259 // ... 260 } 261 } 262 263 264 inline bool 265 DataContainer::_RequiresCacheMode(size_t size) 266 { 267 // we cannot back out of cache mode after entering it, 268 // as there may be other consumers of our VMCache 269 return _IsCacheMode() || (size > kMaximumSmallBufferSize); 270 } 271 272 273 inline bool 274 DataContainer::_IsCacheMode() const 275 { 276 return fCache != NULL; 277 } 278 279 280 inline int32 281 DataContainer::_CountBlocks() const 282 { 283 if (_IsCacheMode()) 284 return fCache->page_count; 285 else if (fSize == 0) // small buffer mode, empty buffer 286 return 0; 287 return 1; // small buffer mode, non-empty buffer 288 } 289 290 291 status_t 292 DataContainer::_SwitchToCacheMode() 293 { 294 VMForVnodeCache* cache = new(gVnodeCacheObjectCache, 0) VMForVnodeCache; 295 if (cache == NULL) 296 return B_NO_MEMORY; 297 298 status_t error = cache->Init(); 299 if (error != B_OK) 300 return error; 301 302 AutoLocker<VMCache> locker(cache); 303 304 fCache = cache; 305 fCache->temporary = 1; 306 fCache->unmergeable = 1; 307 fCache->virtual_end = fSize; 308 309 error = fCache->Commit(fSize, VM_PRIORITY_USER); 310 if (error != B_OK) 311 return error; 312 313 if (fSize != 0) 314 error = _DoCacheIO(0, fSmallBuffer, fSize, NULL, true); 315 316 free(fSmallBuffer); 317 fSmallBuffer = NULL; 318 fSmallBufferSize = 0; 319 320 return error; 321 } 322 323 324 status_t 325 DataContainer::_DoCacheIO(const off_t offset, uint8* buffer, ssize_t length, 326 size_t* bytesProcessed, bool isWrite) 327 { 328 const size_t originalLength = length; 329 const bool user = IS_USER_ADDRESS(buffer); 330 331 const off_t rounded_offset = ROUNDDOWN(offset, B_PAGE_SIZE); 332 const size_t rounded_len = ROUNDUP((length) + (offset - rounded_offset), 333 B_PAGE_SIZE); 334 BStackOrHeapArray<vm_page*, 16> pages(rounded_len / B_PAGE_SIZE); 335 if (!pages.IsValid()) 336 return B_NO_MEMORY; 337 338 cache_get_pages(fCache, rounded_offset, rounded_len, isWrite, pages); 339 340 status_t error = B_OK; 341 size_t index = 0; 342 343 while (length > 0) { 344 vm_page* page = pages[index]; 345 phys_addr_t at = (page != NULL) 346 ? (page->physical_page_number * B_PAGE_SIZE) : 0; 347 ssize_t bytes = B_PAGE_SIZE; 348 if (index == 0) { 349 const uint32 pageoffset = (offset % B_PAGE_SIZE); 350 at += pageoffset; 351 bytes -= pageoffset; 352 } 353 bytes = min(length, bytes); 354 355 if (isWrite) { 356 page->modified = true; 357 error = vm_memcpy_to_physical(at, buffer, bytes, user); 358 } else { 359 if (page != NULL) { 360 error = vm_memcpy_from_physical(buffer, at, bytes, user); 361 } else { 362 if (user) { 363 error = user_memset(buffer, 0, bytes); 364 } else { 365 memset(buffer, 0, bytes); 366 } 367 } 368 } 369 if (error != B_OK) 370 break; 371 372 buffer += bytes; 373 length -= bytes; 374 index++; 375 } 376 377 cache_put_pages(fCache, rounded_offset, rounded_len, pages, error == B_OK); 378 379 if (bytesProcessed != NULL) 380 *bytesProcessed = length > 0 ? originalLength - length : originalLength; 381 382 return error; 383 } 384