diff --git a/headers/private/kernel/vm/VMCache.h b/headers/private/kernel/vm/VMCache.h index 0aee4f9210..2db12cf3fe 100644 --- a/headers/private/kernel/vm/VMCache.h +++ b/headers/private/kernel/vm/VMCache.h @@ -135,6 +135,8 @@ public: virtual status_t Adopt(VMCache* source, off_t offset, off_t size, off_t newOffset); + virtual status_t Discard(off_t offset, off_t size); + status_t FlushAndRemoveAllPages(); void* UserData() { return fUserData; } diff --git a/src/system/kernel/vm/VMAnonymousCache.cpp b/src/system/kernel/vm/VMAnonymousCache.cpp index b5d421f8a4..5baad4c2d0 100644 --- a/src/system/kernel/vm/VMAnonymousCache.cpp +++ b/src/system/kernel/vm/VMAnonymousCache.cpp @@ -554,6 +554,14 @@ VMAnonymousCache::Rebase(off_t newBase, int priority) } +status_t +VMAnonymousCache::Discard(off_t offset, off_t size) +{ + _FreeSwapPageRange(offset, offset + size); + return VMCache::Discard(offset, size); +} + + /*! Moves the swap pages for the given range from the source cache into this cache. Both caches must be locked. */ diff --git a/src/system/kernel/vm/VMAnonymousCache.h b/src/system/kernel/vm/VMAnonymousCache.h index 774c342e33..d463a9705f 100644 --- a/src/system/kernel/vm/VMAnonymousCache.h +++ b/src/system/kernel/vm/VMAnonymousCache.h @@ -44,6 +44,8 @@ public: virtual status_t Adopt(VMCache* source, off_t offset, off_t size, off_t newOffset); + virtual status_t Discard(off_t offset, off_t size); + virtual status_t Commit(off_t size, int priority); virtual bool HasPage(off_t offset); virtual bool DebugHasPage(off_t offset); diff --git a/src/system/kernel/vm/VMCache.cpp b/src/system/kernel/vm/VMCache.cpp index d4e53c4926..0ccd527680 100644 --- a/src/system/kernel/vm/VMCache.cpp +++ b/src/system/kernel/vm/VMCache.cpp @@ -1254,6 +1254,19 @@ VMCache::Adopt(VMCache* source, off_t offset, off_t size, off_t newOffset) } +/*! Discards pages in the given range. */ +status_t +VMCache::Discard(off_t offset, off_t size) +{ + page_num_t startPage = offset >> PAGE_SHIFT; + page_num_t endPage = (offset + size + B_PAGE_SIZE - 1) >> PAGE_SHIFT; + while (_FreePageRange(pages.GetIterator(startPage, true, true), &endPage)) + ; + + return B_OK; +} + + /*! You have to call this function with the VMCache lock held. */ status_t VMCache::FlushAndRemoveAllPages() diff --git a/src/system/kernel/vm/vm.cpp b/src/system/kernel/vm/vm.cpp index fc76b875d8..5048a69cd5 100644 --- a/src/system/kernel/vm/vm.cpp +++ b/src/system/kernel/vm/vm.cpp @@ -865,6 +865,52 @@ unmap_address_range(VMAddressSpace* addressSpace, addr_t address, addr_t size, } +static status_t +discard_area_range(VMArea* area, addr_t address, addr_t size) +{ + addr_t offset; + if (!intersect_area(area, address, size, offset)) + return B_OK; + + // If someone else uses the area's cache or it's not an anonymous cache, we + // can't discard. + VMCache* cache = vm_area_get_locked_cache(area); + if (cache->areas != area || area->cache_next != NULL + || !cache->consumers.IsEmpty() || cache->type != CACHE_TYPE_RAM) { + return B_OK; + } + + VMCacheChainLocker cacheChainLocker(cache); + cacheChainLocker.LockAllSourceCaches(); + + unmap_pages(area, address, size); + + // Since VMCache::Discard() can temporarily drop the lock, we must + // unlock all lower caches to prevent locking order inversion. + cacheChainLocker.Unlock(cache); + cache->Discard(cache->virtual_base + offset, size); + cache->ReleaseRefAndUnlock(); + + return B_OK; +} + + +static status_t +discard_address_range(VMAddressSpace* addressSpace, addr_t address, addr_t size, + bool kernel) +{ + for (VMAddressSpace::AreaRangeIterator it + = addressSpace->GetAreaRangeIterator(address, size); + VMArea* area = it.Next();) { + status_t error = discard_area_range(area, address, size); + if (error != B_OK) + return error; + } + + return B_OK; +} + + /*! You need to hold the lock of the cache and the write lock of the address space when calling this function. Note, that in case of error your cache will be temporarily unlocked.