[boot][kernel] Replace frame allocator with bitmap-based one

The previous frame allocator involved a lot of splitting and merging
linked lists and lost all information about frames while they were
allocated. The new allocator is based on an array of descriptor
structures and a bitmap. Each memory map region of allocatable memory
becomes one or more descriptors, each mapping up to 1GiB of physical
memory. The descriptors implement two levels of a bitmap tree, and have
a pointer into the large contiguous bitmap to track individual pages.
This commit is contained in:
Justin C. Miller
2021-01-22 00:16:01 -08:00
parent fd8552ca3a
commit aae18fd035
14 changed files with 419 additions and 212 deletions

View File

@@ -55,7 +55,7 @@ console::console(uefi::boot_services *bs, uefi::protos::simple_text_output *out)
m_out->output_string(GIT_VERSION_WIDE);
m_out->set_attribute(uefi::attribute::light_gray);
m_out->output_string(L" booting...\r\n\n");
m_out->output_string(L" booting...\r\n");
if (m_fb.type != kernel::args::fb_type::none) {
wchar_t const * type = nullptr;

View File

@@ -27,7 +27,7 @@ load_file(
fs::file file = disk.open(path);
buffer b = file.load(type);
console::print(L" Loaded at: 0x%lx, %d bytes\r\n", b.data, b.size);
//console::print(L" Loaded at: 0x%lx, %d bytes\r\n", b.data, b.size);
return b;
}

View File

@@ -191,11 +191,14 @@ efi_main(uefi::handle image, uefi::system_table *st)
args->video = con.fb();
status_bar status {con.fb()}; // Switch to fb status display
// Map the kernel to the appropriate address
args::program &kernel = args->programs[0];
for (auto &section : kernel.sections)
if (section.size)
paging::map_section(args, section);
memory::fix_frame_blocks(args);
kernel::entrypoint kentry =
reinterpret_cast<kernel::entrypoint>(kernel.entrypoint);
status.next();

View File

@@ -14,6 +14,8 @@ namespace memory {
using mem_entry = kernel::args::mem_entry;
using mem_type = kernel::args::mem_type;
using frame_block = kernel::args::frame_block;
using kernel::args::frames_per_block;
size_t fixup_pointer_index = 0;
void **fixup_pointers[64];
@@ -107,29 +109,143 @@ can_merge(mem_entry &prev, mem_type type, uefi::memory_descriptor *next)
}
void
get_uefi_mappings(efi_mem_map *map, bool allocate, uefi::boot_services *bs)
get_uefi_mappings(efi_mem_map &map, uefi::boot_services *bs)
{
size_t length = 0;
size_t length = map.total;
uefi::status status = bs->get_memory_map(
&length, nullptr, &map->key, &map->size, &map->version);
&length, map.entries, &map.key, &map.size, &map.version);
map.length = length;
if (status == uefi::status::success)
return;
if (status != uefi::status::buffer_too_small)
error::raise(status, L"Error getting memory map size");
map->length = length;
if (allocate) {
map->length += 10*map->size;
if (map.entries) {
try_or_raise(
bs->allocate_pool(
uefi::memory_type::loader_data, map->length,
reinterpret_cast<void**>(&map->entries)),
L"Allocating space for memory map");
bs->free_pool(reinterpret_cast<void*>(map.entries)),
L"Freeing previous memory map space");
}
try_or_raise(
bs->get_memory_map(&map->length, map->entries, &map->key, &map->size, &map->version),
L"Getting UEFI memory map");
map.total = length + 10*map.size;
try_or_raise(
bs->allocate_pool(
uefi::memory_type::loader_data, map.total,
reinterpret_cast<void**>(&map.entries)),
L"Allocating space for memory map");
map.length = map.total;
try_or_raise(
bs->get_memory_map(&map.length, map.entries, &map.key, &map.size, &map.version),
L"Getting UEFI memory map");
}
inline size_t bitmap_size(size_t frames) { return (frames + 63) / 64; }
inline size_t num_blocks(size_t frames) { return (frames + (frames_per_block-1)) / frames_per_block; }
void
build_kernel_frame_blocks(const mem_entry *map, size_t nent, kernel::args::header *args, uefi::boot_services *bs)
{
status_line status {L"Creating kernel frame accounting map"};
size_t block_count = 0;
size_t total_bitmap_size = 0;
for (size_t i = 0; i < nent; ++i) {
const mem_entry &ent = map[i];
if (ent.type != mem_type::free)
continue;
block_count += num_blocks(ent.pages);
total_bitmap_size += bitmap_size(ent.pages) * sizeof(uint64_t);
}
size_t total_size = block_count * sizeof(frame_block) + total_bitmap_size;
frame_block *blocks = nullptr;
try_or_raise(
bs->allocate_pages(
uefi::allocate_type::any_pages,
uefi::memory_type::loader_data,
bytes_to_pages(total_size),
reinterpret_cast<void**>(&blocks)),
L"Error allocating kernel frame block space");
frame_block *next_block = blocks;
for (size_t i = 0; i < nent; ++i) {
const mem_entry &ent = map[i];
if (ent.type != mem_type::free)
continue;
size_t page_count = ent.pages;
uintptr_t base_addr = ent.start;
while (page_count) {
frame_block *blk = next_block++;
bs->set_mem(blk, sizeof(frame_block), 0);
blk->attrs = ent.attr;
blk->base = base_addr;
base_addr += frames_per_block * page_size;
if (page_count >= frames_per_block) {
page_count -= frames_per_block;
blk->count = frames_per_block;
blk->map1 = ~0ull;
bs->set_mem(blk->map2, sizeof(blk->map2), 0xff);
} else {
blk->count = page_count;
unsigned i = 0;
uint64_t b1 = (page_count + 4095) / 4096;
blk->map1 = (1 << b1) - 1;
uint64_t b2 = (page_count + 63) / 64;
uint64_t b2q = b2 / 64;
uint64_t b2r = b2 % 64;
bs->set_mem(blk->map2, b2q, 0xff);
blk->map2[b2q] = (1 << b2r) - 1;
break;
}
}
}
uint64_t *bitmap = reinterpret_cast<uint64_t*>(next_block);
bs->set_mem(bitmap, total_bitmap_size, 0);
for (unsigned i = 0; i < block_count; ++i) {
frame_block &blk = blocks[i];
blk.bitmap = bitmap;
size_t b = blk.count / 64;
size_t r = blk.count % 64;
bs->set_mem(blk.bitmap, b*8, 0xff);
blk.bitmap[b] = (1 << r) - 1;
bitmap += bitmap_size(blk.count);
}
args->frame_block_count = block_count;
args->frame_block_pages = bytes_to_pages(total_size);
args->frame_blocks = blocks;
}
void
fix_frame_blocks(kernel::args::header *args)
{
// Map the frame blocks to the appropriate address
paging::map_pages(args,
reinterpret_cast<uintptr_t>(args->frame_blocks),
::memory::bitmap_start,
args->frame_block_pages,
true, false);
uintptr_t offset = ::memory::bitmap_start -
reinterpret_cast<uintptr_t>(args->frame_blocks);
for (unsigned i = 0; i < args->frame_block_count; ++i) {
frame_block &blk = args->frame_blocks[i];
blk.bitmap = reinterpret_cast<uint64_t*>(
reinterpret_cast<uintptr_t>(blk.bitmap) + offset);
}
}
@@ -139,7 +255,7 @@ build_kernel_mem_map(kernel::args::header *args, uefi::boot_services *bs)
status_line status {L"Creating kernel memory map"};
efi_mem_map map;
get_uefi_mappings(&map, false, bs);
get_uefi_mappings(map, bs);
size_t map_size = map.num_entries() * sizeof(mem_entry);
@@ -153,9 +269,9 @@ build_kernel_mem_map(kernel::args::header *args, uefi::boot_services *bs)
L"Error allocating kernel memory map module space");
bs->set_mem(kernel_map, map_size, 0);
get_uefi_mappings(&map, true, bs);
get_uefi_mappings(map, bs);
size_t i = 0;
size_t nent = 0;
bool first = true;
for (auto desc : map) {
/*
@@ -176,11 +292,8 @@ build_kernel_mem_map(kernel::args::header *args, uefi::boot_services *bs)
case uefi::memory_type::boot_services_code:
case uefi::memory_type::boot_services_data:
case uefi::memory_type::conventional_memory:
type = mem_type::free;
break;
case uefi::memory_type::loader_data:
type = mem_type::pending;
type = mem_type::free;
break;
case uefi::memory_type::runtime_services_code:
@@ -210,18 +323,18 @@ build_kernel_mem_map(kernel::args::header *args, uefi::boot_services *bs)
// TODO: validate uefi's map is sorted
if (first) {
first = false;
kernel_map[i].start = desc->physical_start;
kernel_map[i].pages = desc->number_of_pages;
kernel_map[i].type = type;
kernel_map[i].attr = (desc->attribute & 0xffffffff);
kernel_map[nent].start = desc->physical_start;
kernel_map[nent].pages = desc->number_of_pages;
kernel_map[nent].type = type;
kernel_map[nent].attr = (desc->attribute & 0xffffffff);
continue;
}
mem_entry &prev = kernel_map[i];
mem_entry &prev = kernel_map[nent];
if (can_merge(prev, type, desc)) {
prev.pages += desc->number_of_pages;
} else {
mem_entry &next = kernel_map[++i];
mem_entry &next = kernel_map[++nent];
next.start = desc->physical_start;
next.pages = desc->number_of_pages;
next.type = type;
@@ -231,17 +344,19 @@ build_kernel_mem_map(kernel::args::header *args, uefi::boot_services *bs)
// Give just the actually-set entries in the header
args->mem_map = kernel_map;
args->map_count = i;
args->map_count = nent;
/*
// kernel map dump
for (unsigned i = 0; i < args->map_count; ++i) {
for (unsigned i = 0; i < nent; ++i) {
const kernel::args::mem_entry &e = kernel_map[i];
console::print(L" Range %lx (%lx) %x(%s) [%lu]\r\n",
e.start, e.attr, e.type, kernel_memory_type_name(e.type), e.pages);
}
*/
build_kernel_frame_blocks(kernel_map, nent, args, bs);
get_uefi_mappings(map, bs);
return map;
}

