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