bootloader: Overhaul heap region allocation.

Previously, there was only platform_init_heap/platform_release_heap,
which allocated a single static heap region for the heap to use,
and any subsequent heap allocations had to go through the standard
platform_allocate_region, which allocates regions visible both
to the bootloader and the kernel.

But as mentioned in previous changes, it isn't always easy to
release regions allocated that way. And besides, some bootloaders
(like EFI) use a completely separate mechanism to allocate
bootloader-local memory, which will never get "leaked" into
the kernel.

So instead, refactor all platforms to instead provide two
new methods: platform_{allocate,free}_heap_region. On EFI
this is easy to implement; on most other platforms we have
logic based more on the old platform_init_heap or allocate_region.

(On the BIOS loader in particular, we can only fully release
the memory if it's the last thing we allocated in the physical
addresses. If the "large allocation" threshhold is lowered
back to 16 KB, then we are unable to do this enough times
that we will run past the end of the 8 MB identity map and
thus fail to boot. But with the larger threshhold, we don't
leak nearly as much, and don't hit the threshhold.)

This should further reduce the amount of bootloader memory
permanently "leaked" into the kernel's used memory, though
on some platforms it may still be nonzero.

Change-Id: I5b2257fc5a425c024f298291f1401a26ea246383
Reviewed-on: https://review.haiku-os.org/c/haiku/+/8440
Reviewed-by: waddlesplash <[email protected]>
This commit is contained in:
Augustin Cavalier
2024-10-10 16:13:11 +00:00
committed by waddlesplash
parent 359a04ba96
commit 875b13d582
11 changed files with 153 additions and 140 deletions
+2 -2
View File
@@ -21,8 +21,8 @@ extern void panic(const char *format, ...);
extern void dprintf(const char *format, ...); extern void dprintf(const char *format, ...);
/* heap functions */ /* heap functions */
extern void platform_release_heap(struct stage2_args *args, void *base); extern ssize_t platform_allocate_heap_region(size_t size, void **_base);
extern status_t platform_init_heap(struct stage2_args *args, void **_base, void **_top); extern void platform_free_heap_region(void *_base, size_t size);
/* MMU/memory functions */ /* MMU/memory functions */
extern status_t platform_allocate_region(void **_virtualAddress, size_t size, extern status_t platform_allocate_region(void **_virtualAddress, size_t size,
+20 -16
View File
@@ -665,25 +665,29 @@ platform_free_region(void *address, size_t size)
} }
void ssize_t
platform_release_heap(struct stage2_args *args, void *base) platform_allocate_heap_region(size_t size, void **_base)
{ {
// It will be freed automatically, since it is in the addr_t base = get_next_physical_address(size);
// identity mapped region, and not stored in the kernel's if (base == 0)
// page tables.
}
status_t
platform_init_heap(struct stage2_args *args, void **_base, void **_top)
{
void *heap = (void *)get_next_physical_address(args->heap_size);
if (heap == NULL)
return B_NO_MEMORY; return B_NO_MEMORY;
*_base = heap; if ((base + size) > (32 * 1024 * 1024))
*_top = (void *)((int8 *)heap + args->heap_size); panic("platform_allocate_heap_region: region end is beyond identity map");
return B_OK;
*_base = (void*)base;
return size;
} }
void
platform_free_heap_region(void *_base, size_t size)
{
addr_t base = (addr_t)_base;
status_t status = remove_physical_allocated_range(base, size);
if (status == B_OK && sNextPhysicalAddress == (base + size))
sNextPhysicalAddress -= size;
// Failures don't matter very much as regions should be freed automatically,
// since they're in the identity map and not stored in the kernel's page tables.
}
+13 -11
View File
@@ -56,8 +56,7 @@ const static size_t kAlignment = 8;
const static size_t kDefaultHeapSize = (1024 + 512) * 1024; const static size_t kDefaultHeapSize = (1024 + 512) * 1024;
// default initial heap size, unless overridden by platform loader // default initial heap size, unless overridden by platform loader
const static size_t kLargeAllocationThreshold = 128 * 1024; const static size_t kLargeAllocationThreshold = 128 * 1024;
// allocations of this size or larger are allocated via // allocations of this size or larger are allocated separately
// platform_allocate_region()
class Chunk { class Chunk {
@@ -180,14 +179,17 @@ struct LargeAllocation {
status_t Allocate(size_t size) status_t Allocate(size_t size)
{ {
fSize = size; ssize_t actualSize = platform_allocate_heap_region(size, &fAddress);
return platform_allocate_region(&fAddress, fSize, if (actualSize < 0)
B_READ_AREA | B_WRITE_AREA, false); return actualSize;
fSize = actualSize;
return B_OK;
} }
void Free() void Free()
{ {
platform_free_region(fAddress, fSize); platform_free_heap_region(fAddress, fSize);
} }
void* Address() const void* Address() const
@@ -390,7 +392,7 @@ heap_release(stage2_args* args)
allocation = next; allocation = next;
} }
platform_release_heap(args, sHeapBase); platform_free_heap_region(sHeapBase, (addr_t)sHeapEnd - (addr_t)sHeapBase);
} }
@@ -411,13 +413,13 @@ heap_init(stage2_args* args)
args->heap_size = kDefaultHeapSize; args->heap_size = kDefaultHeapSize;
void* base; void* base;
void* top; ssize_t size = platform_allocate_heap_region(args->heap_size, &base);
if (platform_init_heap(args, &base, &top) < B_OK) if (size < 0)
return B_ERROR; return B_ERROR;
sHeapBase = base; sHeapBase = base;
sHeapEnd = top; sHeapEnd = (void*)((addr_t)base + size);
sMaxHeapSize = (uint8*)top - (uint8*)base; sMaxHeapSize = (uint8*)sHeapEnd - (uint8*)sHeapBase;
// declare the whole heap as one chunk, and add it // declare the whole heap as one chunk, and add it
// to the free list // to the free list
+19 -13
View File
@@ -663,25 +663,31 @@ platform_free_region(void *address, size_t size)
} }
void ssize_t
platform_release_heap(struct stage2_args *args, void *base) platform_allocate_heap_region(size_t size, void **_base)
{ {
// It will be freed automatically, since it is in the addr_t base = get_next_physical_address(size);
// identity mapped region, and not stored in the kernel's if (base == 0)
// page tables. return B_NO_MEMORY;
if ((base + size) > (32 * 1024 * 1024))
panic("platform_allocate_heap_region: region end is beyond identity map");
*_base = (void*)base;
return size;
} }
status_t void
platform_init_heap(struct stage2_args *args, void **_base, void **_top) platform_free_heap_region(void *_base, size_t size)
{ {
void *heap = (void *)get_next_physical_address(args->heap_size); addr_t base = (addr_t)_base;
if (heap == NULL) status_t status = remove_physical_allocated_range(base, size);
return B_NO_MEMORY; if (status == B_OK && sNextPhysicalAddress == (base + size))
sNextPhysicalAddress -= size;
*_base = heap; // Failures don't matter very much as regions should be freed automatically,
*_top = (void *)((int8 *)heap + args->heap_size); // since they're in the identity map and not stored in the kernel's page tables.
return B_OK;
} }
+19 -13
View File
@@ -668,25 +668,31 @@ platform_free_region(void *address, size_t size)
} }
void ssize_t
platform_release_heap(struct stage2_args *args, void *base) platform_allocate_heap_region(size_t size, void **_base)
{ {
// It will be freed automatically, since it is in the addr_t base = get_next_physical_address(size);
// identity mapped region, and not stored in the kernel's if (base == 0)
// page tables. return B_NO_MEMORY;
if ((base + size) > (32 * 1024 * 1024))
panic("platform_allocate_heap_region: region end is beyond identity map");
*_base = (void*)base;
return size;
} }
status_t void
platform_init_heap(struct stage2_args *args, void **_base, void **_top) platform_free_heap_region(void *_base, size_t size)
{ {
void *heap = (void *)get_next_physical_address(args->heap_size); addr_t base = (addr_t)_base;
if (heap == NULL) status_t status = remove_physical_allocated_range(base, size);
return B_NO_MEMORY; if (status == B_OK && sNextPhysicalAddress == (base + size))
sNextPhysicalAddress -= size;
*_base = heap; // Failures don't matter very much as regions should be freed automatically,
*_top = (void *)((int8 *)heap + args->heap_size); // since they're in the identity map and not stored in the kernel's page tables.
return B_OK;
} }
+22 -14
View File
@@ -73,6 +73,7 @@ segment_descriptor gBootGDT[BOOT_GDT_SEGMENT_COUNT];
static const uint32 kDefaultPageTableFlags = 0x07; // present, user, R/W static const uint32 kDefaultPageTableFlags = 0x07; // present, user, R/W
static const size_t kMaxKernelSize = 0x1000000; // 16 MB for the kernel static const size_t kMaxKernelSize = 0x1000000; // 16 MB for the kernel
static const size_t kIdentityMapEnd = (8 * 1024 * 1024);
// working page directory and page table // working page directory and page table
static uint32 *sPageDirectory = 0; static uint32 *sPageDirectory = 0;
@@ -170,7 +171,7 @@ add_page_table(addr_t base)
// Get new page table and clear it out // Get new page table and clear it out
uint32 *pageTable = get_next_page_table(); uint32 *pageTable = get_next_page_table();
if (pageTable > (uint32 *)(8 * 1024 * 1024)) { if (pageTable > (uint32 *)kIdentityMapEnd) {
panic("tried to add page table beyond the identity mapped 8 MB " panic("tried to add page table beyond the identity mapped 8 MB "
"region\n"); "region\n");
return NULL; return NULL;
@@ -809,25 +810,32 @@ platform_free_region(void *address, size_t size)
} }
void ssize_t
platform_release_heap(struct stage2_args *args, void *base) platform_allocate_heap_region(size_t size, void **_base)
{ {
// It will be freed automatically, since it is in the addr_t base = get_next_physical_address(size);
// identity mapped region, and not stored in the kernel's if (base == 0)
// page tables. return B_NO_MEMORY;
if ((base + size) > kIdentityMapEnd)
panic("platform_allocate_heap_region: region end is beyond identity map");
*_base = (void*)base;
return size;
} }
status_t void
platform_init_heap(struct stage2_args *args, void **_base, void **_top) platform_free_heap_region(void *_base, size_t size)
{ {
void *heap = (void *)get_next_physical_address(args->heap_size); addr_t base = (addr_t)_base;
if (heap == NULL) if (sNextPhysicalAddress == (base + size)) {
return B_NO_MEMORY; sNextPhysicalAddress -= size;
remove_physical_allocated_range(sNextPhysicalAddress, size);
}
*_base = heap; // Failures don't matter very much as regions should be freed automatically,
*_top = (void *)((int8 *)heap + args->heap_size); // since they're in the identity map and not stored in the kernel's page tables.
return B_OK;
} }
+13 -24
View File
@@ -1,40 +1,29 @@
/* /*
* Copyright 2016 Haiku, Inc. All rights reserved. * Copyright 2016-2024, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License. * Distributed under the terms of the MIT License.
*/ */
#include <boot/platform.h> #include <boot/platform.h>
#include <boot/stage2.h>
#include "efi_platform.h" #include "efi_platform.h"
#define STAGE_PAGES 0x2000 /* 32 MB */ extern "C" ssize_t
platform_allocate_heap_region(size_t _size, void** _base)
{
size_t pages = (_size + (B_PAGE_SIZE - 1)) / B_PAGE_SIZE;
efi_physical_addr base;
if (kBootServices->AllocatePages(AllocateAnyPages, EfiLoaderData, pages, &base) != EFI_SUCCESS)
return B_NO_MEMORY;
*_base = (void*)base;
static efi_physical_addr staging; return pages * B_PAGE_SIZE;
}
extern "C" void extern "C" void
platform_release_heap(struct stage2_args *args, void *base) platform_free_heap_region(void* base, size_t size)
{ {
if ((void*)staging != base) kBootServices->FreePages((efi_physical_addr)base, size / B_PAGE_SIZE);
panic("Attempt to release heap with wrong base address!");
kBootServices->FreePages(staging, STAGE_PAGES);
}
extern "C" status_t
platform_init_heap(struct stage2_args *args, void **_base, void **_top)
{
if (kBootServices->AllocatePages(AllocateAnyPages, EfiLoaderData,
STAGE_PAGES, &staging) != EFI_SUCCESS)
return B_NO_MEMORY;
*_base = (void*)staging;
*_top = (void*)((int8*)staging + STAGE_PAGES * B_PAGE_SIZE);
return B_OK;
} }
+7 -8
View File
@@ -150,19 +150,18 @@ platform_free_region(void *address, size_t size)
} }
void ssize_t
platform_release_heap(struct stage2_args *args, void *base) platform_allocate_heap_region(size_t size, void **_base)
{ {
// It will be freed automatically, since it is in the return B_UNSUPPORTED;
// identity mapped region, and not stored in the kernel's
// page tables.
} }
status_t void
platform_init_heap(struct stage2_args *args, void **_base, void **_top) platform_free_heap_region(void *_base, size_t size)
{ {
return B_UNSUPPORTED; // Failures don't matter very much as regions should be freed automatically,
// since they're in the identity map and not stored in the kernel's page tables.
} }
+8 -12
View File
@@ -20,29 +20,25 @@
#endif #endif
status_t ssize_t
platform_init_heap(stage2_args *args, void **_base, void **_top) platform_allocate_heap_region(size_t size, void **_base)
{ {
TRACE(("platform_init_heap()\n")); TRACE(("platform_allocate_heap_region()\n"));
*_base = NULL; *_base = NULL;
status_t error = platform_allocate_region(_base, args->heap_size, status_t error = platform_allocate_region(_base, size,
B_READ_AREA | B_WRITE_AREA, false); B_READ_AREA | B_WRITE_AREA, false);
if (error != B_OK) if (error != B_OK)
return error; return error;
printf("heap base = %p\n", *_base); printf("heap base = %p\n", *_base);
*_top = (void *)((int8 *)*_base + args->heap_size); return size;
printf("heap top = %p\n", *_top);
return B_OK;
} }
void void
platform_release_heap(stage2_args *args, void *base) platform_free_heap_region(void *_base, size_t size)
{ {
if (base != NULL) if (_base != NULL)
platform_free_region(base, args->heap_size); platform_free_region(_base, size);
} }
+10 -11
View File
@@ -352,22 +352,21 @@ platform_free_region(void* address, size_t size)
} }
void ssize_t
platform_release_heap(struct stage2_args* args, void* base) platform_allocate_heap_region(size_t size, void **_base)
{ {
} addr_t heap = AllocPhysPages(size);
status_t
platform_init_heap(struct stage2_args* args, void** _base, void** _top)
{
addr_t heap = AllocPhysPages(args->heap_size);
if (heap == 0) if (heap == 0)
return B_NO_MEMORY; return B_NO_MEMORY;
*_base = (void*)heap; *_base = (void*)heap;
*_top = (void*)(heap + args->heap_size); return size;
return B_OK; }
void
platform_free_heap_region(void *_base, size_t size)
{
} }
@@ -102,7 +102,8 @@ static addr_t sNextVirtualAddress = KERNEL_BASE + kMaxKernelSize;
//static addr_t sMaxVirtualAddress = KERNEL_BASE + kMaxKernelSize; //static addr_t sMaxVirtualAddress = KERNEL_BASE + kMaxKernelSize;
// working page directory and page table // working page directory and page table
static void *sPageTable = 0 ; static void *sPageTable = 0;
static bool sHeapRegionAllocated = false;
static addr_t static addr_t
@@ -404,25 +405,28 @@ platform_free_region(void *address, size_t size)
} }
ssize_t
platform_allocate_heap_region(size_t size, void **_base)
{
if (sHeapRegionAllocated)
return B_NO_MEMORY;
sHeapRegionAllocated = true;
// the heap is put right before the pagetable
void *heap = (uint8 *)sPageTable - size;
//FIXME: use phys addresses to allow passing args to U-Boot?
*_base = heap;
TRACE(("boot heap at 0x%p\n", *_base));
return size;
}
void void
platform_release_heap(struct stage2_args *args, void *base) platform_free_heap_region(void *_base, size_t size)
{ {
//XXX //XXX
// It will be freed automatically, since it is in the // It will be freed automatically, since it is in the
// identity mapped region, and not stored in the kernel's // identity mapped region, and not stored in the kernel's
// page tables. // page tables.
} }
status_t
platform_init_heap(struct stage2_args *args, void **_base, void **_top)
{
// the heap is put right before the pagetable
void *heap = (uint8 *)sPageTable - args->heap_size;
//FIXME: use phys addresses to allow passing args to U-Boot?
*_base = heap;
*_top = (void *)((int8 *)heap + args->heap_size);
TRACE(("boot heap at 0x%p to 0x%p\n", *_base, *_top));
return B_OK;
}