View File

@@ -41,12 +41,13 @@ struct efi_mem_map
using iterator = offset_iterator<desc>;
size_t length; ///< Total length of the map data
size_t total; ///< Total allocated space for map data
size_t size; ///< Size of an entry in the array
size_t key; ///< Key for detecting changes
uint32_t version; ///< Version of the `memory_descriptor` struct
desc *entries; ///< The array of UEFI descriptors
efi_mem_map() : length(0), size(0), key(0), version(0), entries(nullptr) {}
efi_mem_map() : length(0), total(0), size(0), key(0), version(0), entries(nullptr) {}
/// Get the count of entries in the array
inline size_t num_entries() const { return length / size; }
@@ -62,6 +63,14 @@ struct efi_mem_map
/// \returns The uefi memory map used to build the kernel map
efi_mem_map build_kernel_mem_map(kernel::args::header *args, uefi::boot_services *bs);
/// Create the kernel frame allocation maps
void build_kernel_frame_blocks(
const kernel::args::mem_entry *map, size_t nent,
kernel::args::header *args, uefi::boot_services *bs);
/// Map the frame allocation maps to the right spot and fix up pointers
void fix_frame_blocks(kernel::args::header *args);
/// Activate the given memory mappings. Sets the given page tables live as well
/// as informs UEFI runtime services of the new mappings.
/// \arg pml4 The root page table for the new mappings

