1 /* 2 * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. 3 * Copyright 2002-2009, Axel Dörfler, axeld@pinc-software.de. 4 * Distributed under the terms of the MIT License. 5 */ 6 7 8 #include "VMAddressSpaceLocking.h" 9 10 #include <AutoDeleter.h> 11 12 #include <vm/vm.h> 13 #include <vm/VMAddressSpace.h> 14 #include <vm/VMArea.h> 15 #include <vm/VMCache.h> 16 17 18 // #pragma mark - AddressSpaceLockerBase 19 20 21 /*static*/ VMAddressSpace* 22 AddressSpaceLockerBase::GetAddressSpaceByAreaID(area_id id) 23 { 24 VMAddressSpace* addressSpace = NULL; 25 26 VMAreas::ReadLock(); 27 28 VMArea* area = VMAreas::LookupLocked(id); 29 if (area != NULL) { 30 addressSpace = area->address_space; 31 addressSpace->Get(); 32 } 33 34 VMAreas::ReadUnlock(); 35 36 return addressSpace; 37 } 38 39 40 // #pragma mark - AddressSpaceReadLocker 41 42 43 AddressSpaceReadLocker::AddressSpaceReadLocker(team_id team) 44 : 45 fSpace(NULL), 46 fLocked(false) 47 { 48 SetTo(team); 49 } 50 51 52 /*! Takes over the reference of the address space, if \a getNewReference is 53 \c false. 54 */ 55 AddressSpaceReadLocker::AddressSpaceReadLocker(VMAddressSpace* space, 56 bool getNewReference) 57 : 58 fSpace(NULL), 59 fLocked(false) 60 { 61 SetTo(space, getNewReference); 62 } 63 64 65 AddressSpaceReadLocker::AddressSpaceReadLocker() 66 : 67 fSpace(NULL), 68 fLocked(false) 69 { 70 } 71 72 73 AddressSpaceReadLocker::~AddressSpaceReadLocker() 74 { 75 Unset(); 76 } 77 78 79 void 80 AddressSpaceReadLocker::Unset() 81 { 82 Unlock(); 83 if (fSpace != NULL) 84 fSpace->Put(); 85 fSpace = NULL; 86 } 87 88 89 status_t 90 AddressSpaceReadLocker::SetTo(team_id team) 91 { 92 ASSERT(fSpace == NULL); 93 94 fSpace = VMAddressSpace::Get(team); 95 if (fSpace == NULL) 96 return B_BAD_TEAM_ID; 97 98 fSpace->ReadLock(); 99 fLocked = true; 100 return B_OK; 101 } 102 103 104 /*! Takes over the reference of the address space, if \a getNewReference is 105 \c false. 106 */ 107 void 108 AddressSpaceReadLocker::SetTo(VMAddressSpace* space, bool getNewReference) 109 { 110 ASSERT(fSpace == NULL); 111 112 fSpace = space; 113 114 if (getNewReference) 115 fSpace->Get(); 116 117 fSpace->ReadLock(); 118 fLocked = true; 119 } 120 121 122 status_t 123 AddressSpaceReadLocker::SetFromArea(area_id areaID, VMArea*& area) 124 { 125 ASSERT(fSpace == NULL); 126 127 fSpace = GetAddressSpaceByAreaID(areaID); 128 if (fSpace == NULL) 129 return B_BAD_TEAM_ID; 130 131 fSpace->ReadLock(); 132 133 area = VMAreas::Lookup(areaID); 134 135 if (area == NULL || area->address_space != fSpace) { 136 fSpace->ReadUnlock(); 137 return B_BAD_VALUE; 138 } 139 140 fLocked = true; 141 return B_OK; 142 } 143 144 145 bool 146 AddressSpaceReadLocker::Lock() 147 { 148 if (fLocked) 149 return true; 150 if (fSpace == NULL) 151 return false; 152 153 fSpace->ReadLock(); 154 fLocked = true; 155 156 return true; 157 } 158 159 160 void 161 AddressSpaceReadLocker::Unlock() 162 { 163 if (fLocked) { 164 fSpace->ReadUnlock(); 165 fLocked = false; 166 } 167 } 168 169 170 // #pragma mark - AddressSpaceWriteLocker 171 172 173 AddressSpaceWriteLocker::AddressSpaceWriteLocker(team_id team) 174 : 175 fSpace(NULL), 176 fLocked(false), 177 fDegraded(false) 178 { 179 SetTo(team); 180 } 181 182 183 AddressSpaceWriteLocker::AddressSpaceWriteLocker(VMAddressSpace* space, 184 bool getNewReference) 185 : 186 fSpace(NULL), 187 fLocked(false), 188 fDegraded(false) 189 { 190 SetTo(space, getNewReference); 191 } 192 193 194 AddressSpaceWriteLocker::AddressSpaceWriteLocker() 195 : 196 fSpace(NULL), 197 fLocked(false), 198 fDegraded(false) 199 { 200 } 201 202 203 AddressSpaceWriteLocker::~AddressSpaceWriteLocker() 204 { 205 Unset(); 206 } 207 208 209 void 210 AddressSpaceWriteLocker::Unset() 211 { 212 Unlock(); 213 if (fSpace != NULL) 214 fSpace->Put(); 215 fSpace = NULL; 216 } 217 218 219 status_t 220 AddressSpaceWriteLocker::SetTo(team_id team) 221 { 222 ASSERT(fSpace == NULL); 223 224 fSpace = VMAddressSpace::Get(team); 225 if (fSpace == NULL) 226 return B_BAD_TEAM_ID; 227 228 fSpace->WriteLock(); 229 fLocked = true; 230 return B_OK; 231 } 232 233 234 void 235 AddressSpaceWriteLocker::SetTo(VMAddressSpace* space, bool getNewReference) 236 { 237 ASSERT(fSpace == NULL); 238 239 fSpace = space; 240 241 if (getNewReference) 242 fSpace->Get(); 243 244 fSpace->WriteLock(); 245 fLocked = true; 246 } 247 248 249 status_t 250 AddressSpaceWriteLocker::SetFromArea(area_id areaID, VMArea*& area) 251 { 252 ASSERT(fSpace == NULL); 253 254 fSpace = GetAddressSpaceByAreaID(areaID); 255 if (fSpace == NULL) 256 return B_BAD_VALUE; 257 258 fSpace->WriteLock(); 259 260 area = VMAreas::Lookup(areaID); 261 262 if (area == NULL || area->address_space != fSpace) { 263 fSpace->WriteUnlock(); 264 return B_BAD_VALUE; 265 } 266 267 fLocked = true; 268 return B_OK; 269 } 270 271 272 status_t 273 AddressSpaceWriteLocker::SetFromArea(team_id team, area_id areaID, 274 bool allowKernel, VMArea*& area) 275 { 276 ASSERT(fSpace == NULL); 277 278 VMAreas::ReadLock(); 279 280 area = VMAreas::LookupLocked(areaID); 281 if (area != NULL 282 && (area->address_space->ID() == team 283 || (allowKernel && team == VMAddressSpace::KernelID()))) { 284 fSpace = area->address_space; 285 fSpace->Get(); 286 } 287 288 VMAreas::ReadUnlock(); 289 290 if (fSpace == NULL) 291 return B_BAD_VALUE; 292 293 // Second try to get the area -- this time with the address space 294 // write lock held 295 296 fSpace->WriteLock(); 297 298 area = VMAreas::Lookup(areaID); 299 300 if (area == NULL) { 301 fSpace->WriteUnlock(); 302 return B_BAD_VALUE; 303 } 304 305 fLocked = true; 306 return B_OK; 307 } 308 309 310 status_t 311 AddressSpaceWriteLocker::SetFromArea(team_id team, area_id areaID, 312 VMArea*& area) 313 { 314 return SetFromArea(team, areaID, false, area); 315 } 316 317 318 void 319 AddressSpaceWriteLocker::Unlock() 320 { 321 if (fLocked) { 322 if (fDegraded) 323 fSpace->ReadUnlock(); 324 else 325 fSpace->WriteUnlock(); 326 fLocked = false; 327 fDegraded = false; 328 } 329 } 330 331 332 void 333 AddressSpaceWriteLocker::DegradeToReadLock() 334 { 335 fSpace->ReadLock(); 336 fSpace->WriteUnlock(); 337 fDegraded = true; 338 } 339 340 341 // #pragma mark - MultiAddressSpaceLocker 342 343 344 MultiAddressSpaceLocker::MultiAddressSpaceLocker() 345 : 346 fItems(NULL), 347 fCapacity(0), 348 fCount(0), 349 fLocked(false) 350 { 351 } 352 353 354 MultiAddressSpaceLocker::~MultiAddressSpaceLocker() 355 { 356 Unset(); 357 free(fItems); 358 } 359 360 361 /*static*/ int 362 MultiAddressSpaceLocker::_CompareItems(const void* _a, const void* _b) 363 { 364 lock_item* a = (lock_item*)_a; 365 lock_item* b = (lock_item*)_b; 366 return b->space->ID() - a->space->ID(); 367 // descending order, i.e. kernel address space last 368 } 369 370 371 bool 372 MultiAddressSpaceLocker::_ResizeIfNeeded() 373 { 374 if (fCount == fCapacity) { 375 lock_item* items = (lock_item*)realloc(fItems, 376 (fCapacity + 4) * sizeof(lock_item)); 377 if (items == NULL) 378 return false; 379 380 fCapacity += 4; 381 fItems = items; 382 } 383 384 return true; 385 } 386 387 388 int32 389 MultiAddressSpaceLocker::_IndexOfAddressSpace(VMAddressSpace* space) const 390 { 391 for (int32 i = 0; i < fCount; i++) { 392 if (fItems[i].space == space) 393 return i; 394 } 395 396 return -1; 397 } 398 399 400 status_t 401 MultiAddressSpaceLocker::_AddAddressSpace(VMAddressSpace* space, 402 bool writeLock, VMAddressSpace** _space) 403 { 404 if (!space) 405 return B_BAD_VALUE; 406 407 int32 index = _IndexOfAddressSpace(space); 408 if (index < 0) { 409 if (!_ResizeIfNeeded()) { 410 space->Put(); 411 return B_NO_MEMORY; 412 } 413 414 lock_item& item = fItems[fCount++]; 415 item.space = space; 416 item.write_lock = writeLock; 417 } else { 418 419 // one reference is enough 420 space->Put(); 421 422 fItems[index].write_lock |= writeLock; 423 } 424 425 if (_space != NULL) 426 *_space = space; 427 428 return B_OK; 429 } 430 431 432 void 433 MultiAddressSpaceLocker::Unset() 434 { 435 Unlock(); 436 437 for (int32 i = 0; i < fCount; i++) 438 fItems[i].space->Put(); 439 440 fCount = 0; 441 } 442 443 444 status_t 445 MultiAddressSpaceLocker::Lock() 446 { 447 ASSERT(!fLocked); 448 449 qsort(fItems, fCount, sizeof(lock_item), &_CompareItems); 450 451 for (int32 i = 0; i < fCount; i++) { 452 status_t status; 453 if (fItems[i].write_lock) 454 status = fItems[i].space->WriteLock(); 455 else 456 status = fItems[i].space->ReadLock(); 457 458 if (status < B_OK) { 459 while (--i >= 0) { 460 if (fItems[i].write_lock) 461 fItems[i].space->WriteUnlock(); 462 else 463 fItems[i].space->ReadUnlock(); 464 } 465 return status; 466 } 467 } 468 469 fLocked = true; 470 return B_OK; 471 } 472 473 474 void 475 MultiAddressSpaceLocker::Unlock() 476 { 477 if (!fLocked) 478 return; 479 480 for (int32 i = 0; i < fCount; i++) { 481 if (fItems[i].write_lock) 482 fItems[i].space->WriteUnlock(); 483 else 484 fItems[i].space->ReadUnlock(); 485 } 486 487 fLocked = false; 488 } 489 490 491 /*! Adds all address spaces of the areas associated with the given area's cache, 492 locks them, and locks the cache (including a reference to it). It retries 493 until the situation is stable (i.e. the neither cache nor cache's areas 494 changed) or an error occurs. 495 */ 496 status_t 497 MultiAddressSpaceLocker::AddAreaCacheAndLock(area_id areaID, 498 bool writeLockThisOne, bool writeLockOthers, VMArea*& _area, 499 VMCache** _cache) 500 { 501 // remember the original state 502 int originalCount = fCount; 503 lock_item* originalItems = NULL; 504 if (fCount > 0) { 505 originalItems = new(nothrow) lock_item[fCount]; 506 if (originalItems == NULL) 507 return B_NO_MEMORY; 508 memcpy(originalItems, fItems, fCount * sizeof(lock_item)); 509 } 510 ArrayDeleter<lock_item> _(originalItems); 511 512 // get the cache 513 VMCache* cache; 514 VMArea* area; 515 status_t error; 516 { 517 AddressSpaceReadLocker locker; 518 error = locker.SetFromArea(areaID, area); 519 if (error != B_OK) 520 return error; 521 522 cache = vm_area_get_locked_cache(area); 523 } 524 525 while (true) { 526 // add all areas 527 VMArea* firstArea = cache->areas; 528 for (VMArea* current = firstArea; current; 529 current = current->cache_next) { 530 error = AddArea(current, 531 current == area ? writeLockThisOne : writeLockOthers); 532 if (error != B_OK) { 533 vm_area_put_locked_cache(cache); 534 return error; 535 } 536 } 537 538 // unlock the cache and attempt to lock the address spaces 539 vm_area_put_locked_cache(cache); 540 541 error = Lock(); 542 if (error != B_OK) 543 return error; 544 545 // lock the cache again and check whether anything has changed 546 547 // check whether the area is gone in the meantime 548 area = VMAreas::Lookup(areaID); 549 550 if (area == NULL) { 551 Unlock(); 552 return B_BAD_VALUE; 553 } 554 555 // lock the cache 556 VMCache* oldCache = cache; 557 cache = vm_area_get_locked_cache(area); 558 559 // If neither the area's cache has changed nor its area list we're 560 // done. 561 if (cache == oldCache && firstArea == cache->areas) { 562 _area = area; 563 if (_cache != NULL) 564 *_cache = cache; 565 return B_OK; 566 } 567 568 // Restore the original state and try again. 569 570 // Unlock the address spaces, but keep the cache locked for the next 571 // iteration. 572 Unlock(); 573 574 // Get an additional reference to the original address spaces. 575 for (int32 i = 0; i < originalCount; i++) 576 originalItems[i].space->Get(); 577 578 // Release all references to the current address spaces. 579 for (int32 i = 0; i < fCount; i++) 580 fItems[i].space->Put(); 581 582 // Copy over the original state. 583 fCount = originalCount; 584 if (originalItems != NULL) 585 memcpy(fItems, originalItems, fCount * sizeof(lock_item)); 586 } 587 } 588