mirror of
https://github.com/RGBCube/serenity
synced 2025-06-29 01:32:07 +00:00
Kernel: Make purgeable memory a VMObject level concept (again)
This patch changes the semantics of purgeable memory. - AnonymousVMObject now has a "purgeable" flag. It can only be set when constructing the object. (Previously, all anonymous memory was effectively purgeable.) - AnonymousVMObject now has a "volatile" flag. It covers the entire range of physical pages. (Previously, we tracked ranges of volatile pages, effectively making it a page-level concept.) - Non-volatile objects maintain a physical page reservation via the committed pages mechanism, to ensure full coverage for page faults. - When an object is made volatile, it relinquishes any unused committed pages immediately. If later made non-volatile again, we then attempt to make a new committed pages reservation. If this fails, we return ENOMEM to userspace. mmap() now creates purgeable objects if passed the MAP_PURGEABLE option together with MAP_ANONYMOUS. anon_create() memory is always purgeable.
This commit is contained in:
parent
6bb53d6a80
commit
2d1a651e0a
17 changed files with 189 additions and 1004 deletions
|
@ -264,7 +264,6 @@ set(KERNEL_SOURCES
|
||||||
VM/PhysicalZone.cpp
|
VM/PhysicalZone.cpp
|
||||||
VM/PrivateInodeVMObject.cpp
|
VM/PrivateInodeVMObject.cpp
|
||||||
VM/ProcessPagingScope.cpp
|
VM/ProcessPagingScope.cpp
|
||||||
VM/PurgeablePageRanges.cpp
|
|
||||||
VM/Range.cpp
|
VM/Range.cpp
|
||||||
VM/RangeAllocator.cpp
|
VM/RangeAllocator.cpp
|
||||||
VM/Region.cpp
|
VM/Region.cpp
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Kernel/FileSystem/File.h>
|
#include <Kernel/FileSystem/File.h>
|
||||||
|
#include <Kernel/VM/AnonymousVMObject.h>
|
||||||
|
|
||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
|
|
||||||
|
|
|
@ -456,7 +456,7 @@ private:
|
||||||
region_object.add("syscall", region->is_syscall_region());
|
region_object.add("syscall", region->is_syscall_region());
|
||||||
region_object.add("purgeable", region->vmobject().is_anonymous());
|
region_object.add("purgeable", region->vmobject().is_anonymous());
|
||||||
if (region->vmobject().is_anonymous()) {
|
if (region->vmobject().is_anonymous()) {
|
||||||
region_object.add("volatile", static_cast<const AnonymousVMObject&>(region->vmobject()).is_any_volatile());
|
region_object.add("volatile", static_cast<AnonymousVMObject const&>(region->vmobject()).is_volatile());
|
||||||
}
|
}
|
||||||
region_object.add("cacheable", region->is_cacheable());
|
region_object.add("cacheable", region->is_cacheable());
|
||||||
region_object.add("address", region->vaddr().get());
|
region_object.add("address", region->vaddr().get());
|
||||||
|
|
|
@ -29,7 +29,7 @@ KResultOr<FlatPtr> Process::sys$anon_create(size_t size, int options)
|
||||||
if (new_fd < 0)
|
if (new_fd < 0)
|
||||||
return new_fd;
|
return new_fd;
|
||||||
|
|
||||||
auto vmobject = AnonymousVMObject::try_create_with_size(size, AllocationStrategy::Reserve);
|
auto vmobject = AnonymousVMObject::try_create_purgeable_with_size(size, AllocationStrategy::Reserve);
|
||||||
if (!vmobject)
|
if (!vmobject)
|
||||||
return ENOMEM;
|
return ENOMEM;
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include <Kernel/PerformanceEventBuffer.h>
|
#include <Kernel/PerformanceEventBuffer.h>
|
||||||
#include <Kernel/PerformanceManager.h>
|
#include <Kernel/PerformanceManager.h>
|
||||||
#include <Kernel/Process.h>
|
#include <Kernel/Process.h>
|
||||||
|
#include <Kernel/VM/AnonymousVMObject.h>
|
||||||
#include <Kernel/VM/MemoryManager.h>
|
#include <Kernel/VM/MemoryManager.h>
|
||||||
#include <Kernel/VM/PageDirectory.h>
|
#include <Kernel/VM/PageDirectory.h>
|
||||||
#include <Kernel/VM/PrivateInodeVMObject.h>
|
#include <Kernel/VM/PrivateInodeVMObject.h>
|
||||||
|
@ -217,7 +218,14 @@ KResultOr<FlatPtr> Process::sys$mmap(Userspace<const Syscall::SC_mmap_params*> u
|
||||||
|
|
||||||
if (map_anonymous) {
|
if (map_anonymous) {
|
||||||
auto strategy = map_noreserve ? AllocationStrategy::None : AllocationStrategy::Reserve;
|
auto strategy = map_noreserve ? AllocationStrategy::None : AllocationStrategy::Reserve;
|
||||||
auto region_or_error = space().allocate_region(range.value(), {}, prot, strategy);
|
RefPtr<AnonymousVMObject> vmobject;
|
||||||
|
if (flags & MAP_PURGEABLE)
|
||||||
|
vmobject = AnonymousVMObject::try_create_purgeable_with_size(page_round_up(size), strategy);
|
||||||
|
else
|
||||||
|
vmobject = AnonymousVMObject::try_create_with_size(page_round_up(size), strategy);
|
||||||
|
if (!vmobject)
|
||||||
|
return ENOMEM;
|
||||||
|
auto region_or_error = space().allocate_region_with_vmobject(range.value(), vmobject.release_nonnull(), 0, {}, prot, map_shared);
|
||||||
if (region_or_error.is_error())
|
if (region_or_error.is_error())
|
||||||
return region_or_error.error().error();
|
return region_or_error.error().error();
|
||||||
region = region_or_error.value();
|
region = region_or_error.value();
|
||||||
|
@ -465,23 +473,17 @@ KResultOr<FlatPtr> Process::sys$madvise(Userspace<void*> address, size_t size, i
|
||||||
if (set_volatile || set_nonvolatile) {
|
if (set_volatile || set_nonvolatile) {
|
||||||
if (!region->vmobject().is_anonymous())
|
if (!region->vmobject().is_anonymous())
|
||||||
return EPERM;
|
return EPERM;
|
||||||
|
auto& vmobject = static_cast<AnonymousVMObject&>(region->vmobject());
|
||||||
bool was_purged = false;
|
bool was_purged = false;
|
||||||
switch (region->set_volatile(VirtualAddress(address), size, set_volatile, was_purged)) {
|
auto result = vmobject.set_volatile(set_volatile, was_purged);
|
||||||
case Region::SetVolatileError::Success:
|
if (result.is_error())
|
||||||
break;
|
return result.error();
|
||||||
case Region::SetVolatileError::NotPurgeable:
|
return was_purged ? 1 : 0;
|
||||||
return EPERM;
|
|
||||||
case Region::SetVolatileError::OutOfMemory:
|
|
||||||
return ENOMEM;
|
|
||||||
}
|
|
||||||
if (set_nonvolatile)
|
|
||||||
return was_purged ? 1 : 0;
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
if (advice & MADV_GET_VOLATILE) {
|
if (advice & MADV_GET_VOLATILE) {
|
||||||
if (!region->vmobject().is_anonymous())
|
if (!region->vmobject().is_anonymous())
|
||||||
return EPERM;
|
return EPERM;
|
||||||
return region->is_volatile(VirtualAddress(address), size) ? 0 : 1;
|
return static_cast<AnonymousVMObject&>(region->vmobject()).is_volatile() ? 0 : 1;
|
||||||
}
|
}
|
||||||
return EINVAL;
|
return EINVAL;
|
||||||
}
|
}
|
||||||
|
@ -668,5 +670,4 @@ KResultOr<FlatPtr> Process::sys$msyscall(Userspace<void*> address)
|
||||||
region->set_syscall_region(true);
|
region->set_syscall_region(true);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ KResultOr<FlatPtr> Process::sys$purge(int mode)
|
||||||
REQUIRE_NO_PROMISES;
|
REQUIRE_NO_PROMISES;
|
||||||
if (!is_superuser())
|
if (!is_superuser())
|
||||||
return EPERM;
|
return EPERM;
|
||||||
int purged_page_count = 0;
|
size_t purged_page_count = 0;
|
||||||
if (mode & PURGE_ALL_VOLATILE) {
|
if (mode & PURGE_ALL_VOLATILE) {
|
||||||
NonnullRefPtrVector<AnonymousVMObject> vmobjects;
|
NonnullRefPtrVector<AnonymousVMObject> vmobjects;
|
||||||
{
|
{
|
||||||
|
|
|
@ -92,6 +92,7 @@ enum {
|
||||||
#define MAP_STACK 0x40
|
#define MAP_STACK 0x40
|
||||||
#define MAP_NORESERVE 0x80
|
#define MAP_NORESERVE 0x80
|
||||||
#define MAP_RANDOMIZED 0x100
|
#define MAP_RANDOMIZED 0x100
|
||||||
|
#define MAP_PURGEABLE 0x200
|
||||||
|
|
||||||
#define PROT_READ 0x1
|
#define PROT_READ 0x1
|
||||||
#define PROT_WRITE 0x2
|
#define PROT_WRITE 0x2
|
||||||
|
|
|
@ -22,16 +22,17 @@ RefPtr<VMObject> AnonymousVMObject::try_clone()
|
||||||
// commit the number of pages that we need to potentially allocate
|
// commit the number of pages that we need to potentially allocate
|
||||||
// so that the parent is still guaranteed to be able to have all
|
// so that the parent is still guaranteed to be able to have all
|
||||||
// non-volatile memory available.
|
// non-volatile memory available.
|
||||||
size_t need_cow_pages = 0;
|
size_t new_cow_pages_needed = 0;
|
||||||
|
|
||||||
// We definitely need to commit non-volatile areas
|
if (is_volatile()) {
|
||||||
for_each_nonvolatile_range([&](VolatilePageRange const& nonvolatile_range) {
|
// NOTE: If this object is currently volatile, we don't own any committed pages.
|
||||||
need_cow_pages += nonvolatile_range.count;
|
} else {
|
||||||
});
|
new_cow_pages_needed = page_count();
|
||||||
|
}
|
||||||
|
|
||||||
dbgln_if(COMMIT_DEBUG, "Cloning {:p}, need {} committed cow pages", this, need_cow_pages);
|
dbgln_if(COMMIT_DEBUG, "Cloning {:p}, need {} committed cow pages", this, new_cow_pages_needed);
|
||||||
|
|
||||||
if (!MM.commit_user_physical_pages(need_cow_pages))
|
if (!MM.commit_user_physical_pages(new_cow_pages_needed))
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
// Create or replace the committed cow pages. When cloning a previously
|
// Create or replace the committed cow pages. When cloning a previously
|
||||||
|
@ -40,10 +41,10 @@ RefPtr<VMObject> AnonymousVMObject::try_clone()
|
||||||
// one would keep the one it still has. This ensures that the original
|
// one would keep the one it still has. This ensures that the original
|
||||||
// one and this one, as well as the clone have sufficient resources
|
// one and this one, as well as the clone have sufficient resources
|
||||||
// to cow all pages as needed
|
// to cow all pages as needed
|
||||||
m_shared_committed_cow_pages = try_create<CommittedCowPages>(need_cow_pages);
|
m_shared_committed_cow_pages = try_create<CommittedCowPages>(new_cow_pages_needed);
|
||||||
|
|
||||||
if (!m_shared_committed_cow_pages) {
|
if (!m_shared_committed_cow_pages) {
|
||||||
MM.uncommit_user_physical_pages(need_cow_pages);
|
MM.uncommit_user_physical_pages(new_cow_pages_needed);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,6 +66,20 @@ RefPtr<AnonymousVMObject> AnonymousVMObject::try_create_with_size(size_t size, A
|
||||||
return adopt_ref_if_nonnull(new (nothrow) AnonymousVMObject(size, commit));
|
return adopt_ref_if_nonnull(new (nothrow) AnonymousVMObject(size, commit));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RefPtr<AnonymousVMObject> AnonymousVMObject::try_create_purgeable_with_size(size_t size, AllocationStrategy commit)
|
||||||
|
{
|
||||||
|
if (commit == AllocationStrategy::Reserve || commit == AllocationStrategy::AllocateNow) {
|
||||||
|
// We need to attempt to commit before actually creating the object
|
||||||
|
if (!MM.commit_user_physical_pages(ceil_div(size, static_cast<size_t>(PAGE_SIZE))))
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto vmobject = adopt_ref_if_nonnull(new (nothrow) AnonymousVMObject(size, commit));
|
||||||
|
if (!vmobject)
|
||||||
|
return {};
|
||||||
|
vmobject->m_purgeable = true;
|
||||||
|
return vmobject;
|
||||||
|
}
|
||||||
|
|
||||||
RefPtr<AnonymousVMObject> AnonymousVMObject::try_create_with_physical_pages(Span<NonnullRefPtr<PhysicalPage>> physical_pages)
|
RefPtr<AnonymousVMObject> AnonymousVMObject::try_create_with_physical_pages(Span<NonnullRefPtr<PhysicalPage>> physical_pages)
|
||||||
{
|
{
|
||||||
return adopt_ref_if_nonnull(new (nothrow) AnonymousVMObject(physical_pages));
|
return adopt_ref_if_nonnull(new (nothrow) AnonymousVMObject(physical_pages));
|
||||||
|
@ -81,7 +96,6 @@ RefPtr<AnonymousVMObject> AnonymousVMObject::try_create_for_physical_range(Physi
|
||||||
|
|
||||||
AnonymousVMObject::AnonymousVMObject(size_t size, AllocationStrategy strategy)
|
AnonymousVMObject::AnonymousVMObject(size_t size, AllocationStrategy strategy)
|
||||||
: VMObject(size)
|
: VMObject(size)
|
||||||
, m_volatile_ranges_cache({ 0, page_count() })
|
|
||||||
, m_unused_committed_pages(strategy == AllocationStrategy::Reserve ? page_count() : 0)
|
, m_unused_committed_pages(strategy == AllocationStrategy::Reserve ? page_count() : 0)
|
||||||
{
|
{
|
||||||
if (strategy == AllocationStrategy::AllocateNow) {
|
if (strategy == AllocationStrategy::AllocateNow) {
|
||||||
|
@ -97,7 +111,6 @@ AnonymousVMObject::AnonymousVMObject(size_t size, AllocationStrategy strategy)
|
||||||
|
|
||||||
AnonymousVMObject::AnonymousVMObject(PhysicalAddress paddr, size_t size)
|
AnonymousVMObject::AnonymousVMObject(PhysicalAddress paddr, size_t size)
|
||||||
: VMObject(size)
|
: VMObject(size)
|
||||||
, m_volatile_ranges_cache({ 0, page_count() })
|
|
||||||
{
|
{
|
||||||
VERIFY(paddr.page_base() == paddr);
|
VERIFY(paddr.page_base() == paddr);
|
||||||
for (size_t i = 0; i < page_count(); ++i)
|
for (size_t i = 0; i < page_count(); ++i)
|
||||||
|
@ -106,7 +119,6 @@ AnonymousVMObject::AnonymousVMObject(PhysicalAddress paddr, size_t size)
|
||||||
|
|
||||||
AnonymousVMObject::AnonymousVMObject(Span<NonnullRefPtr<PhysicalPage>> physical_pages)
|
AnonymousVMObject::AnonymousVMObject(Span<NonnullRefPtr<PhysicalPage>> physical_pages)
|
||||||
: VMObject(physical_pages.size() * PAGE_SIZE)
|
: VMObject(physical_pages.size() * PAGE_SIZE)
|
||||||
, m_volatile_ranges_cache({ 0, page_count() })
|
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < physical_pages.size(); ++i) {
|
for (size_t i = 0; i < physical_pages.size(); ++i) {
|
||||||
m_physical_pages[i] = physical_pages[i];
|
m_physical_pages[i] = physical_pages[i];
|
||||||
|
@ -115,9 +127,6 @@ AnonymousVMObject::AnonymousVMObject(Span<NonnullRefPtr<PhysicalPage>> physical_
|
||||||
|
|
||||||
AnonymousVMObject::AnonymousVMObject(AnonymousVMObject const& other)
|
AnonymousVMObject::AnonymousVMObject(AnonymousVMObject const& other)
|
||||||
: VMObject(other)
|
: VMObject(other)
|
||||||
, m_volatile_ranges_cache({ 0, page_count() }) // do *not* clone this
|
|
||||||
, m_volatile_ranges_cache_dirty(true) // do *not* clone this
|
|
||||||
, m_purgeable_ranges() // do *not* clone this
|
|
||||||
, m_unused_committed_pages(other.m_unused_committed_pages)
|
, m_unused_committed_pages(other.m_unused_committed_pages)
|
||||||
, m_cow_map() // do *not* clone this
|
, m_cow_map() // do *not* clone this
|
||||||
, m_shared_committed_cow_pages(other.m_shared_committed_cow_pages) // share the pool
|
, m_shared_committed_cow_pages(other.m_shared_committed_cow_pages) // share the pool
|
||||||
|
@ -152,217 +161,94 @@ AnonymousVMObject::~AnonymousVMObject()
|
||||||
MM.uncommit_user_physical_pages(m_unused_committed_pages);
|
MM.uncommit_user_physical_pages(m_unused_committed_pages);
|
||||||
}
|
}
|
||||||
|
|
||||||
int AnonymousVMObject::purge()
|
size_t AnonymousVMObject::purge()
|
||||||
{
|
|
||||||
int purged_page_count = 0;
|
|
||||||
ScopedSpinLock lock(m_lock);
|
|
||||||
for_each_volatile_range([&](auto const& range) {
|
|
||||||
int purged_in_range = 0;
|
|
||||||
auto range_end = range.base + range.count;
|
|
||||||
for (size_t i = range.base; i < range_end; i++) {
|
|
||||||
auto& phys_page = m_physical_pages[i];
|
|
||||||
if (phys_page && !phys_page->is_shared_zero_page()) {
|
|
||||||
VERIFY(!phys_page->is_lazy_committed_page());
|
|
||||||
++purged_in_range;
|
|
||||||
}
|
|
||||||
phys_page = MM.shared_zero_page();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (purged_in_range > 0) {
|
|
||||||
purged_page_count += purged_in_range;
|
|
||||||
set_was_purged(range);
|
|
||||||
for_each_region([&](auto& region) {
|
|
||||||
if (auto owner = region.get_owner()) {
|
|
||||||
// we need to hold a reference the process here (if there is one) as we may not own this region
|
|
||||||
dmesgln("Purged {} pages from region {} owned by {} at {} - {}",
|
|
||||||
purged_in_range,
|
|
||||||
region.name(),
|
|
||||||
*owner,
|
|
||||||
region.vaddr_from_page_index(range.base),
|
|
||||||
region.vaddr_from_page_index(range.base + range.count));
|
|
||||||
} else {
|
|
||||||
dmesgln("Purged {} pages from region {} (no ownership) at {} - {}",
|
|
||||||
purged_in_range,
|
|
||||||
region.name(),
|
|
||||||
region.vaddr_from_page_index(range.base),
|
|
||||||
region.vaddr_from_page_index(range.base + range.count));
|
|
||||||
}
|
|
||||||
region.remap_vmobject_page_range(range.base, range.count);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return purged_page_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AnonymousVMObject::set_was_purged(VolatilePageRange const& range)
|
|
||||||
{
|
|
||||||
VERIFY(m_lock.is_locked());
|
|
||||||
for (auto* purgeable_ranges : m_purgeable_ranges)
|
|
||||||
purgeable_ranges->set_was_purged(range);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AnonymousVMObject::register_purgeable_page_ranges(PurgeablePageRanges& purgeable_page_ranges)
|
|
||||||
{
|
{
|
||||||
ScopedSpinLock lock(m_lock);
|
ScopedSpinLock lock(m_lock);
|
||||||
purgeable_page_ranges.set_vmobject(this);
|
|
||||||
VERIFY(!m_purgeable_ranges.contains_slow(&purgeable_page_ranges));
|
|
||||||
m_purgeable_ranges.append(&purgeable_page_ranges);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AnonymousVMObject::unregister_purgeable_page_ranges(PurgeablePageRanges& purgeable_page_ranges)
|
if (!is_purgeable() || !is_volatile())
|
||||||
{
|
return 0;
|
||||||
ScopedSpinLock lock(m_lock);
|
|
||||||
for (size_t i = 0; i < m_purgeable_ranges.size(); i++) {
|
size_t total_pages_purged = 0;
|
||||||
if (m_purgeable_ranges[i] != &purgeable_page_ranges)
|
|
||||||
|
for (auto& page : m_physical_pages) {
|
||||||
|
VERIFY(page);
|
||||||
|
if (page->is_shared_zero_page())
|
||||||
continue;
|
continue;
|
||||||
purgeable_page_ranges.set_vmobject(nullptr);
|
page = MM.shared_zero_page();
|
||||||
m_purgeable_ranges.remove(i);
|
++total_pages_purged;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
VERIFY_NOT_REACHED();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AnonymousVMObject::is_any_volatile() const
|
m_was_purged = true;
|
||||||
{
|
|
||||||
ScopedSpinLock lock(m_lock);
|
|
||||||
for (auto& volatile_ranges : m_purgeable_ranges) {
|
|
||||||
ScopedSpinLock lock(volatile_ranges->m_volatile_ranges_lock);
|
|
||||||
if (!volatile_ranges->is_empty())
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t AnonymousVMObject::remove_lazy_commit_pages(VolatilePageRange const& range)
|
for_each_region([](Region& region) {
|
||||||
{
|
region.remap();
|
||||||
VERIFY(m_lock.is_locked());
|
|
||||||
|
|
||||||
size_t removed_count = 0;
|
|
||||||
auto range_end = range.base + range.count;
|
|
||||||
for (size_t i = range.base; i < range_end; i++) {
|
|
||||||
auto& phys_page = m_physical_pages[i];
|
|
||||||
if (phys_page && phys_page->is_lazy_committed_page()) {
|
|
||||||
phys_page = MM.shared_zero_page();
|
|
||||||
removed_count++;
|
|
||||||
VERIFY(m_unused_committed_pages > 0);
|
|
||||||
if (--m_unused_committed_pages == 0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return removed_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AnonymousVMObject::update_volatile_cache()
|
|
||||||
{
|
|
||||||
VERIFY(m_lock.is_locked());
|
|
||||||
VERIFY(m_volatile_ranges_cache_dirty);
|
|
||||||
|
|
||||||
m_volatile_ranges_cache.clear();
|
|
||||||
for_each_nonvolatile_range([&](VolatilePageRange const& range) {
|
|
||||||
m_volatile_ranges_cache.add_unchecked(range);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
m_volatile_ranges_cache_dirty = false;
|
return total_pages_purged;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnonymousVMObject::range_made_volatile(VolatilePageRange const& range)
|
KResult AnonymousVMObject::set_volatile(bool is_volatile, bool& was_purged)
|
||||||
{
|
{
|
||||||
VERIFY(m_lock.is_locked());
|
VERIFY(is_purgeable());
|
||||||
|
|
||||||
if (m_unused_committed_pages == 0)
|
ScopedSpinLock locker(m_lock);
|
||||||
return;
|
|
||||||
|
|
||||||
// We need to check this range for any pages that are marked for
|
was_purged = m_was_purged;
|
||||||
// lazy committed allocation and turn them into shared zero pages
|
if (m_volatile == is_volatile)
|
||||||
// and also adjust the m_unused_committed_pages for each such page.
|
return KSuccess;
|
||||||
// Take into account all the other views as well.
|
|
||||||
size_t uncommit_page_count = 0;
|
if (is_volatile) {
|
||||||
for_each_volatile_range([&](auto const& r) {
|
// When a VMObject is made volatile, it gives up all of its committed memory.
|
||||||
auto intersected = range.intersected(r);
|
// Any physical pages already allocated remain in the VMObject for now, but the kernel is free to take them at any moment.
|
||||||
if (!intersected.is_empty()) {
|
for (auto& page : m_physical_pages) {
|
||||||
uncommit_page_count += remove_lazy_commit_pages(intersected);
|
if (page && page->is_lazy_committed_page())
|
||||||
if (m_unused_committed_pages == 0)
|
page = MM.shared_zero_page();
|
||||||
return IterationDecision::Break;
|
|
||||||
}
|
}
|
||||||
return IterationDecision::Continue;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Return those committed pages back to the system
|
if (m_unused_committed_pages) {
|
||||||
if (uncommit_page_count > 0) {
|
MM.uncommit_user_physical_pages(m_unused_committed_pages);
|
||||||
dbgln_if(COMMIT_DEBUG, "Uncommit {} lazy-commit pages from {:p}", uncommit_page_count, this);
|
m_unused_committed_pages = 0;
|
||||||
MM.uncommit_user_physical_pages(uncommit_page_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_volatile_ranges_cache_dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AnonymousVMObject::range_made_nonvolatile(VolatilePageRange const&)
|
|
||||||
{
|
|
||||||
VERIFY(m_lock.is_locked());
|
|
||||||
m_volatile_ranges_cache_dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t AnonymousVMObject::count_needed_commit_pages_for_nonvolatile_range(VolatilePageRange const& range)
|
|
||||||
{
|
|
||||||
VERIFY(m_lock.is_locked());
|
|
||||||
VERIFY(!range.is_empty());
|
|
||||||
|
|
||||||
size_t need_commit_pages = 0;
|
|
||||||
auto range_end = range.base + range.count;
|
|
||||||
for (size_t page_index = range.base; page_index < range_end; page_index++) {
|
|
||||||
// COW pages are accounted for in m_shared_committed_cow_pages
|
|
||||||
if (!m_cow_map.is_null() && m_cow_map.get(page_index))
|
|
||||||
continue;
|
|
||||||
auto& phys_page = m_physical_pages[page_index];
|
|
||||||
if (phys_page && phys_page->is_shared_zero_page())
|
|
||||||
need_commit_pages++;
|
|
||||||
}
|
|
||||||
return need_commit_pages;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t AnonymousVMObject::mark_committed_pages_for_nonvolatile_range(VolatilePageRange const& range, size_t mark_total)
|
|
||||||
{
|
|
||||||
VERIFY(m_lock.is_locked());
|
|
||||||
VERIFY(!range.is_empty());
|
|
||||||
VERIFY(mark_total > 0);
|
|
||||||
|
|
||||||
size_t pages_updated = 0;
|
|
||||||
auto range_end = range.base + range.count;
|
|
||||||
for (size_t page_index = range.base; page_index < range_end; page_index++) {
|
|
||||||
// COW pages are accounted for in m_shared_committed_cow_pages
|
|
||||||
if (!m_cow_map.is_null() && m_cow_map.get(page_index))
|
|
||||||
continue;
|
|
||||||
auto& phys_page = m_physical_pages[page_index];
|
|
||||||
if (phys_page && phys_page->is_shared_zero_page()) {
|
|
||||||
phys_page = MM.lazy_committed_page();
|
|
||||||
if (++pages_updated == mark_total)
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_volatile = true;
|
||||||
|
m_was_purged = false;
|
||||||
|
return KSuccess;
|
||||||
|
}
|
||||||
|
// When a VMObject is made non-volatile, we try to commit however many pages are not currently available.
|
||||||
|
// If that fails, we return false to indicate that memory allocation failed.
|
||||||
|
size_t committed_pages_needed = 0;
|
||||||
|
for (auto& page : m_physical_pages) {
|
||||||
|
VERIFY(page);
|
||||||
|
if (page->is_shared_zero_page())
|
||||||
|
++committed_pages_needed;
|
||||||
}
|
}
|
||||||
|
|
||||||
dbgln_if(COMMIT_DEBUG, "Added {} lazy-commit pages to {:p}", pages_updated, this);
|
if (!committed_pages_needed) {
|
||||||
|
m_volatile = false;
|
||||||
|
return KSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
m_unused_committed_pages += pages_updated;
|
if (!MM.commit_user_physical_pages(committed_pages_needed))
|
||||||
return pages_updated;
|
return ENOMEM;
|
||||||
|
|
||||||
|
m_unused_committed_pages = committed_pages_needed;
|
||||||
|
|
||||||
|
for (auto& page : m_physical_pages) {
|
||||||
|
if (page->is_shared_zero_page())
|
||||||
|
page = MM.lazy_committed_page();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_volatile = false;
|
||||||
|
m_was_purged = false;
|
||||||
|
return KSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
NonnullRefPtr<PhysicalPage> AnonymousVMObject::allocate_committed_page(Badge<Region>, size_t page_index)
|
NonnullRefPtr<PhysicalPage> AnonymousVMObject::allocate_committed_page(Badge<Region>)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
ScopedSpinLock lock(m_lock);
|
ScopedSpinLock lock(m_lock);
|
||||||
|
|
||||||
VERIFY(m_unused_committed_pages > 0);
|
VERIFY(m_unused_committed_pages > 0);
|
||||||
|
--m_unused_committed_pages;
|
||||||
// We shouldn't have any committed page tags in volatile regions
|
|
||||||
VERIFY([&]() {
|
|
||||||
for (auto* purgeable_ranges : m_purgeable_ranges) {
|
|
||||||
if (purgeable_ranges->is_volatile(page_index))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}());
|
|
||||||
|
|
||||||
m_unused_committed_pages--;
|
|
||||||
}
|
}
|
||||||
return MM.allocate_committed_user_physical_page(MemoryManager::ShouldZeroFill::Yes);
|
return MM.allocate_committed_user_physical_page(MemoryManager::ShouldZeroFill::Yes);
|
||||||
}
|
}
|
||||||
|
@ -404,19 +290,20 @@ size_t AnonymousVMObject::cow_pages() const
|
||||||
return m_cow_map.count_slow(true);
|
return m_cow_map.count_slow(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AnonymousVMObject::is_nonvolatile(size_t page_index)
|
|
||||||
{
|
|
||||||
if (m_volatile_ranges_cache_dirty)
|
|
||||||
update_volatile_cache();
|
|
||||||
return !m_volatile_ranges_cache.contains(page_index);
|
|
||||||
}
|
|
||||||
|
|
||||||
PageFaultResponse AnonymousVMObject::handle_cow_fault(size_t page_index, VirtualAddress vaddr)
|
PageFaultResponse AnonymousVMObject::handle_cow_fault(size_t page_index, VirtualAddress vaddr)
|
||||||
{
|
{
|
||||||
VERIFY_INTERRUPTS_DISABLED();
|
VERIFY_INTERRUPTS_DISABLED();
|
||||||
ScopedSpinLock lock(m_lock);
|
ScopedSpinLock lock(m_lock);
|
||||||
|
|
||||||
|
if (is_volatile()) {
|
||||||
|
// A COW fault in a volatile region? Userspace is writing to volatile memory, this is a bug. Crash.
|
||||||
|
dbgln("COW fault in volatile region, will crash.");
|
||||||
|
return PageFaultResponse::ShouldCrash;
|
||||||
|
}
|
||||||
|
|
||||||
auto& page_slot = physical_pages()[page_index];
|
auto& page_slot = physical_pages()[page_index];
|
||||||
bool have_committed = m_shared_committed_cow_pages && is_nonvolatile(page_index);
|
bool have_committed = m_shared_committed_cow_pages;
|
||||||
|
|
||||||
if (page_slot->ref_count() == 1) {
|
if (page_slot->ref_count() == 1) {
|
||||||
dbgln_if(PAGE_FAULT_DEBUG, " >> It's a COW page but nobody is sharing it anymore. Remap r/w");
|
dbgln_if(PAGE_FAULT_DEBUG, " >> It's a COW page but nobody is sharing it anymore. Remap r/w");
|
||||||
set_should_cow(page_index, false);
|
set_should_cow(page_index, false);
|
||||||
|
@ -462,4 +349,33 @@ PageFaultResponse AnonymousVMObject::handle_cow_fault(size_t page_index, Virtual
|
||||||
return PageFaultResponse::Continue;
|
return PageFaultResponse::Continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CommittedCowPages::CommittedCowPages(size_t committed_pages)
|
||||||
|
: m_committed_pages(committed_pages)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CommittedCowPages::~CommittedCowPages()
|
||||||
|
{
|
||||||
|
// Return unused committed pages
|
||||||
|
if (m_committed_pages > 0)
|
||||||
|
MM.uncommit_user_physical_pages(m_committed_pages);
|
||||||
|
}
|
||||||
|
|
||||||
|
NonnullRefPtr<PhysicalPage> CommittedCowPages::allocate_one()
|
||||||
|
{
|
||||||
|
VERIFY(m_committed_pages > 0);
|
||||||
|
m_committed_pages--;
|
||||||
|
|
||||||
|
return MM.allocate_committed_user_physical_page(MemoryManager::ShouldZeroFill::Yes);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CommittedCowPages::return_one()
|
||||||
|
{
|
||||||
|
VERIFY(m_committed_pages > 0);
|
||||||
|
m_committed_pages--;
|
||||||
|
|
||||||
|
MM.uncommit_user_physical_pages(1);
|
||||||
|
return m_committed_pages == 0;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,108 +10,48 @@
|
||||||
#include <Kernel/VM/AllocationStrategy.h>
|
#include <Kernel/VM/AllocationStrategy.h>
|
||||||
#include <Kernel/VM/MemoryManager.h>
|
#include <Kernel/VM/MemoryManager.h>
|
||||||
#include <Kernel/VM/PageFaultResponse.h>
|
#include <Kernel/VM/PageFaultResponse.h>
|
||||||
#include <Kernel/VM/PurgeablePageRanges.h>
|
|
||||||
#include <Kernel/VM/VMObject.h>
|
#include <Kernel/VM/VMObject.h>
|
||||||
|
|
||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
|
|
||||||
class AnonymousVMObject final : public VMObject {
|
class CommittedCowPages : public RefCounted<CommittedCowPages> {
|
||||||
friend class PurgeablePageRanges;
|
AK_MAKE_NONCOPYABLE(CommittedCowPages);
|
||||||
|
|
||||||
|
public:
|
||||||
|
CommittedCowPages() = delete;
|
||||||
|
|
||||||
|
CommittedCowPages(size_t);
|
||||||
|
~CommittedCowPages();
|
||||||
|
|
||||||
|
NonnullRefPtr<PhysicalPage> allocate_one();
|
||||||
|
bool return_one();
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t m_committed_pages;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AnonymousVMObject final : public VMObject {
|
||||||
public:
|
public:
|
||||||
virtual ~AnonymousVMObject() override;
|
virtual ~AnonymousVMObject() override;
|
||||||
|
|
||||||
static RefPtr<AnonymousVMObject> try_create_with_size(size_t, AllocationStrategy);
|
static RefPtr<AnonymousVMObject> try_create_with_size(size_t, AllocationStrategy);
|
||||||
static RefPtr<AnonymousVMObject> try_create_for_physical_range(PhysicalAddress paddr, size_t size);
|
static RefPtr<AnonymousVMObject> try_create_for_physical_range(PhysicalAddress paddr, size_t size);
|
||||||
static RefPtr<AnonymousVMObject> try_create_with_physical_pages(Span<NonnullRefPtr<PhysicalPage>>);
|
static RefPtr<AnonymousVMObject> try_create_with_physical_pages(Span<NonnullRefPtr<PhysicalPage>>);
|
||||||
|
static RefPtr<AnonymousVMObject> try_create_purgeable_with_size(size_t, AllocationStrategy);
|
||||||
virtual RefPtr<VMObject> try_clone() override;
|
virtual RefPtr<VMObject> try_clone() override;
|
||||||
|
|
||||||
[[nodiscard]] NonnullRefPtr<PhysicalPage> allocate_committed_page(Badge<Region>, size_t);
|
[[nodiscard]] NonnullRefPtr<PhysicalPage> allocate_committed_page(Badge<Region>);
|
||||||
PageFaultResponse handle_cow_fault(size_t, VirtualAddress);
|
PageFaultResponse handle_cow_fault(size_t, VirtualAddress);
|
||||||
size_t cow_pages() const;
|
size_t cow_pages() const;
|
||||||
bool should_cow(size_t page_index, bool) const;
|
bool should_cow(size_t page_index, bool) const;
|
||||||
void set_should_cow(size_t page_index, bool);
|
void set_should_cow(size_t page_index, bool);
|
||||||
|
|
||||||
void register_purgeable_page_ranges(PurgeablePageRanges&);
|
bool is_purgeable() const { return m_purgeable; }
|
||||||
void unregister_purgeable_page_ranges(PurgeablePageRanges&);
|
bool is_volatile() const { return m_volatile; }
|
||||||
|
|
||||||
int purge();
|
KResult set_volatile(bool is_volatile, bool& was_purged);
|
||||||
|
|
||||||
bool is_any_volatile() const;
|
size_t purge();
|
||||||
|
|
||||||
template<IteratorFunction<VolatilePageRange const&> F>
|
|
||||||
IterationDecision for_each_volatile_range(F f) const
|
|
||||||
{
|
|
||||||
VERIFY(m_lock.is_locked());
|
|
||||||
// This is a little ugly. Basically, we're trying to find the
|
|
||||||
// volatile ranges that all share, because those are the only
|
|
||||||
// pages we can actually purge
|
|
||||||
for (auto* purgeable_range : m_purgeable_ranges) {
|
|
||||||
ScopedSpinLock purgeable_lock(purgeable_range->m_volatile_ranges_lock);
|
|
||||||
for (auto& r1 : purgeable_range->volatile_ranges().ranges()) {
|
|
||||||
VolatilePageRange range(r1);
|
|
||||||
for (auto* purgeable_range2 : m_purgeable_ranges) {
|
|
||||||
if (purgeable_range2 == purgeable_range)
|
|
||||||
continue;
|
|
||||||
ScopedSpinLock purgeable2_lock(purgeable_range2->m_volatile_ranges_lock);
|
|
||||||
if (purgeable_range2->is_empty()) {
|
|
||||||
// If just one doesn't allow any purging, we can
|
|
||||||
// immediately bail
|
|
||||||
return IterationDecision::Continue;
|
|
||||||
}
|
|
||||||
for (auto const& r2 : purgeable_range2->volatile_ranges().ranges()) {
|
|
||||||
range = range.intersected(r2);
|
|
||||||
if (range.is_empty())
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (range.is_empty())
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (range.is_empty())
|
|
||||||
continue;
|
|
||||||
IterationDecision decision = f(range);
|
|
||||||
if (decision != IterationDecision::Continue)
|
|
||||||
return decision;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return IterationDecision::Continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<IteratorFunction<VolatilePageRange const&> F>
|
|
||||||
IterationDecision for_each_nonvolatile_range(F f) const
|
|
||||||
{
|
|
||||||
size_t base = 0;
|
|
||||||
for_each_volatile_range([&](VolatilePageRange const& volatile_range) {
|
|
||||||
if (volatile_range.base == base)
|
|
||||||
return IterationDecision::Continue;
|
|
||||||
IterationDecision decision = f(VolatilePageRange { base, volatile_range.base - base });
|
|
||||||
if (decision != IterationDecision::Continue)
|
|
||||||
return decision;
|
|
||||||
base = volatile_range.base + volatile_range.count;
|
|
||||||
return IterationDecision::Continue;
|
|
||||||
});
|
|
||||||
if (base < page_count())
|
|
||||||
return f(VolatilePageRange { base, page_count() - base });
|
|
||||||
return IterationDecision::Continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<VoidFunction<VolatilePageRange const&> F>
|
|
||||||
IterationDecision for_each_volatile_range(F f) const
|
|
||||||
{
|
|
||||||
return for_each_volatile_range([&](auto& range) {
|
|
||||||
f(range);
|
|
||||||
return IterationDecision::Continue;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
template<VoidFunction<VolatilePageRange const&> F>
|
|
||||||
IterationDecision for_each_nonvolatile_range(F f) const
|
|
||||||
{
|
|
||||||
return for_each_nonvolatile_range([&](auto range) {
|
|
||||||
f(move(range));
|
|
||||||
return IterationDecision::Continue;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit AnonymousVMObject(size_t, AllocationStrategy);
|
explicit AnonymousVMObject(size_t, AllocationStrategy);
|
||||||
|
@ -121,15 +61,6 @@ private:
|
||||||
|
|
||||||
virtual StringView class_name() const override { return "AnonymousVMObject"sv; }
|
virtual StringView class_name() const override { return "AnonymousVMObject"sv; }
|
||||||
|
|
||||||
void update_volatile_cache();
|
|
||||||
void set_was_purged(VolatilePageRange const&);
|
|
||||||
size_t remove_lazy_commit_pages(VolatilePageRange const&);
|
|
||||||
void range_made_volatile(VolatilePageRange const&);
|
|
||||||
void range_made_nonvolatile(VolatilePageRange const&);
|
|
||||||
size_t count_needed_commit_pages_for_nonvolatile_range(VolatilePageRange const&);
|
|
||||||
size_t mark_committed_pages_for_nonvolatile_range(VolatilePageRange const&, size_t);
|
|
||||||
bool is_nonvolatile(size_t page_index);
|
|
||||||
|
|
||||||
AnonymousVMObject& operator=(AnonymousVMObject const&) = delete;
|
AnonymousVMObject& operator=(AnonymousVMObject const&) = delete;
|
||||||
AnonymousVMObject& operator=(AnonymousVMObject&&) = delete;
|
AnonymousVMObject& operator=(AnonymousVMObject&&) = delete;
|
||||||
AnonymousVMObject(AnonymousVMObject&&) = delete;
|
AnonymousVMObject(AnonymousVMObject&&) = delete;
|
||||||
|
@ -139,15 +70,15 @@ private:
|
||||||
Bitmap& ensure_cow_map();
|
Bitmap& ensure_cow_map();
|
||||||
void ensure_or_reset_cow_map();
|
void ensure_or_reset_cow_map();
|
||||||
|
|
||||||
VolatilePageRanges m_volatile_ranges_cache;
|
|
||||||
bool m_volatile_ranges_cache_dirty { true };
|
|
||||||
Vector<PurgeablePageRanges*> m_purgeable_ranges;
|
|
||||||
size_t m_unused_committed_pages { 0 };
|
size_t m_unused_committed_pages { 0 };
|
||||||
|
|
||||||
Bitmap m_cow_map;
|
Bitmap m_cow_map;
|
||||||
|
|
||||||
// We share a pool of committed cow-pages with clones
|
// We share a pool of committed cow-pages with clones
|
||||||
RefPtr<CommittedCowPages> m_shared_committed_cow_pages;
|
RefPtr<CommittedCowPages> m_shared_committed_cow_pages;
|
||||||
|
|
||||||
|
bool m_purgeable { false };
|
||||||
|
bool m_volatile { false };
|
||||||
|
bool m_was_purged { false };
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,310 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <AK/BinarySearch.h>
|
|
||||||
#include <AK/ScopeGuard.h>
|
|
||||||
#include <Kernel/Debug.h>
|
|
||||||
#include <Kernel/Process.h>
|
|
||||||
#include <Kernel/VM/AnonymousVMObject.h>
|
|
||||||
#include <Kernel/VM/MemoryManager.h>
|
|
||||||
#include <Kernel/VM/PhysicalPage.h>
|
|
||||||
#include <Kernel/VM/PurgeablePageRanges.h>
|
|
||||||
|
|
||||||
namespace AK {
|
|
||||||
template<>
|
|
||||||
struct Formatter<Kernel::VolatilePageRange> : Formatter<String> {
|
|
||||||
void format(FormatBuilder& builder, const Kernel::VolatilePageRange& value)
|
|
||||||
{
|
|
||||||
return Formatter<String>::format(builder, String::formatted("{{{} ({}) purged: {}}}", value.base, value.count, value.was_purged));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Kernel {
|
|
||||||
|
|
||||||
static void dump_volatile_page_ranges(const Vector<VolatilePageRange>& ranges)
|
|
||||||
{
|
|
||||||
if constexpr (VOLATILE_PAGE_RANGES_DEBUG) {
|
|
||||||
for (size_t i = 0; i < ranges.size(); i++) {
|
|
||||||
dbgln("[{}] {}", i, ranges[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void VolatilePageRanges::add_unchecked(const VolatilePageRange& range)
|
|
||||||
{
|
|
||||||
auto add_range = m_total_range.intersected(range);
|
|
||||||
if (add_range.is_empty())
|
|
||||||
return;
|
|
||||||
m_ranges.append(range);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VolatilePageRanges::add(const VolatilePageRange& range)
|
|
||||||
{
|
|
||||||
auto add_range = m_total_range.intersected(range);
|
|
||||||
if (add_range.is_empty())
|
|
||||||
return false;
|
|
||||||
add_range.was_purged = range.was_purged;
|
|
||||||
|
|
||||||
if constexpr (VOLATILE_PAGE_RANGES_DEBUG) {
|
|
||||||
dbgln("ADD {} (total range: {}) -->", range, m_total_range);
|
|
||||||
dump_volatile_page_ranges(m_ranges);
|
|
||||||
ScopeGuard debug_guard([&]() {
|
|
||||||
dbgln("After adding {} (total range: {})", range, m_total_range);
|
|
||||||
dump_volatile_page_ranges(m_ranges);
|
|
||||||
dbgln("<-- ADD {} (total range: {})", range, m_total_range);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t nearby_index = 0;
|
|
||||||
auto* existing_range = binary_search(
|
|
||||||
m_ranges.span(), add_range, &nearby_index, [](auto& a, auto& b) {
|
|
||||||
if (a.intersects_or_adjacent(b))
|
|
||||||
return 0;
|
|
||||||
return (signed)(a.base - (b.base + b.count - 1));
|
|
||||||
});
|
|
||||||
|
|
||||||
size_t inserted_index = 0;
|
|
||||||
if (existing_range) {
|
|
||||||
if (*existing_range == add_range)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (existing_range->was_purged != add_range.was_purged) {
|
|
||||||
// Found an intersecting or adjacent range, but the purge flag
|
|
||||||
// doesn't match. Subtract what we're adding from it, and
|
|
||||||
existing_range->subtract_intersecting(add_range);
|
|
||||||
if (existing_range->is_empty()) {
|
|
||||||
*existing_range = add_range;
|
|
||||||
} else {
|
|
||||||
m_ranges.insert(++nearby_index, add_range);
|
|
||||||
existing_range = &m_ranges[nearby_index];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Found an intersecting or adjacent range that can be merged
|
|
||||||
existing_range->combine_intersecting_or_adjacent(add_range);
|
|
||||||
}
|
|
||||||
inserted_index = nearby_index;
|
|
||||||
} else {
|
|
||||||
// Insert into the sorted list
|
|
||||||
m_ranges.insert_before_matching(
|
|
||||||
VolatilePageRange(add_range), [&](auto& entry) {
|
|
||||||
return entry.base >= add_range.base + add_range.count;
|
|
||||||
},
|
|
||||||
nearby_index, &inserted_index);
|
|
||||||
existing_range = &m_ranges[inserted_index];
|
|
||||||
}
|
|
||||||
|
|
||||||
// See if we can merge any of the following ranges
|
|
||||||
inserted_index++;
|
|
||||||
while (inserted_index < m_ranges.size()) {
|
|
||||||
auto& next_range = m_ranges[inserted_index];
|
|
||||||
if (!next_range.intersects_or_adjacent(*existing_range))
|
|
||||||
break;
|
|
||||||
if (next_range.was_purged != existing_range->was_purged) {
|
|
||||||
// The purged flag of following range is not the same.
|
|
||||||
// Subtract the added/combined range from it
|
|
||||||
next_range.subtract_intersecting(*existing_range);
|
|
||||||
if (next_range.is_empty())
|
|
||||||
m_ranges.remove(inserted_index);
|
|
||||||
} else {
|
|
||||||
existing_range->combine_intersecting_or_adjacent(next_range);
|
|
||||||
m_ranges.remove(inserted_index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VolatilePageRanges::remove(const VolatilePageRange& range, bool& was_purged)
|
|
||||||
{
|
|
||||||
auto remove_range = m_total_range.intersected(range);
|
|
||||||
if (remove_range.is_empty())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if constexpr (VOLATILE_PAGE_RANGES_DEBUG) {
|
|
||||||
dbgln("REMOVE {} (total range: {}) -->", range, m_total_range);
|
|
||||||
dump_volatile_page_ranges(m_ranges);
|
|
||||||
ScopeGuard debug_guard([&]() {
|
|
||||||
dbgln("After removing {} (total range: {})", range, m_total_range);
|
|
||||||
dump_volatile_page_ranges(m_ranges);
|
|
||||||
dbgln("<-- REMOVE {} (total range: {}) was_purged: {}", range, m_total_range, was_purged);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t nearby_index = 0;
|
|
||||||
auto* existing_range = binary_search(
|
|
||||||
m_ranges.span(), remove_range, &nearby_index, [](auto& a, auto& b) {
|
|
||||||
if (a.intersects(b))
|
|
||||||
return 0;
|
|
||||||
return (signed)(a.base - (b.base + b.count - 1));
|
|
||||||
});
|
|
||||||
if (!existing_range)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
was_purged = existing_range->was_purged;
|
|
||||||
if (existing_range->range_equals(remove_range)) {
|
|
||||||
m_ranges.remove(nearby_index);
|
|
||||||
} else {
|
|
||||||
// See if we need to remove any of the following ranges
|
|
||||||
VERIFY(existing_range == &m_ranges[nearby_index]); // sanity check
|
|
||||||
while (nearby_index < m_ranges.size()) {
|
|
||||||
existing_range = &m_ranges[nearby_index];
|
|
||||||
if (!existing_range->intersects(range))
|
|
||||||
break;
|
|
||||||
was_purged |= existing_range->was_purged;
|
|
||||||
existing_range->subtract_intersecting(remove_range);
|
|
||||||
if (existing_range->is_empty()) {
|
|
||||||
m_ranges.remove(nearby_index);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VolatilePageRanges::intersects(const VolatilePageRange& range) const
|
|
||||||
{
|
|
||||||
auto* existing_range = binary_search(
|
|
||||||
m_ranges.span(), range, nullptr, [](auto& a, auto& b) {
|
|
||||||
if (a.intersects(b))
|
|
||||||
return 0;
|
|
||||||
return (signed)(a.base - (b.base + b.count - 1));
|
|
||||||
});
|
|
||||||
return existing_range != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
PurgeablePageRanges::PurgeablePageRanges(const VMObject& vmobject)
|
|
||||||
: m_volatile_ranges({ 0, vmobject.is_anonymous() ? vmobject.page_count() : 0 })
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PurgeablePageRanges::add_volatile_range(const VolatilePageRange& range)
|
|
||||||
{
|
|
||||||
if (range.is_empty())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Since we may need to call into AnonymousVMObject we need to acquire
|
|
||||||
// its lock as well, and acquire it first. This is important so that
|
|
||||||
// we don't deadlock when a page fault (e.g. on another processor)
|
|
||||||
// happens that is meant to lazy-allocate a committed page. It would
|
|
||||||
// call into AnonymousVMObject::range_made_volatile, which then would
|
|
||||||
// also call into this object and need to acquire m_lock. By acquiring
|
|
||||||
// the vmobject lock first in both cases, we avoid deadlocking.
|
|
||||||
// We can access m_vmobject without any locks for that purpose because
|
|
||||||
// add_volatile_range and remove_volatile_range can only be called
|
|
||||||
// by same object that calls set_vmobject.
|
|
||||||
ScopedSpinLock vmobject_lock(m_vmobject->m_lock);
|
|
||||||
ScopedSpinLock lock(m_volatile_ranges_lock);
|
|
||||||
bool added = m_volatile_ranges.add(range);
|
|
||||||
if (added)
|
|
||||||
m_vmobject->range_made_volatile(range);
|
|
||||||
return added;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto PurgeablePageRanges::remove_volatile_range(const VolatilePageRange& range, bool& was_purged) -> RemoveVolatileError
|
|
||||||
{
|
|
||||||
if (range.is_empty()) {
|
|
||||||
was_purged = false;
|
|
||||||
return RemoveVolatileError::Success;
|
|
||||||
}
|
|
||||||
ScopedSpinLock vmobject_lock(m_vmobject->m_lock); // see comment in add_volatile_range
|
|
||||||
ScopedSpinLock lock(m_volatile_ranges_lock);
|
|
||||||
VERIFY(m_vmobject);
|
|
||||||
|
|
||||||
// Before we actually remove this range, we need to check if we need
|
|
||||||
// to commit any pages, which may fail. If it fails, we don't actually
|
|
||||||
// want to make any modifications. COW pages are already accounted for
|
|
||||||
// in m_shared_committed_cow_pages
|
|
||||||
size_t need_commit_pages = 0;
|
|
||||||
m_volatile_ranges.for_each_intersecting_range(range, [&](const VolatilePageRange& intersected_range) {
|
|
||||||
need_commit_pages += m_vmobject->count_needed_commit_pages_for_nonvolatile_range(intersected_range);
|
|
||||||
return IterationDecision::Continue;
|
|
||||||
});
|
|
||||||
if (need_commit_pages > 0) {
|
|
||||||
// See if we can grab enough pages for what we're marking non-volatile
|
|
||||||
if (!MM.commit_user_physical_pages(need_commit_pages))
|
|
||||||
return RemoveVolatileError::OutOfMemory;
|
|
||||||
|
|
||||||
// Now that we are committed to these pages, mark them for lazy-commit allocation
|
|
||||||
auto pages_to_mark = need_commit_pages;
|
|
||||||
m_volatile_ranges.for_each_intersecting_range(range, [&](const VolatilePageRange& intersected_range) {
|
|
||||||
auto pages_marked = m_vmobject->mark_committed_pages_for_nonvolatile_range(intersected_range, pages_to_mark);
|
|
||||||
pages_to_mark -= pages_marked;
|
|
||||||
return IterationDecision::Continue;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now actually remove the range
|
|
||||||
if (m_volatile_ranges.remove(range, was_purged)) {
|
|
||||||
m_vmobject->range_made_nonvolatile(range);
|
|
||||||
return RemoveVolatileError::Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
VERIFY(need_commit_pages == 0); // We should have not touched anything
|
|
||||||
return RemoveVolatileError::SuccessNoChange;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PurgeablePageRanges::is_volatile_range(const VolatilePageRange& range) const
|
|
||||||
{
|
|
||||||
if (range.is_empty())
|
|
||||||
return false;
|
|
||||||
ScopedSpinLock lock(m_volatile_ranges_lock);
|
|
||||||
return m_volatile_ranges.intersects(range);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PurgeablePageRanges::is_volatile(size_t index) const
|
|
||||||
{
|
|
||||||
ScopedSpinLock lock(m_volatile_ranges_lock);
|
|
||||||
return m_volatile_ranges.contains(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PurgeablePageRanges::set_was_purged(const VolatilePageRange& range)
|
|
||||||
{
|
|
||||||
ScopedSpinLock lock(m_volatile_ranges_lock);
|
|
||||||
m_volatile_ranges.add({ range.base, range.count, true });
|
|
||||||
}
|
|
||||||
|
|
||||||
void PurgeablePageRanges::set_vmobject(AnonymousVMObject* vmobject)
|
|
||||||
{
|
|
||||||
// No lock needed here
|
|
||||||
if (vmobject) {
|
|
||||||
VERIFY(!m_vmobject);
|
|
||||||
m_vmobject = vmobject;
|
|
||||||
} else {
|
|
||||||
VERIFY(m_vmobject);
|
|
||||||
m_vmobject = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CommittedCowPages::CommittedCowPages(size_t committed_pages)
|
|
||||||
: m_committed_pages(committed_pages)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
CommittedCowPages::~CommittedCowPages()
|
|
||||||
{
|
|
||||||
// Return unused committed pages
|
|
||||||
if (m_committed_pages > 0)
|
|
||||||
MM.uncommit_user_physical_pages(m_committed_pages);
|
|
||||||
}
|
|
||||||
|
|
||||||
NonnullRefPtr<PhysicalPage> CommittedCowPages::allocate_one()
|
|
||||||
{
|
|
||||||
VERIFY(m_committed_pages > 0);
|
|
||||||
m_committed_pages--;
|
|
||||||
|
|
||||||
return MM.allocate_committed_user_physical_page(MemoryManager::ShouldZeroFill::Yes);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CommittedCowPages::return_one()
|
|
||||||
{
|
|
||||||
VERIFY(m_committed_pages > 0);
|
|
||||||
m_committed_pages--;
|
|
||||||
|
|
||||||
MM.uncommit_user_physical_pages(1);
|
|
||||||
return m_committed_pages == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,245 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <AK/Bitmap.h>
|
|
||||||
#include <AK/RefCounted.h>
|
|
||||||
#include <Kernel/SpinLock.h>
|
|
||||||
|
|
||||||
namespace Kernel {
|
|
||||||
|
|
||||||
struct VolatilePageRange {
|
|
||||||
size_t base { 0 };
|
|
||||||
size_t count { 0 };
|
|
||||||
bool was_purged { false };
|
|
||||||
|
|
||||||
bool is_empty() const { return count == 0; }
|
|
||||||
|
|
||||||
bool intersects(const VolatilePageRange& other) const
|
|
||||||
{
|
|
||||||
return other.base < base + count || other.base + other.count > base;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool intersects_or_adjacent(const VolatilePageRange& other) const
|
|
||||||
{
|
|
||||||
return other.base <= base + count || other.base + other.count >= base;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool contains(const VolatilePageRange& other) const
|
|
||||||
{
|
|
||||||
return base <= other.base && base + count >= other.base + other.count;
|
|
||||||
}
|
|
||||||
|
|
||||||
VolatilePageRange intersected(const VolatilePageRange& other) const
|
|
||||||
{
|
|
||||||
auto b = max(base, other.base);
|
|
||||||
auto e = min(base + count, other.base + other.count);
|
|
||||||
if (b >= e)
|
|
||||||
return {};
|
|
||||||
return { b, e - b, was_purged };
|
|
||||||
}
|
|
||||||
|
|
||||||
void combine_intersecting_or_adjacent(const VolatilePageRange& other)
|
|
||||||
{
|
|
||||||
VERIFY(intersects_or_adjacent(other));
|
|
||||||
if (base <= other.base) {
|
|
||||||
count = (other.base - base) + other.count;
|
|
||||||
} else {
|
|
||||||
count = (base - other.base) + count;
|
|
||||||
base = other.base;
|
|
||||||
}
|
|
||||||
was_purged |= other.was_purged;
|
|
||||||
}
|
|
||||||
|
|
||||||
void subtract_intersecting(const VolatilePageRange& other)
|
|
||||||
{
|
|
||||||
if (!intersects(other))
|
|
||||||
return;
|
|
||||||
if (other.contains(*this)) {
|
|
||||||
count = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (base <= other.base) {
|
|
||||||
count = (other.base - base);
|
|
||||||
} else {
|
|
||||||
auto new_base = other.base + other.count;
|
|
||||||
count = (base + count) - new_base;
|
|
||||||
base = new_base;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool range_equals(const VolatilePageRange& other) const
|
|
||||||
{
|
|
||||||
return base == other.base && count == other.count;
|
|
||||||
}
|
|
||||||
bool operator==(const VolatilePageRange& other) const
|
|
||||||
{
|
|
||||||
return base == other.base && count == other.count && was_purged == other.was_purged;
|
|
||||||
}
|
|
||||||
bool operator!=(const VolatilePageRange& other) const
|
|
||||||
{
|
|
||||||
return base != other.base || count != other.count || was_purged != other.was_purged;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class VolatilePageRanges {
|
|
||||||
public:
|
|
||||||
VolatilePageRanges(const VolatilePageRange& total_range)
|
|
||||||
: m_total_range(total_range)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
VolatilePageRanges(const VolatilePageRanges& other)
|
|
||||||
: m_ranges(other.m_ranges)
|
|
||||||
, m_total_range(other.m_total_range)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_empty() const { return m_ranges.is_empty(); }
|
|
||||||
void clear() { m_ranges.clear_with_capacity(); }
|
|
||||||
|
|
||||||
bool is_all() const
|
|
||||||
{
|
|
||||||
if (m_ranges.size() != 1)
|
|
||||||
return false;
|
|
||||||
return m_ranges[0] == m_total_range;
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_all()
|
|
||||||
{
|
|
||||||
if (m_ranges.size() != 1)
|
|
||||||
m_ranges = { m_total_range };
|
|
||||||
else
|
|
||||||
m_ranges[0] = m_total_range;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool intersects(const VolatilePageRange&) const;
|
|
||||||
bool contains(size_t index) const
|
|
||||||
{
|
|
||||||
return intersects({ index, 1 });
|
|
||||||
}
|
|
||||||
|
|
||||||
bool add(const VolatilePageRange&);
|
|
||||||
void add_unchecked(const VolatilePageRange&);
|
|
||||||
bool remove(const VolatilePageRange&, bool&);
|
|
||||||
|
|
||||||
template<typename F>
|
|
||||||
IterationDecision for_each_intersecting_range(const VolatilePageRange& range, F f)
|
|
||||||
{
|
|
||||||
auto r = m_total_range.intersected(range);
|
|
||||||
if (r.is_empty())
|
|
||||||
return IterationDecision::Continue;
|
|
||||||
|
|
||||||
size_t nearby_index = 0;
|
|
||||||
auto* existing_range = binary_search(
|
|
||||||
m_ranges.span(), r, &nearby_index, [](auto& a, auto& b) {
|
|
||||||
if (a.intersects(b))
|
|
||||||
return 0;
|
|
||||||
return (signed)(a.base - (b.base + b.count - 1));
|
|
||||||
});
|
|
||||||
if (!existing_range)
|
|
||||||
return IterationDecision::Continue;
|
|
||||||
|
|
||||||
if (existing_range->range_equals(r))
|
|
||||||
return f(r);
|
|
||||||
VERIFY(existing_range == &m_ranges[nearby_index]); // sanity check
|
|
||||||
while (nearby_index < m_ranges.size()) {
|
|
||||||
existing_range = &m_ranges[nearby_index];
|
|
||||||
if (!existing_range->intersects(range))
|
|
||||||
break;
|
|
||||||
|
|
||||||
IterationDecision decision = f(existing_range->intersected(r));
|
|
||||||
if (decision != IterationDecision::Continue)
|
|
||||||
return decision;
|
|
||||||
|
|
||||||
nearby_index++;
|
|
||||||
}
|
|
||||||
return IterationDecision::Continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename F>
|
|
||||||
IterationDecision for_each_nonvolatile_range(F f) const
|
|
||||||
{
|
|
||||||
size_t base = m_total_range.base;
|
|
||||||
for (const auto& volatile_range : m_ranges) {
|
|
||||||
if (volatile_range.base == base)
|
|
||||||
continue;
|
|
||||||
IterationDecision decision = f({ base, volatile_range.base - base });
|
|
||||||
if (decision != IterationDecision::Continue)
|
|
||||||
return decision;
|
|
||||||
base = volatile_range.base + volatile_range.count;
|
|
||||||
}
|
|
||||||
if (base < m_total_range.base + m_total_range.count)
|
|
||||||
return f({ base, (m_total_range.base + m_total_range.count) - base });
|
|
||||||
return IterationDecision::Continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector<VolatilePageRange>& ranges() { return m_ranges; }
|
|
||||||
const Vector<VolatilePageRange>& ranges() const { return m_ranges; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
Vector<VolatilePageRange> m_ranges;
|
|
||||||
VolatilePageRange m_total_range;
|
|
||||||
};
|
|
||||||
|
|
||||||
class AnonymousVMObject;
|
|
||||||
|
|
||||||
class PurgeablePageRanges {
|
|
||||||
friend class AnonymousVMObject;
|
|
||||||
|
|
||||||
public:
|
|
||||||
PurgeablePageRanges(const VMObject&);
|
|
||||||
|
|
||||||
void copy_purgeable_page_ranges(const PurgeablePageRanges& other)
|
|
||||||
{
|
|
||||||
if (this == &other)
|
|
||||||
return;
|
|
||||||
ScopedSpinLock lock(m_volatile_ranges_lock);
|
|
||||||
ScopedSpinLock other_lock(other.m_volatile_ranges_lock);
|
|
||||||
m_volatile_ranges = other.m_volatile_ranges;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool add_volatile_range(const VolatilePageRange& range);
|
|
||||||
enum class RemoveVolatileError {
|
|
||||||
Success = 0,
|
|
||||||
SuccessNoChange,
|
|
||||||
OutOfMemory
|
|
||||||
};
|
|
||||||
RemoveVolatileError remove_volatile_range(const VolatilePageRange& range, bool& was_purged);
|
|
||||||
bool is_volatile_range(const VolatilePageRange& range) const;
|
|
||||||
bool is_volatile(size_t) const;
|
|
||||||
|
|
||||||
bool is_empty() const { return m_volatile_ranges.is_empty(); }
|
|
||||||
|
|
||||||
void set_was_purged(const VolatilePageRange&);
|
|
||||||
|
|
||||||
const VolatilePageRanges& volatile_ranges() const { return m_volatile_ranges; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void set_vmobject(AnonymousVMObject*);
|
|
||||||
|
|
||||||
VolatilePageRanges m_volatile_ranges;
|
|
||||||
mutable RecursiveSpinLock m_volatile_ranges_lock;
|
|
||||||
AnonymousVMObject* m_vmobject { nullptr };
|
|
||||||
};
|
|
||||||
|
|
||||||
class CommittedCowPages : public RefCounted<CommittedCowPages> {
|
|
||||||
AK_MAKE_NONCOPYABLE(CommittedCowPages);
|
|
||||||
|
|
||||||
public:
|
|
||||||
CommittedCowPages() = delete;
|
|
||||||
|
|
||||||
CommittedCowPages(size_t);
|
|
||||||
~CommittedCowPages();
|
|
||||||
|
|
||||||
NonnullRefPtr<PhysicalPage> allocate_one();
|
|
||||||
bool return_one();
|
|
||||||
|
|
||||||
private:
|
|
||||||
size_t m_committed_pages;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -20,8 +20,7 @@
|
||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
|
|
||||||
Region::Region(Range const& range, NonnullRefPtr<VMObject> vmobject, size_t offset_in_vmobject, OwnPtr<KString> name, Region::Access access, Cacheable cacheable, bool shared)
|
Region::Region(Range const& range, NonnullRefPtr<VMObject> vmobject, size_t offset_in_vmobject, OwnPtr<KString> name, Region::Access access, Cacheable cacheable, bool shared)
|
||||||
: PurgeablePageRanges(vmobject)
|
: m_range(range)
|
||||||
, m_range(range)
|
|
||||||
, m_offset_in_vmobject(offset_in_vmobject)
|
, m_offset_in_vmobject(offset_in_vmobject)
|
||||||
, m_vmobject(move(vmobject))
|
, m_vmobject(move(vmobject))
|
||||||
, m_name(move(name))
|
, m_name(move(name))
|
||||||
|
@ -34,14 +33,12 @@ Region::Region(Range const& range, NonnullRefPtr<VMObject> vmobject, size_t offs
|
||||||
VERIFY((m_range.size() % PAGE_SIZE) == 0);
|
VERIFY((m_range.size() % PAGE_SIZE) == 0);
|
||||||
|
|
||||||
m_vmobject->add_region(*this);
|
m_vmobject->add_region(*this);
|
||||||
register_purgeable_page_ranges();
|
|
||||||
MM.register_region(*this);
|
MM.register_region(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
Region::~Region()
|
Region::~Region()
|
||||||
{
|
{
|
||||||
m_vmobject->remove_region(*this);
|
m_vmobject->remove_region(*this);
|
||||||
unregister_purgeable_page_ranges();
|
|
||||||
|
|
||||||
// Make sure we disable interrupts so we don't get interrupted between unmapping and unregistering.
|
// Make sure we disable interrupts so we don't get interrupted between unmapping and unregistering.
|
||||||
// Unmapping the region will give the VM back to the RangeAllocator, so an interrupt handler would
|
// Unmapping the region will give the VM back to the RangeAllocator, so an interrupt handler would
|
||||||
|
@ -55,22 +52,6 @@ Region::~Region()
|
||||||
MM.unregister_region(*this);
|
MM.unregister_region(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Region::register_purgeable_page_ranges()
|
|
||||||
{
|
|
||||||
if (m_vmobject->is_anonymous()) {
|
|
||||||
auto& vmobject = static_cast<AnonymousVMObject&>(*m_vmobject);
|
|
||||||
vmobject.register_purgeable_page_ranges(*this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Region::unregister_purgeable_page_ranges()
|
|
||||||
{
|
|
||||||
if (m_vmobject->is_anonymous()) {
|
|
||||||
auto& vmobject = static_cast<AnonymousVMObject&>(*m_vmobject);
|
|
||||||
vmobject.unregister_purgeable_page_ranges(*this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OwnPtr<Region> Region::clone(Process& new_owner)
|
OwnPtr<Region> Region::clone(Process& new_owner)
|
||||||
{
|
{
|
||||||
VERIFY(Process::current());
|
VERIFY(Process::current());
|
||||||
|
@ -89,8 +70,6 @@ OwnPtr<Region> Region::clone(Process& new_owner)
|
||||||
dbgln("Region::clone: Unable to allocate new Region");
|
dbgln("Region::clone: Unable to allocate new Region");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
if (m_vmobject->is_anonymous())
|
|
||||||
region->copy_purgeable_page_ranges(*this);
|
|
||||||
region->set_mmap(m_mmap);
|
region->set_mmap(m_mmap);
|
||||||
region->set_shared(m_shared);
|
region->set_shared(m_shared);
|
||||||
region->set_syscall_region(is_syscall_region());
|
region->set_syscall_region(is_syscall_region());
|
||||||
|
@ -112,8 +91,6 @@ OwnPtr<Region> Region::clone(Process& new_owner)
|
||||||
dbgln("Region::clone: Unable to allocate new Region for COW");
|
dbgln("Region::clone: Unable to allocate new Region for COW");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
if (m_vmobject->is_anonymous())
|
|
||||||
clone_region->copy_purgeable_page_ranges(*this);
|
|
||||||
if (m_stack) {
|
if (m_stack) {
|
||||||
VERIFY(is_readable());
|
VERIFY(is_readable());
|
||||||
VERIFY(is_writable());
|
VERIFY(is_writable());
|
||||||
|
@ -129,55 +106,9 @@ void Region::set_vmobject(NonnullRefPtr<VMObject>&& obj)
|
||||||
{
|
{
|
||||||
if (m_vmobject.ptr() == obj.ptr())
|
if (m_vmobject.ptr() == obj.ptr())
|
||||||
return;
|
return;
|
||||||
unregister_purgeable_page_ranges();
|
|
||||||
m_vmobject->remove_region(*this);
|
m_vmobject->remove_region(*this);
|
||||||
m_vmobject = move(obj);
|
m_vmobject = move(obj);
|
||||||
m_vmobject->add_region(*this);
|
m_vmobject->add_region(*this);
|
||||||
register_purgeable_page_ranges();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Region::is_volatile(VirtualAddress vaddr, size_t size) const
|
|
||||||
{
|
|
||||||
if (!m_vmobject->is_anonymous())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto offset_in_vmobject = vaddr.get() - (this->vaddr().get() - m_offset_in_vmobject);
|
|
||||||
size_t first_page_index = page_round_down(offset_in_vmobject) / PAGE_SIZE;
|
|
||||||
size_t last_page_index = page_round_up(offset_in_vmobject + size) / PAGE_SIZE;
|
|
||||||
return is_volatile_range({ first_page_index, last_page_index - first_page_index });
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Region::set_volatile(VirtualAddress vaddr, size_t size, bool is_volatile, bool& was_purged) -> SetVolatileError
|
|
||||||
{
|
|
||||||
was_purged = false;
|
|
||||||
if (!m_vmobject->is_anonymous())
|
|
||||||
return SetVolatileError::NotPurgeable;
|
|
||||||
|
|
||||||
auto offset_in_vmobject = vaddr.get() - (this->vaddr().get() - m_offset_in_vmobject);
|
|
||||||
if (is_volatile) {
|
|
||||||
// If marking pages as volatile, be prudent by not marking
|
|
||||||
// partial pages volatile to prevent potentially non-volatile
|
|
||||||
// data to be discarded. So rund up the first page and round
|
|
||||||
// down the last page.
|
|
||||||
size_t first_page_index = page_round_up(offset_in_vmobject) / PAGE_SIZE;
|
|
||||||
size_t last_page_index = page_round_down(offset_in_vmobject + size) / PAGE_SIZE;
|
|
||||||
if (first_page_index != last_page_index)
|
|
||||||
add_volatile_range({ first_page_index, last_page_index - first_page_index });
|
|
||||||
} else {
|
|
||||||
// If marking pages as non-volatile, round down the first page
|
|
||||||
// and round up the last page to make sure the beginning and
|
|
||||||
// end of the range doesn't inadvertedly get discarded.
|
|
||||||
size_t first_page_index = page_round_down(offset_in_vmobject) / PAGE_SIZE;
|
|
||||||
size_t last_page_index = page_round_up(offset_in_vmobject + size) / PAGE_SIZE;
|
|
||||||
switch (remove_volatile_range({ first_page_index, last_page_index - first_page_index }, was_purged)) {
|
|
||||||
case PurgeablePageRanges::RemoveVolatileError::Success:
|
|
||||||
case PurgeablePageRanges::RemoveVolatileError::SuccessNoChange:
|
|
||||||
break;
|
|
||||||
case PurgeablePageRanges::RemoveVolatileError::OutOfMemory:
|
|
||||||
return SetVolatileError::OutOfMemory;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return SetVolatileError::Success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Region::cow_pages() const
|
size_t Region::cow_pages() const
|
||||||
|
@ -279,43 +210,6 @@ bool Region::map_individual_page_impl(size_t page_index)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Region::do_remap_vmobject_page_range(size_t page_index, size_t page_count)
|
|
||||||
{
|
|
||||||
bool success = true;
|
|
||||||
if (!m_page_directory)
|
|
||||||
return success; // not an error, region may have not yet mapped it
|
|
||||||
if (!translate_vmobject_page_range(page_index, page_count))
|
|
||||||
return success; // not an error, region doesn't map this page range
|
|
||||||
ScopedSpinLock page_lock(m_page_directory->get_lock());
|
|
||||||
size_t index = page_index;
|
|
||||||
while (index < page_index + page_count) {
|
|
||||||
if (!map_individual_page_impl(index)) {
|
|
||||||
success = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
if (index > page_index)
|
|
||||||
MM.flush_tlb(m_page_directory, vaddr_from_page_index(page_index), index - page_index);
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Region::remap_vmobject_page_range(size_t page_index, size_t page_count)
|
|
||||||
{
|
|
||||||
bool success = true;
|
|
||||||
auto& vmobject = this->vmobject();
|
|
||||||
if (vmobject.is_shared_by_multiple_regions()) {
|
|
||||||
vmobject.for_each_region([&](auto& region) {
|
|
||||||
if (!region.do_remap_vmobject_page_range(page_index, page_count))
|
|
||||||
success = false;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (!do_remap_vmobject_page_range(page_index, page_count))
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Region::do_remap_vmobject_page(size_t page_index, bool with_flush)
|
bool Region::do_remap_vmobject_page(size_t page_index, bool with_flush)
|
||||||
{
|
{
|
||||||
ScopedSpinLock lock(vmobject().m_lock);
|
ScopedSpinLock lock(vmobject().m_lock);
|
||||||
|
@ -428,7 +322,7 @@ PageFaultResponse Region::handle_fault(PageFault const& fault)
|
||||||
if (page_slot->is_lazy_committed_page()) {
|
if (page_slot->is_lazy_committed_page()) {
|
||||||
auto page_index_in_vmobject = translate_to_vmobject_page(page_index_in_region);
|
auto page_index_in_vmobject = translate_to_vmobject_page(page_index_in_region);
|
||||||
VERIFY(m_vmobject->is_anonymous());
|
VERIFY(m_vmobject->is_anonymous());
|
||||||
page_slot = static_cast<AnonymousVMObject&>(*m_vmobject).allocate_committed_page({}, page_index_in_vmobject);
|
page_slot = static_cast<AnonymousVMObject&>(*m_vmobject).allocate_committed_page({});
|
||||||
remap_vmobject_page(page_index_in_vmobject);
|
remap_vmobject_page(page_index_in_vmobject);
|
||||||
return PageFaultResponse::Continue;
|
return PageFaultResponse::Continue;
|
||||||
}
|
}
|
||||||
|
@ -472,7 +366,7 @@ PageFaultResponse Region::handle_zero_fault(size_t page_index_in_region)
|
||||||
|
|
||||||
if (page_slot->is_lazy_committed_page()) {
|
if (page_slot->is_lazy_committed_page()) {
|
||||||
VERIFY(m_vmobject->is_anonymous());
|
VERIFY(m_vmobject->is_anonymous());
|
||||||
page_slot = static_cast<AnonymousVMObject&>(*m_vmobject).allocate_committed_page({}, page_index_in_vmobject);
|
page_slot = static_cast<AnonymousVMObject&>(*m_vmobject).allocate_committed_page({});
|
||||||
dbgln_if(PAGE_FAULT_DEBUG, " >> ALLOCATED COMMITTED {}", page_slot->paddr());
|
dbgln_if(PAGE_FAULT_DEBUG, " >> ALLOCATED COMMITTED {}", page_slot->paddr());
|
||||||
} else {
|
} else {
|
||||||
page_slot = MM.allocate_user_physical_page(MemoryManager::ShouldZeroFill::Yes);
|
page_slot = MM.allocate_user_physical_page(MemoryManager::ShouldZeroFill::Yes);
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
#include <Kernel/Sections.h>
|
#include <Kernel/Sections.h>
|
||||||
#include <Kernel/UnixTypes.h>
|
#include <Kernel/UnixTypes.h>
|
||||||
#include <Kernel/VM/PageFaultResponse.h>
|
#include <Kernel/VM/PageFaultResponse.h>
|
||||||
#include <Kernel/VM/PurgeablePageRanges.h>
|
|
||||||
#include <Kernel/VM/RangeAllocator.h>
|
#include <Kernel/VM/RangeAllocator.h>
|
||||||
|
|
||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
|
@ -28,8 +27,7 @@ enum class ShouldFlushTLB {
|
||||||
};
|
};
|
||||||
|
|
||||||
class Region final
|
class Region final
|
||||||
: public Weakable<Region>
|
: public Weakable<Region> {
|
||||||
, public PurgeablePageRanges {
|
|
||||||
friend class MemoryManager;
|
friend class MemoryManager;
|
||||||
|
|
||||||
MAKE_SLAB_ALLOCATED(Region)
|
MAKE_SLAB_ALLOCATED(Region)
|
||||||
|
@ -201,15 +199,11 @@ public:
|
||||||
|
|
||||||
void remap();
|
void remap();
|
||||||
|
|
||||||
bool remap_vmobject_page_range(size_t page_index, size_t page_count);
|
|
||||||
|
|
||||||
bool is_volatile(VirtualAddress vaddr, size_t size) const;
|
|
||||||
enum class SetVolatileError {
|
enum class SetVolatileError {
|
||||||
Success = 0,
|
Success = 0,
|
||||||
NotPurgeable,
|
NotPurgeable,
|
||||||
OutOfMemory
|
OutOfMemory
|
||||||
};
|
};
|
||||||
SetVolatileError set_volatile(VirtualAddress vaddr, size_t size, bool is_volatile, bool& was_purged);
|
|
||||||
|
|
||||||
RefPtr<Process> get_owner();
|
RefPtr<Process> get_owner();
|
||||||
|
|
||||||
|
@ -219,7 +213,8 @@ public:
|
||||||
private:
|
private:
|
||||||
Region(Range const&, NonnullRefPtr<VMObject>, size_t offset_in_vmobject, OwnPtr<KString>, Region::Access access, Cacheable, bool shared);
|
Region(Range const&, NonnullRefPtr<VMObject>, size_t offset_in_vmobject, OwnPtr<KString>, Region::Access access, Cacheable, bool shared);
|
||||||
|
|
||||||
bool do_remap_vmobject_page_range(size_t page_index, size_t page_count);
|
bool remap_vmobject_page(size_t page_index, bool with_flush = true);
|
||||||
|
bool do_remap_vmobject_page(size_t page_index, bool with_flush = true);
|
||||||
|
|
||||||
void set_access_bit(Access access, bool b)
|
void set_access_bit(Access access, bool b)
|
||||||
{
|
{
|
||||||
|
@ -229,18 +224,12 @@ private:
|
||||||
m_access &= ~access;
|
m_access &= ~access;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool do_remap_vmobject_page(size_t index, bool with_flush = true);
|
|
||||||
bool remap_vmobject_page(size_t index, bool with_flush = true);
|
|
||||||
|
|
||||||
PageFaultResponse handle_cow_fault(size_t page_index);
|
PageFaultResponse handle_cow_fault(size_t page_index);
|
||||||
PageFaultResponse handle_inode_fault(size_t page_index);
|
PageFaultResponse handle_inode_fault(size_t page_index);
|
||||||
PageFaultResponse handle_zero_fault(size_t page_index);
|
PageFaultResponse handle_zero_fault(size_t page_index);
|
||||||
|
|
||||||
bool map_individual_page_impl(size_t page_index);
|
bool map_individual_page_impl(size_t page_index);
|
||||||
|
|
||||||
void register_purgeable_page_ranges();
|
|
||||||
void unregister_purgeable_page_ranges();
|
|
||||||
|
|
||||||
RefPtr<PageDirectory> m_page_directory;
|
RefPtr<PageDirectory> m_page_directory;
|
||||||
Range m_range;
|
Range m_range;
|
||||||
size_t m_offset_in_vmobject { 0 };
|
size_t m_offset_in_vmobject { 0 };
|
||||||
|
|
|
@ -414,7 +414,10 @@ size_t Space::amount_purgeable_volatile() const
|
||||||
ScopedSpinLock lock(m_lock);
|
ScopedSpinLock lock(m_lock);
|
||||||
size_t amount = 0;
|
size_t amount = 0;
|
||||||
for (auto& region : m_regions) {
|
for (auto& region : m_regions) {
|
||||||
if (region->vmobject().is_anonymous() && static_cast<const AnonymousVMObject&>(region->vmobject()).is_any_volatile())
|
if (!region->vmobject().is_anonymous())
|
||||||
|
continue;
|
||||||
|
auto const& vmobject = static_cast<AnonymousVMObject const&>(region->vmobject());
|
||||||
|
if (vmobject.is_purgeable() && vmobject.is_volatile())
|
||||||
amount += region->amount_resident();
|
amount += region->amount_resident();
|
||||||
}
|
}
|
||||||
return amount;
|
return amount;
|
||||||
|
@ -425,7 +428,10 @@ size_t Space::amount_purgeable_nonvolatile() const
|
||||||
ScopedSpinLock lock(m_lock);
|
ScopedSpinLock lock(m_lock);
|
||||||
size_t amount = 0;
|
size_t amount = 0;
|
||||||
for (auto& region : m_regions) {
|
for (auto& region : m_regions) {
|
||||||
if (region->vmobject().is_anonymous() && !static_cast<const AnonymousVMObject&>(region->vmobject()).is_any_volatile())
|
if (!region->vmobject().is_anonymous())
|
||||||
|
continue;
|
||||||
|
auto const& vmobject = static_cast<AnonymousVMObject const&>(region->vmobject());
|
||||||
|
if (vmobject.is_purgeable() && !vmobject.is_volatile())
|
||||||
amount += region->amount_resident();
|
amount += region->amount_resident();
|
||||||
}
|
}
|
||||||
return amount;
|
return amount;
|
||||||
|
|
|
@ -158,7 +158,7 @@ extern "C" {
|
||||||
|
|
||||||
static void* os_alloc(size_t size, const char* name)
|
static void* os_alloc(size_t size, const char* name)
|
||||||
{
|
{
|
||||||
auto* ptr = serenity_mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0, ChunkedBlock::block_size, name);
|
auto* ptr = serenity_mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_PURGEABLE, 0, 0, ChunkedBlock::block_size, name);
|
||||||
VERIFY(ptr != MAP_FAILED);
|
VERIFY(ptr != MAP_FAILED);
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#define MAP_STACK 0x40
|
#define MAP_STACK 0x40
|
||||||
#define MAP_NORESERVE 0x80
|
#define MAP_NORESERVE 0x80
|
||||||
#define MAP_RANDOMIZED 0x100
|
#define MAP_RANDOMIZED 0x100
|
||||||
|
#define MAP_PURGEABLE 0x200
|
||||||
|
|
||||||
#define PROT_READ 0x1
|
#define PROT_READ 0x1
|
||||||
#define PROT_WRITE 0x2
|
#define PROT_WRITE 0x2
|
||||||
|
|
|
@ -544,11 +544,11 @@ void Bitmap::set_volatile()
|
||||||
int rc = madvise(m_data, size_in_bytes(), MADV_SET_NONVOLATILE);
|
int rc = madvise(m_data, size_in_bytes(), MADV_SET_NONVOLATILE);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
if (errno == ENOMEM) {
|
if (errno == ENOMEM) {
|
||||||
was_purged = was_purged_int;
|
was_purged = true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
perror("madvise(MADV_SET_NONVOLATILE)");
|
perror("madvise(MADV_SET_NONVOLATILE)");
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
was_purged = rc != 0;
|
was_purged = rc != 0;
|
||||||
#endif
|
#endif
|
||||||
|
@ -574,6 +574,7 @@ Optional<BackingStore> Bitmap::try_allocate_backing_store(BitmapFormat format, I
|
||||||
|
|
||||||
int map_flags = MAP_ANONYMOUS | MAP_PRIVATE;
|
int map_flags = MAP_ANONYMOUS | MAP_PRIVATE;
|
||||||
#ifdef __serenity__
|
#ifdef __serenity__
|
||||||
|
map_flags |= MAP_PURGEABLE;
|
||||||
void* data = mmap_with_name(nullptr, data_size_in_bytes, PROT_READ | PROT_WRITE, map_flags, 0, 0, String::formatted("GraphicsBitmap [{}]", size).characters());
|
void* data = mmap_with_name(nullptr, data_size_in_bytes, PROT_READ | PROT_WRITE, map_flags, 0, 0, String::formatted("GraphicsBitmap [{}]", size).characters());
|
||||||
#else
|
#else
|
||||||
void* data = mmap(nullptr, data_size_in_bytes, PROT_READ | PROT_WRITE, map_flags, 0, 0);
|
void* data = mmap(nullptr, data_size_in_bytes, PROT_READ | PROT_WRITE, map_flags, 0, 0);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue