From 8e74e30784c650255874387077f3c1c9865f2712 Mon Sep 17 00:00:00 2001 From: Michael Lotz Date: Fri, 29 May 2020 15:23:49 +0200 Subject: [PATCH] kernel/vm: Add discard_address_range that discards pages. Pages in the given range are unmapped and freed without getting written back anywhere. It can be used whenever a caller does not care about the data in the given range anymore and wants to reduce page pressure. Change-Id: I8bcce68fab278efef710d3714677e1d463504a56 Reviewed-on: https://review.haiku-os.org/c/haiku/+/2843 Reviewed-by: waddlesplash --- headers/private/kernel/vm/VMCache.h | 2 + src/system/kernel/vm/VMAnonymousCache.cpp | 8 ++++ src/system/kernel/vm/VMAnonymousCache.h | 2 + src/system/kernel/vm/VMCache.cpp | 13 +++++++ src/system/kernel/vm/vm.cpp | 46 +++++++++++++++++++++++ 5 files changed, 71 insertions(+) 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.