View File

@@ -212,6 +212,7 @@ allocate_tables(kernel::args::header *args, uefi::boot_services *bs)
page_table *pml4 = reinterpret_cast<page_table*>(addr);
args->pml4 = pml4;
args->table_pages = tables_needed;
args->table_count = tables_needed - 1;
args->page_tables = offset_ptr<void>(addr, page_size);
@@ -220,7 +221,7 @@ allocate_tables(kernel::args::header *args, uefi::boot_services *bs)
add_kernel_pds(pml4, args->page_tables, args->table_count);
add_offset_mappings(pml4, args->page_tables, args->table_count);
console::print(L" Set up initial mappings, %d spare tables.\r\n", args->table_count);
//console::print(L" Set up initial mappings, %d spare tables.\r\n", args->table_count);
}
template <typename E>
@@ -231,32 +232,31 @@ constexpr bool has_flag(E set, E flag) {
}
void
map_section(
map_pages(
kernel::args::header *args,
const kernel::args::program_section &section)
uintptr_t phys, uintptr_t virt,
size_t count, bool write_flag, bool exe_flag)
{
if (!count)
return;
paging::page_table *pml4 =
reinterpret_cast<paging::page_table*>(args->pml4);
size_t pages = memory::bytes_to_pages(section.size);
page_entry_iterator<4> iterator{
section.virt_addr, pml4,
virt, pml4,
args->page_tables,
args->table_count};
using kernel::args::section_flags;
uint64_t flags = page_flags;
if (!has_flag(section.type, section_flags::execute))
if (!exe_flag)
flags |= (1ull << 63); // set NX bit
if (has_flag(section.type, section_flags::write))
if (write_flag)
flags |= 2;
uintptr_t phys = section.phys_addr;
while (true) {
*iterator = phys | flags;
if (--pages == 0)
if (--count == 0)
break;
iterator.increment();
@@ -264,6 +264,24 @@ map_section(
}
}
void
map_section(
kernel::args::header *args,
const kernel::args::program_section &section)
{
using kernel::args::section_flags;
size_t pages = memory::bytes_to_pages(section.size);
map_pages(
args,
section.phys_addr,
section.virt_addr,
pages,
has_flag(section.type, section_flags::write),
has_flag(section.type, section_flags::execute));
}
} // namespace paging
} // namespace boot

View File

@@ -38,6 +38,14 @@ void allocate_tables(
/// tables in the current PML4.
void add_current_mappings(page_table *new_pml4);
/// Map physical memory pages to virtual addresses in the given page tables.
/// \arg args The kernel args header, used for the page table cache and pml4
/// \arg section The program section to load
void map_pages(
kernel::args::header *args,
uintptr_t phys, uintptr_t virt,
size_t count, bool write_flag, bool exe_flag);
/// Map a program section in physical memory to its virtual address in the
/// given page tables.
/// \arg args The kernel args header, used for the page table cache and pml4