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