1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 11:18:11 +00:00
serenity/Kernel/Memory/VirtualRangeAllocator.cpp
Andreas Kling 79fa9765ca Kernel: Replace KResult and KResultOr<T> with Error and ErrorOr<T>
We now use AK::Error and AK::ErrorOr<T> in both kernel and userspace!
This was a slightly tedious refactoring that took a long time, so it's
not unlikely that some bugs crept in.

Nevertheless, it does pass basic functionality testing, and it's just
real nice to finally see the same pattern in all contexts. :^)
2021-11-08 01:10:53 +01:00

192 lines
6.3 KiB
C++

/*
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Checked.h>
#include <Kernel/Memory/VirtualRangeAllocator.h>
#include <Kernel/Random.h>
#define VM_GUARD_PAGES
namespace Kernel::Memory {
VirtualRangeAllocator::VirtualRangeAllocator()
: m_total_range({}, 0)
{
}
void VirtualRangeAllocator::initialize_with_range(VirtualAddress base, size_t size)
{
m_total_range = { base, size };
m_available_ranges.insert(base.get(), VirtualRange { base, size });
}
void VirtualRangeAllocator::initialize_from_parent(VirtualRangeAllocator const& parent_allocator)
{
SpinlockLocker lock(parent_allocator.m_lock);
m_total_range = parent_allocator.m_total_range;
m_available_ranges.clear();
for (auto it = parent_allocator.m_available_ranges.begin(); !it.is_end(); ++it) {
m_available_ranges.insert(it.key(), *it);
}
}
void VirtualRangeAllocator::dump() const
{
VERIFY(m_lock.is_locked());
dbgln("VirtualRangeAllocator({})", this);
for (auto& range : m_available_ranges) {
dbgln(" {:x} -> {:x}", range.base().get(), range.end().get() - 1);
}
}
void VirtualRangeAllocator::carve_from_region(VirtualRange const& from, VirtualRange const& range)
{
VERIFY(m_lock.is_locked());
auto remaining_parts = from.carve(range);
VERIFY(remaining_parts.size() >= 1);
VERIFY(m_total_range.contains(remaining_parts[0]));
m_available_ranges.remove(from.base().get());
m_available_ranges.insert(remaining_parts[0].base().get(), remaining_parts[0]);
if (remaining_parts.size() == 2) {
VERIFY(m_total_range.contains(remaining_parts[1]));
m_available_ranges.insert(remaining_parts[1].base().get(), remaining_parts[1]);
}
}
ErrorOr<VirtualRange> VirtualRangeAllocator::try_allocate_randomized(size_t size, size_t alignment)
{
if (!size)
return EINVAL;
VERIFY((size % PAGE_SIZE) == 0);
VERIFY((alignment % PAGE_SIZE) == 0);
// FIXME: I'm sure there's a smarter way to do this.
static constexpr size_t maximum_randomization_attempts = 1000;
for (size_t i = 0; i < maximum_randomization_attempts; ++i) {
VirtualAddress random_address { round_up_to_power_of_two(get_fast_random<FlatPtr>() % m_total_range.end().get(), alignment) };
if (!m_total_range.contains(random_address, size))
continue;
auto range_or_error = try_allocate_specific(random_address, size);
if (!range_or_error.is_error())
return range_or_error.release_value();
}
return try_allocate_anywhere(size, alignment);
}
ErrorOr<VirtualRange> VirtualRangeAllocator::try_allocate_anywhere(size_t size, size_t alignment)
{
if (!size)
return EINVAL;
VERIFY((size % PAGE_SIZE) == 0);
VERIFY((alignment % PAGE_SIZE) == 0);
#ifdef VM_GUARD_PAGES
// NOTE: We pad VM allocations with a guard page on each side.
if (Checked<size_t>::addition_would_overflow(size, PAGE_SIZE * 2))
return EOVERFLOW;
size_t effective_size = size + PAGE_SIZE * 2;
size_t offset_from_effective_base = PAGE_SIZE;
#else
size_t effective_size = size;
size_t offset_from_effective_base = 0;
#endif
if (Checked<size_t>::addition_would_overflow(effective_size, alignment))
return EOVERFLOW;
SpinlockLocker lock(m_lock);
for (auto it = m_available_ranges.begin(); !it.is_end(); ++it) {
auto& available_range = *it;
// FIXME: This check is probably excluding some valid candidates when using a large alignment.
if (available_range.size() < (effective_size + alignment))
continue;
FlatPtr initial_base = available_range.base().offset(offset_from_effective_base).get();
FlatPtr aligned_base = round_up_to_power_of_two(initial_base, alignment);
VirtualRange const allocated_range(VirtualAddress(aligned_base), size);
VERIFY(m_total_range.contains(allocated_range));
if (available_range == allocated_range) {
m_available_ranges.remove(it.key());
return allocated_range;
}
carve_from_region(*it, allocated_range);
return allocated_range;
}
dmesgln("VirtualRangeAllocator: Failed to allocate anywhere: size={}, alignment={}", size, alignment);
return ENOMEM;
}
ErrorOr<VirtualRange> VirtualRangeAllocator::try_allocate_specific(VirtualAddress base, size_t size)
{
if (!size)
return EINVAL;
VERIFY(base.is_page_aligned());
VERIFY((size % PAGE_SIZE) == 0);
VirtualRange const allocated_range(base, size);
if (!m_total_range.contains(allocated_range))
return ENOMEM;
SpinlockLocker lock(m_lock);
auto available_range = m_available_ranges.find_largest_not_above(base.get());
if (!available_range)
return ENOMEM;
if (!available_range->contains(allocated_range))
return ENOMEM;
if (*available_range == allocated_range) {
m_available_ranges.remove(available_range->base().get());
return allocated_range;
}
carve_from_region(*available_range, allocated_range);
return allocated_range;
}
void VirtualRangeAllocator::deallocate(VirtualRange const& range)
{
SpinlockLocker lock(m_lock);
VERIFY(m_total_range.contains(range));
VERIFY(range.size());
VERIFY((range.size() % PAGE_SIZE) == 0);
VERIFY(range.base() < range.end());
VERIFY(!m_available_ranges.is_empty());
VirtualRange merged_range = range;
{
// Try merging with preceding range.
auto* preceding_range = m_available_ranges.find_largest_not_above(range.base().get());
if (preceding_range && preceding_range->end() == range.base()) {
preceding_range->m_size += range.size();
merged_range = *preceding_range;
} else {
m_available_ranges.insert(range.base().get(), range);
}
}
{
// Try merging with following range.
auto* following_range = m_available_ranges.find_largest_not_above(range.end().get());
if (following_range && merged_range.end() == following_range->base()) {
auto* existing_range = m_available_ranges.find_largest_not_above(range.base().get());
VERIFY(existing_range->base() == merged_range.base());
existing_range->m_size += following_range->size();
m_available_ranges.remove(following_range->base().get());
}
}
}
}