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*
GetAddressSpaceByAreaID(area_id id)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
AddressSpaceReadLocker(team_id team)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 */
AddressSpaceReadLocker(VMAddressSpace * space,bool getNewReference)55 AddressSpaceReadLocker::AddressSpaceReadLocker(VMAddressSpace* space,
56 bool getNewReference)
57 :
58 fSpace(NULL),
59 fLocked(false)
60 {
61 SetTo(space, getNewReference);
62 }
63
64
AddressSpaceReadLocker()65 AddressSpaceReadLocker::AddressSpaceReadLocker()
66 :
67 fSpace(NULL),
68 fLocked(false)
69 {
70 }
71
72
~AddressSpaceReadLocker()73 AddressSpaceReadLocker::~AddressSpaceReadLocker()
74 {
75 Unset();
76 }
77
78
79 void
Unset()80 AddressSpaceReadLocker::Unset()
81 {
82 Unlock();
83 if (fSpace != NULL)
84 fSpace->Put();
85 fSpace = NULL;
86 }
87
88
89 status_t
SetTo(team_id team)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
SetTo(VMAddressSpace * space,bool getNewReference)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
SetFromArea(area_id areaID,VMArea * & area)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
Lock()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
Unlock()161 AddressSpaceReadLocker::Unlock()
162 {
163 if (fLocked) {
164 fSpace->ReadUnlock();
165 fLocked = false;
166 }
167 }
168
169
170 // #pragma mark - AddressSpaceWriteLocker
171
172
AddressSpaceWriteLocker(team_id team)173 AddressSpaceWriteLocker::AddressSpaceWriteLocker(team_id team)
174 :
175 fSpace(NULL),
176 fLocked(false),
177 fDegraded(false)
178 {
179 SetTo(team);
180 }
181
182
AddressSpaceWriteLocker(VMAddressSpace * space,bool getNewReference)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
AddressSpaceWriteLocker()194 AddressSpaceWriteLocker::AddressSpaceWriteLocker()
195 :
196 fSpace(NULL),
197 fLocked(false),
198 fDegraded(false)
199 {
200 }
201
202
~AddressSpaceWriteLocker()203 AddressSpaceWriteLocker::~AddressSpaceWriteLocker()
204 {
205 Unset();
206 }
207
208
209 void
Unset()210 AddressSpaceWriteLocker::Unset()
211 {
212 Unlock();
213 if (fSpace != NULL)
214 fSpace->Put();
215 fSpace = NULL;
216 }
217
218
219 status_t
SetTo(team_id team)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
SetTo(VMAddressSpace * space,bool getNewReference)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
SetFromArea(area_id areaID,VMArea * & area)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
SetFromArea(team_id team,area_id areaID,bool allowKernel,VMArea * & area)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
SetFromArea(team_id team,area_id areaID,VMArea * & area)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
Unlock()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
DegradeToReadLock()333 AddressSpaceWriteLocker::DegradeToReadLock()
334 {
335 fSpace->ReadLock();
336 fSpace->WriteUnlock();
337 fDegraded = true;
338 }
339
340
341 // #pragma mark - MultiAddressSpaceLocker
342
343
MultiAddressSpaceLocker()344 MultiAddressSpaceLocker::MultiAddressSpaceLocker()
345 :
346 fItems(NULL),
347 fCapacity(0),
348 fCount(0),
349 fLocked(false)
350 {
351 }
352
353
~MultiAddressSpaceLocker()354 MultiAddressSpaceLocker::~MultiAddressSpaceLocker()
355 {
356 Unset();
357 free(fItems);
358 }
359
360
361 /*static*/ int
_CompareItems(const void * _a,const void * _b)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
_ResizeIfNeeded()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
_IndexOfAddressSpace(VMAddressSpace * space) const389 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
_AddAddressSpace(VMAddressSpace * space,bool writeLock,VMAddressSpace ** _space)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
Unset()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
Lock()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
Unlock()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
AddAreaCacheAndLock(area_id areaID,bool writeLockThisOne,bool writeLockOthers,VMArea * & _area,VMCache ** _cache)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