xref: /haiku/src/system/kernel/vm/VMAddressSpaceLocking.cpp (revision 9a6a20d4689307142a7ed26a1437ba47e244e73f)
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