mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 21:37:35 +00:00
Kernel: Implement zone-based buddy allocator for physical memory
The previous allocator was very naive and kept the state of all pages in one big bitmap. When allocating, we had to scan through the bitmap until we found an unset bit. This patch introduces a new binary buddy allocator that manages the physical memory pages. Each PhysicalRegion is divided into zones (PhysicalZone) of 16MB each. Any extra pages at the end of physical RAM that don't fit into a 16MB zone are turned into 15 or fewer 1MB zones. Each zone starts out with one full-sized block, which is then recursively subdivided into halves upon allocation, until a block of the request size can be returned. There are more opportunities for improvement here: the way zone objects are allocated and stored is non-optimal. Same goes for the allocation of buddy block state bitmaps.
This commit is contained in:
parent
be83b3aff4
commit
ba87571366
9 changed files with 411 additions and 145 deletions
|
@ -8,10 +8,28 @@
|
|||
#include <AK/RefPtr.h>
|
||||
#include <Kernel/Assertions.h>
|
||||
#include <Kernel/Random.h>
|
||||
#include <Kernel/VM/MemoryManager.h>
|
||||
#include <Kernel/VM/PhysicalRegion.h>
|
||||
#include <Kernel/VM/PhysicalZone.h>
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
static constexpr u32 next_power_of_two(u32 value)
|
||||
{
|
||||
value--;
|
||||
value |= value >> 1;
|
||||
value |= value >> 2;
|
||||
value |= value >> 4;
|
||||
value |= value >> 8;
|
||||
value |= value >> 16;
|
||||
value++;
|
||||
return value;
|
||||
}
|
||||
|
||||
PhysicalRegion::~PhysicalRegion()
|
||||
{
|
||||
}
|
||||
|
||||
PhysicalRegion::PhysicalRegion(PhysicalAddress lower, PhysicalAddress upper)
|
||||
: m_lower(lower)
|
||||
, m_upper(upper)
|
||||
|
@ -26,17 +44,34 @@ void PhysicalRegion::expand(PhysicalAddress lower, PhysicalAddress upper)
|
|||
m_upper = upper;
|
||||
}
|
||||
|
||||
void PhysicalRegion::initialize_zones()
|
||||
{
|
||||
size_t remaining_pages = m_pages;
|
||||
auto base_address = m_lower;
|
||||
|
||||
auto make_zones = [&](size_t zone_size) {
|
||||
while (remaining_pages >= zone_size) {
|
||||
m_zones.append(make<PhysicalZone>(base_address, zone_size));
|
||||
dmesgln(" * Zone {:016x}-{:016x} ({} bytes)", base_address.get(), base_address.get() + zone_size * PAGE_SIZE - 1, zone_size * PAGE_SIZE);
|
||||
base_address = base_address.offset(zone_size * PAGE_SIZE);
|
||||
remaining_pages -= zone_size;
|
||||
}
|
||||
};
|
||||
|
||||
make_zones(4096);
|
||||
make_zones(256);
|
||||
}
|
||||
|
||||
unsigned PhysicalRegion::finalize_capacity()
|
||||
{
|
||||
VERIFY(!m_pages);
|
||||
|
||||
m_pages = (m_upper.get() - m_lower.get()) / PAGE_SIZE;
|
||||
m_bitmap.grow(m_pages, false);
|
||||
|
||||
return size();
|
||||
}
|
||||
|
||||
PhysicalRegion PhysicalRegion::take_pages_from_beginning(unsigned page_count)
|
||||
OwnPtr<PhysicalRegion> PhysicalRegion::try_take_pages_from_beginning(unsigned page_count)
|
||||
{
|
||||
VERIFY(m_used == 0);
|
||||
VERIFY(page_count > 0);
|
||||
|
@ -47,136 +82,64 @@ PhysicalRegion PhysicalRegion::take_pages_from_beginning(unsigned page_count)
|
|||
|
||||
// TODO: find a more elegant way to re-init the existing region
|
||||
m_pages = 0;
|
||||
m_bitmap = {}; // FIXME: Kind of wasteful
|
||||
finalize_capacity();
|
||||
|
||||
auto taken_region = create(taken_lower, taken_upper);
|
||||
taken_region.finalize_capacity();
|
||||
auto taken_region = try_create(taken_lower, taken_upper);
|
||||
if (!taken_region)
|
||||
return {};
|
||||
taken_region->finalize_capacity();
|
||||
return taken_region;
|
||||
}
|
||||
|
||||
NonnullRefPtrVector<PhysicalPage> PhysicalRegion::take_contiguous_free_pages(size_t count, size_t physical_alignment)
|
||||
{
|
||||
VERIFY(m_pages);
|
||||
VERIFY(m_used != m_pages);
|
||||
// FIXME: Care about alignment.
|
||||
(void)physical_alignment;
|
||||
|
||||
auto rounded_page_count = next_power_of_two(count);
|
||||
auto order = __builtin_ctz(rounded_page_count);
|
||||
|
||||
Optional<PhysicalAddress> page_base;
|
||||
for (auto& zone : m_zones) {
|
||||
page_base = zone.allocate_block(order);
|
||||
|
||||
if (page_base.has_value())
|
||||
break;
|
||||
}
|
||||
|
||||
if (!page_base.has_value())
|
||||
return {};
|
||||
|
||||
NonnullRefPtrVector<PhysicalPage> physical_pages;
|
||||
physical_pages.ensure_capacity(count);
|
||||
|
||||
auto first_contiguous_page = find_contiguous_free_pages(count, physical_alignment);
|
||||
|
||||
for (size_t index = 0; index < count; index++)
|
||||
physical_pages.append(PhysicalPage::create(m_lower.offset(PAGE_SIZE * (index + first_contiguous_page))));
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
physical_pages.append(PhysicalPage::create(page_base.value().offset(i * PAGE_SIZE)));
|
||||
return physical_pages;
|
||||
}
|
||||
|
||||
unsigned PhysicalRegion::find_contiguous_free_pages(size_t count, size_t physical_alignment)
|
||||
{
|
||||
VERIFY(count != 0);
|
||||
VERIFY(physical_alignment % PAGE_SIZE == 0);
|
||||
// search from the last page we allocated
|
||||
auto range = find_and_allocate_contiguous_range(count, physical_alignment / PAGE_SIZE);
|
||||
VERIFY(range.has_value());
|
||||
return range.value();
|
||||
}
|
||||
|
||||
Optional<unsigned> PhysicalRegion::find_one_free_page()
|
||||
{
|
||||
if (m_used == m_pages) {
|
||||
// We know we don't have any free pages, no need to check the bitmap
|
||||
// Check if we can draw one from the return queue
|
||||
if (m_recently_returned.size() > 0) {
|
||||
u8 index = get_fast_random<u8>() % m_recently_returned.size();
|
||||
Checked<PhysicalPtr> local_offset = m_recently_returned[index].get();
|
||||
local_offset -= m_lower.get();
|
||||
m_recently_returned.remove(index);
|
||||
VERIFY(!local_offset.has_overflow());
|
||||
VERIFY(local_offset.value() < (FlatPtr)(m_pages * PAGE_SIZE));
|
||||
return local_offset.value() / PAGE_SIZE;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
auto free_index = m_bitmap.find_one_anywhere_unset(m_free_hint);
|
||||
if (!free_index.has_value())
|
||||
return {};
|
||||
|
||||
auto page_index = free_index.value();
|
||||
m_bitmap.set(page_index, true);
|
||||
m_used++;
|
||||
m_free_hint = free_index.value() + 1; // Just a guess
|
||||
if (m_free_hint >= m_bitmap.size())
|
||||
m_free_hint = 0;
|
||||
return page_index;
|
||||
}
|
||||
|
||||
Optional<unsigned> PhysicalRegion::find_and_allocate_contiguous_range(size_t count, unsigned alignment)
|
||||
{
|
||||
VERIFY(count != 0);
|
||||
size_t found_pages_count = 0;
|
||||
// TODO: Improve how we deal with alignment != 1
|
||||
auto first_index = m_bitmap.find_longest_range_of_unset_bits(count + alignment - 1, found_pages_count);
|
||||
if (!first_index.has_value())
|
||||
return {};
|
||||
|
||||
auto page = first_index.value();
|
||||
if (alignment != 1) {
|
||||
auto lower_page = m_lower.get() / PAGE_SIZE;
|
||||
page = ((lower_page + page + alignment - 1) & ~(alignment - 1)) - lower_page;
|
||||
}
|
||||
if (found_pages_count >= count) {
|
||||
m_bitmap.set_range<true>(page, count);
|
||||
m_used += count;
|
||||
m_free_hint = first_index.value() + count + 1; // Just a guess
|
||||
if (m_free_hint >= m_bitmap.size())
|
||||
m_free_hint = 0;
|
||||
return page;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
RefPtr<PhysicalPage> PhysicalRegion::take_free_page()
|
||||
{
|
||||
VERIFY(m_pages);
|
||||
|
||||
auto free_index = find_one_free_page();
|
||||
if (!free_index.has_value())
|
||||
return nullptr;
|
||||
|
||||
return PhysicalPage::create(m_lower.offset((PhysicalPtr)free_index.value() * PAGE_SIZE));
|
||||
}
|
||||
|
||||
void PhysicalRegion::free_page_at(PhysicalAddress addr)
|
||||
{
|
||||
VERIFY(m_pages);
|
||||
|
||||
if (m_used == 0) {
|
||||
VERIFY_NOT_REACHED();
|
||||
for (auto& zone : m_zones) {
|
||||
auto page = zone.allocate_block(0);
|
||||
if (page.has_value())
|
||||
return PhysicalPage::create(page.value());
|
||||
}
|
||||
|
||||
Checked<PhysicalPtr> local_offset = addr.get();
|
||||
local_offset -= m_lower.get();
|
||||
VERIFY(!local_offset.has_overflow());
|
||||
VERIFY(local_offset.value() < ((PhysicalPtr)m_pages * PAGE_SIZE));
|
||||
|
||||
auto page = local_offset.value() / PAGE_SIZE;
|
||||
m_bitmap.set(page, false);
|
||||
m_free_hint = page; // We know we can find one here for sure
|
||||
m_used--;
|
||||
dbgln("PhysicalRegion::take_free_page: No free physical pages");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void PhysicalRegion::return_page(PhysicalAddress paddr)
|
||||
{
|
||||
auto returned_count = m_recently_returned.size();
|
||||
if (returned_count >= m_recently_returned.capacity()) {
|
||||
// Return queue is full, pick a random entry and free that page
|
||||
// and replace the entry with this page
|
||||
auto& entry = m_recently_returned[get_fast_random<u8>()];
|
||||
free_page_at(entry);
|
||||
entry = paddr;
|
||||
} else {
|
||||
// Still filling the return queue, just append it
|
||||
m_recently_returned.append(paddr);
|
||||
for (auto& zone : m_zones) {
|
||||
if (zone.contains(paddr)) {
|
||||
zone.deallocate_block(paddr, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue