Complete rework of the heap implementation. Freelists are now part of the pages
and pages are now kept in lists as well. This allows to return free pages once a bin does not need them anymore. Partially filled pages are kept in a sorted linked list so that allocation will always happen on the fullest page - this favours having full pages and makes it more likely lightly used pages will get completely empty so they can be returned. Generally this now goes more in the direction of a slab allocator. The allocation logic has been extracted, so a heap is now simply attachable to a region of memory. This allows for multiple heaps and for dynamic growing. In case the allocator runs out of free pages, an asynchronous growing thread is notified to create a new area and attach a new heap to it. By default the kernel heap is now set to 16MB and grows by 8MB each time all heaps run full. This should solve quite a few issues, like certain bins just claiming all pages so that even if there is free space nothing can be allocated. Also it obviously does aways with filling the heap page by page until it overgrows. I think this is now a well performing and scalable allocator we can live with for quite some time. It is well tested under emulation and real hardware and performs as expected. If problems come up there is an extensive sanity checker that can be enabled by PARANOID_VALIDATION that covers most aspects of the allocator. For normal operation this is not necessary though and is therefore disabled by default. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@23939 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
@@ -5,18 +5,17 @@
|
||||
* Copyright 2001-2002, Travis Geiselbrecht. All rights reserved.
|
||||
* Distributed under the terms of the NewOS License.
|
||||
*/
|
||||
#ifndef _KERNEL_MEMHEAP_H
|
||||
#define _KERNEL_MEMHEAP_H
|
||||
|
||||
#ifndef _KERNEL_HEAP_H
|
||||
#define _KERNEL_HEAP_H
|
||||
|
||||
#include <OS.h>
|
||||
|
||||
struct kernel_args;
|
||||
// allocate 16MB initial heap for the kernel
|
||||
#define INITIAL_HEAP_SIZE 16 * 1024 * 1024
|
||||
// grow by another 8MB each time the heap runs out of memory
|
||||
#define HEAP_GROW_SIZE 8 * 1024 * 1024
|
||||
|
||||
|
||||
#define HEAP_SIZE 0x02000000
|
||||
// 32 MB heap for the kernel (!)
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -25,8 +24,8 @@ extern "C" {
|
||||
void *memalign(size_t alignment, size_t size);
|
||||
|
||||
status_t heap_init(addr_t heapBase, size_t heapSize);
|
||||
status_t heap_init_post_sem(struct kernel_args *args);
|
||||
status_t heap_init_post_thread(struct kernel_args *args);
|
||||
status_t heap_init_post_sem();
|
||||
status_t heap_init_post_thread();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
@@ -21,7 +21,7 @@ KernelMergeObject kernel_core.o :
|
||||
condition_variable.cpp
|
||||
cpu.c
|
||||
elf.cpp
|
||||
heap.c
|
||||
heap.cpp
|
||||
image.c
|
||||
int.c
|
||||
kernel_daemon.c
|
||||
|
||||
@@ -1,770 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2006, Axel Dörfler, [email protected].
|
||||
* Distributed under the terms of the MIT License.
|
||||
*
|
||||
* Copyright 2001, Travis Geiselbrecht. All rights reserved.
|
||||
* Distributed under the terms of the NewOS License.
|
||||
*/
|
||||
|
||||
|
||||
#include <KernelExport.h>
|
||||
#include <kernel.h>
|
||||
#include <vm.h>
|
||||
#include <lock.h>
|
||||
#include <int.h>
|
||||
#include <memheap.h>
|
||||
#include <malloc.h>
|
||||
#include <debug.h>
|
||||
#include <arch/cpu.h>
|
||||
#include <boot/kernel_args.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
//#define TRACE_HEAP
|
||||
#ifdef TRACE_HEAP
|
||||
# define TRACE(x) dprintf x
|
||||
#else
|
||||
# define TRACE(x) ;
|
||||
#endif
|
||||
|
||||
/* prevent freeing pointers that were not allocated by kmalloc or are already freeed */
|
||||
#if KDEBUG > 1
|
||||
# define PARANOID_POINTER_CHECK 1
|
||||
#else
|
||||
# define PARANOID_POINTER_CHECK 0
|
||||
#endif
|
||||
/* initialize newly allocated memory with something non zero */
|
||||
#define PARANOID_KMALLOC 1
|
||||
/* check if freed pointers are already freed, and fill freed memory with 0xdeadbeef */
|
||||
#define PARANOID_KFREE 1
|
||||
/* use a back and front wall around each allocation */
|
||||
#define USE_WALL 0
|
||||
#define USE_CHECKING_WALL 0
|
||||
|
||||
#define WALL_SIZE 8
|
||||
// must be a multiple of 4
|
||||
|
||||
#if USE_CHECKING_WALL
|
||||
# define WALL_CHECK_FREQUENCY 1 /* every tenth second */
|
||||
#endif
|
||||
|
||||
#if USE_WALL
|
||||
struct front_wall {
|
||||
# if USE_CHECKING_WALL
|
||||
struct list_link link;
|
||||
# endif
|
||||
size_t alignment;
|
||||
size_t size;
|
||||
uint32 wall[WALL_SIZE / 4];
|
||||
};
|
||||
|
||||
struct back_wall {
|
||||
uint32 wall[WALL_SIZE / 4];
|
||||
};
|
||||
#endif // USE_WALL
|
||||
|
||||
// heap stuff
|
||||
// ripped mostly from nujeffos
|
||||
|
||||
struct heap_page {
|
||||
uint16 bin_index : 5;
|
||||
uint16 free_count : 9;
|
||||
uint16 cleaning : 1;
|
||||
uint16 in_use : 1;
|
||||
} PACKED;
|
||||
|
||||
// used for bin==bin_count allocations
|
||||
#define allocation_id free_count
|
||||
|
||||
static vint32 current_alloc_id = 0;
|
||||
static struct heap_page *heap_alloc_table;
|
||||
static addr_t heap_base_ptr;
|
||||
static addr_t heap_base;
|
||||
static addr_t heap_size;
|
||||
|
||||
struct heap_bin {
|
||||
uint32 element_size;
|
||||
uint32 grow_size;
|
||||
uint32 alloc_count;
|
||||
void *free_list;
|
||||
uint32 free_count;
|
||||
char *raw_list;
|
||||
uint32 raw_count;
|
||||
};
|
||||
|
||||
static struct heap_bin bins[] = {
|
||||
{16, B_PAGE_SIZE, 0, 0, 0, 0, 0},
|
||||
{32, B_PAGE_SIZE, 0, 0, 0, 0, 0},
|
||||
{64, B_PAGE_SIZE, 0, 0, 0, 0, 0},
|
||||
{128, B_PAGE_SIZE, 0, 0, 0, 0, 0},
|
||||
{256, B_PAGE_SIZE, 0, 0, 0, 0, 0},
|
||||
{512, B_PAGE_SIZE, 0, 0, 0, 0, 0},
|
||||
{1024, B_PAGE_SIZE, 0, 0, 0, 0, 0},
|
||||
{2048, B_PAGE_SIZE, 0, 0, 0, 0, 0},
|
||||
{0x1000, 0x1000, 0, 0, 0, 0, 0},
|
||||
{0x2000, 0x2000, 0, 0, 0, 0, 0},
|
||||
{0x3000, 0x3000, 0, 0, 0, 0, 0},
|
||||
{0x4000, 0x4000, 0, 0, 0, 0, 0},
|
||||
{0x5000, 0x5000, 0, 0, 0, 0, 0},
|
||||
{0x6000, 0x6000, 0, 0, 0, 0, 0},
|
||||
{0x7000, 0x7000, 0, 0, 0, 0, 0},
|
||||
{0x8000, 0x8000, 0, 0, 0, 0, 0},
|
||||
{0x9000, 0x9000, 0, 0, 0, 0, 0},
|
||||
{0xa000, 0xa000, 0, 0, 0, 0, 0},
|
||||
{0xb000, 0xb000, 0, 0, 0, 0, 0},
|
||||
{0xc000, 0xc000, 0, 0, 0, 0, 0},
|
||||
{0xd000, 0xd000, 0, 0, 0, 0, 0},
|
||||
{0xe000, 0xe000, 0, 0, 0, 0, 0},
|
||||
{0xf000, 0xf000, 0, 0, 0, 0, 0},
|
||||
{0x10000, 0x10000, 0, 0, 0, 0, 0} // 64k
|
||||
};
|
||||
|
||||
static const int bin_count = sizeof(bins) / sizeof(struct heap_bin);
|
||||
static mutex heap_lock;
|
||||
|
||||
#if USE_CHECKING_WALL
|
||||
sem_id sWallCheckLock = -1;
|
||||
struct list sWalls;
|
||||
#endif
|
||||
|
||||
#if PARANOID_POINTER_CHECK
|
||||
|
||||
#define PTRCHECKLIST_ENTRIES 32768
|
||||
static void *ptrchecklist[PTRCHECKLIST_ENTRIES]; /* automatically initialized to zero */
|
||||
|
||||
static void
|
||||
ptrchecklist_store(void *ptr)
|
||||
{
|
||||
int i;
|
||||
mutex_lock(&heap_lock);
|
||||
for (i = 0; i != PTRCHECKLIST_ENTRIES; i++)
|
||||
if (ptrchecklist[i] == NULL) {
|
||||
ptrchecklist[i] = ptr;
|
||||
break;
|
||||
}
|
||||
mutex_unlock(&heap_lock);
|
||||
if (i == PTRCHECKLIST_ENTRIES)
|
||||
panic("Sorry, out of entries, increase PTRCHECKLIST_ENTRIES in heap.c\n");
|
||||
}
|
||||
|
||||
|
||||
static bool
|
||||
ptrchecklist_remove(void *ptr)
|
||||
{
|
||||
int i;
|
||||
bool found = false;
|
||||
mutex_lock(&heap_lock);
|
||||
for (i = 0; i != PTRCHECKLIST_ENTRIES; i++)
|
||||
if (ptrchecklist[i] == ptr) {
|
||||
ptrchecklist[i] = NULL;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
mutex_unlock(&heap_lock);
|
||||
return found;
|
||||
}
|
||||
|
||||
#endif /* PARANOID_POINTER_CHECK */
|
||||
|
||||
#if USE_WALL
|
||||
|
||||
static size_t
|
||||
wall_size(size_t alignment)
|
||||
{
|
||||
if (alignment == 0)
|
||||
return sizeof(struct front_wall) + sizeof(struct back_wall);
|
||||
|
||||
return 2 * alignment;
|
||||
}
|
||||
|
||||
|
||||
static void *
|
||||
get_base_address_from_wall(struct front_wall *wall)
|
||||
{
|
||||
if (wall->alignment == 0)
|
||||
return (void *)wall;
|
||||
|
||||
return (void *)((addr_t)wall + sizeof(struct front_wall) - wall->alignment);
|
||||
}
|
||||
|
||||
|
||||
static struct front_wall *
|
||||
get_wall(void *address)
|
||||
{
|
||||
return (struct front_wall *)((addr_t)address - sizeof(struct front_wall));
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
set_wall(struct front_wall *wall, size_t size, size_t alignment)
|
||||
{
|
||||
struct back_wall *backWall;
|
||||
uint32 i;
|
||||
|
||||
size -= wall_size(alignment);
|
||||
|
||||
wall->alignment = alignment;
|
||||
wall->size = size;
|
||||
|
||||
for (i = 0; i < WALL_SIZE / sizeof(uint32); i++) {
|
||||
wall->wall[i] = 0xabadcafe;
|
||||
}
|
||||
|
||||
backWall = (struct back_wall *)((addr_t)wall + sizeof(struct front_wall) + size);
|
||||
for (i = 0; i < WALL_SIZE / sizeof(uint32); i++) {
|
||||
backWall->wall[i] = 0xabadcafe;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void *
|
||||
add_wall(void *address, size_t size, size_t alignment)
|
||||
{
|
||||
struct front_wall *wall;
|
||||
|
||||
if (alignment == 0)
|
||||
address = (uint8 *)address + sizeof(struct front_wall);
|
||||
else
|
||||
address = (uint8 *)address + alignment;
|
||||
|
||||
wall = get_wall(address);
|
||||
set_wall(wall, size, alignment);
|
||||
|
||||
#if USE_CHECKING_WALL
|
||||
acquire_sem(sWallCheckLock);
|
||||
list_add_link_to_tail(&sWalls, &wall->link);
|
||||
release_sem(sWallCheckLock);
|
||||
#endif
|
||||
return address;
|
||||
}
|
||||
|
||||
|
||||
void check_wall(void *address);
|
||||
void
|
||||
check_wall(void *address)
|
||||
{
|
||||
struct front_wall *frontWall = get_wall(address);
|
||||
struct back_wall *backWall;
|
||||
uint32 i;
|
||||
|
||||
for (i = 0; i < WALL_SIZE / 4; i++) {
|
||||
if (frontWall->wall[i] != 0xabadcafe) {
|
||||
panic("free: front wall %i (%p) was overwritten (allocation at %p, %lu bytes): %08lx\n",
|
||||
i, frontWall, address, frontWall->size, frontWall->wall[i]);
|
||||
}
|
||||
}
|
||||
|
||||
backWall = (struct back_wall *)((uint8 *)address + frontWall->size);
|
||||
|
||||
for (i = 0; i < WALL_SIZE / 4; i++) {
|
||||
if (backWall->wall[i] != 0xabadcafe) {
|
||||
panic("free: back wall %i (%p) was overwritten (allocation at %p, %lu bytes): %08lx\n",
|
||||
i, backWall, address, frontWall->size, backWall->wall[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* USE_WALL */
|
||||
|
||||
#if USE_CHECKING_WALL
|
||||
|
||||
static void
|
||||
check_wall_daemon(void *arg, int iteration)
|
||||
{
|
||||
struct front_wall *wall = NULL;
|
||||
|
||||
acquire_sem(sWallCheckLock);
|
||||
|
||||
while ((wall = list_get_next_item(&sWalls, wall)) != NULL) {
|
||||
check_wall((uint8 *)wall + sizeof(struct front_wall));
|
||||
}
|
||||
|
||||
release_sem(sWallCheckLock);
|
||||
}
|
||||
|
||||
#endif /* USE_CHECKING_WALL */
|
||||
|
||||
static void
|
||||
dump_bin(int bin_index)
|
||||
{
|
||||
struct heap_bin *bin = &bins[bin_index];
|
||||
unsigned int *temp;
|
||||
|
||||
dprintf("%d:\tesize %lu\tgrow_size %lu\talloc_count %lu\tfree_count %lu\traw_count %lu\traw_list %p\n",
|
||||
bin_index, bin->element_size, bin->grow_size, bin->alloc_count, bin->free_count, bin->raw_count, bin->raw_list);
|
||||
dprintf("free_list: ");
|
||||
for(temp = bin->free_list; temp != NULL; temp = (unsigned int *)*temp) {
|
||||
dprintf("%p ", temp);
|
||||
}
|
||||
dprintf("NULL\n");
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
dump_bin_list(int argc, char **argv)
|
||||
{
|
||||
int i;
|
||||
|
||||
dprintf("%d heap bins at %p:\n", bin_count, bins);
|
||||
|
||||
for (i = 0; i < bin_count; i++) {
|
||||
dump_bin(i);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* called from vm_init. The heap should already be mapped in at this point, we just
|
||||
* do a little housekeeping to set up the data structure.
|
||||
*/
|
||||
|
||||
status_t
|
||||
heap_init(addr_t heapBase, size_t heapSize)
|
||||
{
|
||||
const unsigned int page_entries = B_PAGE_SIZE / sizeof(struct heap_page);
|
||||
// set some global pointers
|
||||
heap_alloc_table = (struct heap_page *)heapBase;
|
||||
heap_size = ((uint64)heapSize * page_entries / (page_entries + 1)) & ~(B_PAGE_SIZE-1);
|
||||
heap_base = (unsigned int)heap_alloc_table + PAGE_ALIGN(heap_size / page_entries);
|
||||
heap_base_ptr = heap_base;
|
||||
TRACE(("heap_alloc_table = %p, heap_base = 0x%lx, heap_size = 0x%lx\n",
|
||||
heap_alloc_table, heap_base, heap_size));
|
||||
|
||||
// zero out the heap alloc table at the base of the heap
|
||||
memset((void *)heap_alloc_table, 0, (heap_size / B_PAGE_SIZE) * sizeof(struct heap_page));
|
||||
|
||||
// pre-init the mutex to at least fall through any semaphore calls
|
||||
heap_lock.sem = -1;
|
||||
heap_lock.holder = -1;
|
||||
|
||||
#if USE_CHECKING_WALL
|
||||
list_init(&sWalls);
|
||||
#endif
|
||||
|
||||
// set up some debug commands
|
||||
add_debugger_command("heap_bindump", &dump_bin_list, "dump stats about bin usage");
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
heap_init_post_sem(kernel_args *args)
|
||||
{
|
||||
if (mutex_init(&heap_lock, "heap_mutex") < 0)
|
||||
panic("error creating heap mutex\n");
|
||||
|
||||
#if USE_CHECKING_WALL
|
||||
sWallCheckLock = create_sem(1, "check wall");
|
||||
#endif
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
heap_init_post_thread(kernel_args *args)
|
||||
{
|
||||
#if USE_CHECKING_WALL
|
||||
register_kernel_daemon(check_wall_daemon, NULL, WALL_CHECK_FREQUENCY);
|
||||
#endif
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
static inline int32
|
||||
next_alloc_id(void)
|
||||
{
|
||||
return atomic_add(¤t_alloc_id, 1) & ((1<<(9-1))-1);
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
raw_alloc(unsigned int size, int bin_index)
|
||||
{
|
||||
unsigned int new_heap_ptr;
|
||||
char *retval;
|
||||
struct heap_page *page;
|
||||
addr_t addr;
|
||||
|
||||
new_heap_ptr = heap_base_ptr + PAGE_ALIGN(size);
|
||||
if (new_heap_ptr > heap_base + heap_size) {
|
||||
panic("heap overgrew itself!\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (addr = heap_base_ptr; addr < new_heap_ptr; addr += B_PAGE_SIZE) {
|
||||
page = &heap_alloc_table[(addr - heap_base) / B_PAGE_SIZE];
|
||||
page->in_use = 1;
|
||||
page->cleaning = 0;
|
||||
page->bin_index = bin_index;
|
||||
if (bin_index < bin_count && bins[bin_index].element_size < B_PAGE_SIZE)
|
||||
page->free_count = B_PAGE_SIZE / bins[bin_index].element_size;
|
||||
else
|
||||
page->free_count = 1;
|
||||
}
|
||||
|
||||
retval = (char *)heap_base_ptr;
|
||||
heap_base_ptr = new_heap_ptr;
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
#if DEBUG
|
||||
static bool
|
||||
is_valid_alignment(size_t number)
|
||||
{
|
||||
// this cryptic line accepts zero and all powers of two
|
||||
return ((~number + 1) | ((number << 1) - 1)) == ~0UL;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// #pragma mark -
|
||||
|
||||
|
||||
void *
|
||||
memalign(size_t alignment, size_t size)
|
||||
{
|
||||
void *address = NULL;
|
||||
int bin_index;
|
||||
unsigned int i;
|
||||
struct heap_page *page;
|
||||
|
||||
TRACE(("memalign(alignment = %lu, size = %lu\n", alignment, size));
|
||||
|
||||
#if DEBUG
|
||||
if (!is_valid_alignment(alignment))
|
||||
panic("memalign() with an alignment which is not a power of 2\n");
|
||||
#endif
|
||||
|
||||
if (!kernel_startup && !are_interrupts_enabled())
|
||||
panic("malloc: called with interrupts disabled\n");
|
||||
|
||||
mutex_lock(&heap_lock);
|
||||
|
||||
#if USE_WALL
|
||||
if (alignment > 0) {
|
||||
// make the alignment big enough to contain the front wall
|
||||
while (alignment < sizeof(struct front_wall))
|
||||
alignment *= 2;
|
||||
}
|
||||
|
||||
size += wall_size(alignment);
|
||||
#else
|
||||
// ToDo: that code "aligns" the buffer because the bins are always
|
||||
// aligned on their bin size
|
||||
if (size < alignment)
|
||||
size = alignment;
|
||||
#endif
|
||||
|
||||
for (bin_index = 0; bin_index < bin_count; bin_index++)
|
||||
if (size <= bins[bin_index].element_size)
|
||||
break;
|
||||
|
||||
if (bin_index == bin_count) {
|
||||
int32 alloc_id;
|
||||
alloc_id = next_alloc_id();
|
||||
|
||||
// try to find freed blocks first...
|
||||
if (size < (heap_base_ptr - heap_base) / 10) { // but don't try too hard
|
||||
int first = -1;
|
||||
page = heap_alloc_table;
|
||||
for (i = 0; i < (heap_base_ptr - heap_base) / B_PAGE_SIZE; i++) {
|
||||
if (page[i].in_use) {
|
||||
first = -1;
|
||||
continue;
|
||||
}
|
||||
if (first > 0) {
|
||||
if ((1 + i - first) * B_PAGE_SIZE > size)
|
||||
break;
|
||||
} else
|
||||
first = i;
|
||||
}
|
||||
if (first > -1)
|
||||
address = (void *)(heap_base + first * B_PAGE_SIZE);
|
||||
}
|
||||
if (address == NULL)
|
||||
address = raw_alloc(size, bin_index);
|
||||
page = &heap_alloc_table[((unsigned int)address - heap_base) / B_PAGE_SIZE];
|
||||
for (i = 0; i < (size + B_PAGE_SIZE - 1) / B_PAGE_SIZE; i++) {
|
||||
page[i].in_use = 1;
|
||||
page[i].cleaning = 0;
|
||||
page[i].bin_index = bin_count;
|
||||
page[i].allocation_id = alloc_id;
|
||||
}
|
||||
} else {
|
||||
if (bins[bin_index].free_list != NULL) {
|
||||
address = bins[bin_index].free_list;
|
||||
bins[bin_index].free_list = (void *)(*(unsigned int *)bins[bin_index].free_list);
|
||||
bins[bin_index].free_count--;
|
||||
#if PARANOID_KFREE
|
||||
// Ensure that the 0xdeadbeef is cleared so we do not walk this
|
||||
// freelist if the user does not clear/use these bytes.
|
||||
((uint32 *)address)[1] = 0;
|
||||
#endif
|
||||
} else {
|
||||
if (bins[bin_index].raw_count == 0) {
|
||||
bins[bin_index].raw_list = raw_alloc(bins[bin_index].grow_size, bin_index);
|
||||
bins[bin_index].raw_count = bins[bin_index].grow_size / bins[bin_index].element_size;
|
||||
}
|
||||
|
||||
bins[bin_index].raw_count--;
|
||||
address = bins[bin_index].raw_list;
|
||||
bins[bin_index].raw_list += bins[bin_index].element_size;
|
||||
}
|
||||
|
||||
bins[bin_index].alloc_count++;
|
||||
page = &heap_alloc_table[((unsigned int)address - heap_base) / B_PAGE_SIZE];
|
||||
page[0].free_count--;
|
||||
|
||||
TRACE(("kmalloc0: page %p: bin_index %d, free_count %d\n", page, page->bin_index, page->free_count));
|
||||
|
||||
for(i = 1; i < bins[bin_index].element_size / B_PAGE_SIZE; i++) {
|
||||
page[i].free_count--;
|
||||
TRACE(("kmalloc1: page 0x%x: bin_index %d, free_count %d\n", page[i], page[i].bin_index, page[i].free_count));
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&heap_lock);
|
||||
|
||||
TRACE(("kmalloc: asked to allocate size %d, returning ptr = %p\n", size, address));
|
||||
|
||||
#if PARANOID_KMALLOC
|
||||
memset(address, 0xcc, size);
|
||||
#endif
|
||||
|
||||
#if PARANOID_POINTER_CHECK
|
||||
ptrchecklist_store(address);
|
||||
#endif
|
||||
|
||||
#if USE_WALL
|
||||
address = add_wall(address, size, alignment);
|
||||
#endif
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
|
||||
void *
|
||||
malloc(size_t size)
|
||||
{
|
||||
return memalign(0, size);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
free(void *address)
|
||||
{
|
||||
struct heap_page *page;
|
||||
struct heap_bin *bin;
|
||||
unsigned int i;
|
||||
|
||||
if (!kernel_startup && !are_interrupts_enabled())
|
||||
panic("free: called with interrupts disabled\n");
|
||||
|
||||
if (address == NULL)
|
||||
return;
|
||||
|
||||
if ((addr_t)address < heap_base || (addr_t)address >= (heap_base + heap_size))
|
||||
panic("free: asked to free invalid address %p\n", address);
|
||||
|
||||
#if USE_WALL
|
||||
{
|
||||
struct front_wall *wall = get_wall(address);
|
||||
|
||||
#if USE_CHECKING_WALL
|
||||
acquire_sem(sWallCheckLock);
|
||||
list_remove_link(&wall->link);
|
||||
release_sem(sWallCheckLock);
|
||||
#endif
|
||||
check_wall(address);
|
||||
address = get_base_address_from_wall(wall);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if PARANOID_POINTER_CHECK
|
||||
if (!ptrchecklist_remove(address))
|
||||
panic("free(): asked to free invalid pointer %p\n", address);
|
||||
#endif
|
||||
|
||||
mutex_lock(&heap_lock);
|
||||
|
||||
TRACE(("free(): asked to free at ptr = %p\n", address));
|
||||
|
||||
page = &heap_alloc_table[((unsigned)address - heap_base) / B_PAGE_SIZE];
|
||||
|
||||
TRACE(("free(): page %p: bin_index %d, free_count %d\n", page, page->bin_index, page->free_count));
|
||||
|
||||
if (page[0].bin_index > bin_count)
|
||||
panic("free(): page %p: invalid bin_index %d\n", page, page->bin_index);
|
||||
|
||||
if (page[0].bin_index < bin_count) {
|
||||
bin = &bins[page[0].bin_index];
|
||||
|
||||
if (bin->element_size <= B_PAGE_SIZE && (addr_t)address % bin->element_size != 0)
|
||||
panic("kfree: passed invalid pointer %p! Supposed to be in bin for esize 0x%lx\n", address, bin->element_size);
|
||||
|
||||
for (i = 0; i < bin->element_size / B_PAGE_SIZE; i++) {
|
||||
if (page[i].bin_index != page[0].bin_index)
|
||||
panic("free(): not all pages in allocation match bin_index\n");
|
||||
page[i].free_count++;
|
||||
}
|
||||
|
||||
#if PARANOID_KFREE
|
||||
if (((uint32 *)address)[1] == 0xdeadbeef) {
|
||||
// This block looks like it was freed already, walk the free list
|
||||
// on this bin to make sure this address doesn't exist.
|
||||
unsigned int *temp;
|
||||
for (temp = bin->free_list; temp != NULL; temp = (unsigned int *)*temp) {
|
||||
if (temp == (unsigned int *)address)
|
||||
panic("free(): address %p already exists in bin free list\n", address);
|
||||
}
|
||||
}
|
||||
|
||||
// mark the free space as freed
|
||||
{
|
||||
uint32 i;
|
||||
uint32 *dead = (uint32 *)address;
|
||||
|
||||
if (bin->element_size % 4 != 0)
|
||||
panic("free(): didn't expect a bin element size that is not a multiple of 4\n");
|
||||
|
||||
// the first 4 bytes are overwritten with the next free list pointer later
|
||||
for (i = 1; i < bin->element_size / 4; i++)
|
||||
dead[i] = 0xdeadbeef;
|
||||
}
|
||||
#endif
|
||||
|
||||
*(unsigned int *)address = (unsigned int)bin->free_list;
|
||||
bin->free_list = address;
|
||||
bin->alloc_count--;
|
||||
bin->free_count++;
|
||||
} else {
|
||||
// this chunk has been allocated via raw_alloc() directly
|
||||
// TODO: since the heap must be replaced anyway, we don't
|
||||
// free this allocation anymore... (tracking them would
|
||||
// require some extra stuff)
|
||||
|
||||
for (i = 1; i <= (heap_base_ptr - heap_base) / B_PAGE_SIZE; i++) {
|
||||
if (!page[i].in_use)
|
||||
break;
|
||||
if (page[i].bin_index != bin_count)
|
||||
break;
|
||||
if (page[i].allocation_id != page[0].allocation_id)
|
||||
break;
|
||||
page[i].in_use = 0;
|
||||
page[i].cleaning = 0;
|
||||
page[i].allocation_id = 0;
|
||||
}
|
||||
page[0].in_use = 0;
|
||||
page[0].cleaning = 0;
|
||||
page[0].allocation_id = 0;
|
||||
}
|
||||
|
||||
mutex_unlock(&heap_lock);
|
||||
}
|
||||
|
||||
|
||||
/** Naive implementation of realloc() - it's very simple but
|
||||
* it's there and working.
|
||||
* It takes the bin of the current allocation if the new size
|
||||
* fits in and is larger than the size of the next smaller bin.
|
||||
* If not, it allocates a new chunk of memory, and copies and
|
||||
* frees the old buffer.
|
||||
*/
|
||||
|
||||
void *
|
||||
realloc(void *address, size_t newSize)
|
||||
{
|
||||
void *newAddress = NULL;
|
||||
size_t maxSize = 0, minSize;
|
||||
|
||||
if (!kernel_startup && !are_interrupts_enabled())
|
||||
panic("realloc(): called with interrupts disabled\n");
|
||||
|
||||
if (address != NULL && ((addr_t)address < heap_base || (addr_t)address >= (heap_base + heap_size)))
|
||||
panic("realloc(): asked to realloc invalid address %p\n", address);
|
||||
|
||||
if (newSize == 0) {
|
||||
free(address);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// find out the size of the old allocation first
|
||||
|
||||
if (address != NULL) {
|
||||
struct heap_page *page;
|
||||
#if USE_WALL
|
||||
struct front_wall *wall = get_wall(address);
|
||||
#endif
|
||||
|
||||
mutex_lock(&heap_lock);
|
||||
page = &heap_alloc_table[((unsigned)address - heap_base) / B_PAGE_SIZE];
|
||||
|
||||
TRACE(("realloc(): page %p: bin_index %d, free_count %d\n", page, page->bin_index, page->free_count));
|
||||
|
||||
if (page[0].bin_index > bin_count)
|
||||
panic("realloc(): page %p: invalid bin_index %d\n", page, page->bin_index);
|
||||
|
||||
if (page[0].bin_index < bin_count) {
|
||||
maxSize = bins[page[0].bin_index].element_size;
|
||||
minSize = page[0].bin_index > 0 ? bins[page[0].bin_index - 1].element_size : 0;
|
||||
} else {
|
||||
int i;
|
||||
for (i = 1; (addr_t)&page[i] < heap_base; i++) {
|
||||
if (!page[i].in_use)
|
||||
break;
|
||||
if (page[i].bin_index != bin_count)
|
||||
break;
|
||||
if (page[i].allocation_id != page[0].allocation_id)
|
||||
break;
|
||||
}
|
||||
minSize = 0;
|
||||
maxSize = i * B_PAGE_SIZE;
|
||||
}
|
||||
|
||||
mutex_unlock(&heap_lock);
|
||||
|
||||
#if USE_WALL
|
||||
newSize += wall_size(wall->alignment);
|
||||
#endif
|
||||
|
||||
// does the new allocation simply fit in the bin?
|
||||
if (newSize > minSize && newSize <= maxSize) {
|
||||
#if USE_WALL
|
||||
check_wall(address);
|
||||
|
||||
// we need to move the back wall, and honour the
|
||||
// alignment so that the address stays the same
|
||||
set_wall(wall, newSize, wall->alignment);
|
||||
#endif
|
||||
return address;
|
||||
}
|
||||
}
|
||||
|
||||
// if not, allocate a new chunk of memory
|
||||
newAddress = malloc(newSize);
|
||||
if (newAddress == NULL)
|
||||
return NULL;
|
||||
|
||||
// copy the old data and free the old allocation
|
||||
if (address) {
|
||||
// we do have the maxSize of the bin at this point
|
||||
memcpy(newAddress, address, min(maxSize, newSize));
|
||||
free(address);
|
||||
}
|
||||
|
||||
return newAddress;
|
||||
}
|
||||
|
||||
|
||||
void *
|
||||
calloc(size_t numElements, size_t size)
|
||||
{
|
||||
void *address = malloc(numElements * size);
|
||||
if (address != NULL)
|
||||
memset(address, 0, numElements * size);
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,921 @@
|
||||
/*
|
||||
* Copyright 2008, Michael Lotz, [email protected].
|
||||
* Distributed under the terms of the MIT License.
|
||||
*
|
||||
* Copyright 2002-2006, Axel Dörfler, [email protected].
|
||||
* Distributed under the terms of the MIT License.
|
||||
*
|
||||
* Copyright 2001, Travis Geiselbrecht. All rights reserved.
|
||||
* Distributed under the terms of the NewOS License.
|
||||
*/
|
||||
#include <debug.h>
|
||||
#include <heap.h>
|
||||
#include <int.h>
|
||||
#include <kernel.h>
|
||||
#include <lock.h>
|
||||
#include <malloc.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#include <vm.h>
|
||||
|
||||
//#define TRACE_HEAP
|
||||
#ifdef TRACE_HEAP
|
||||
# define TRACE(x) dprintf x
|
||||
#else
|
||||
# define TRACE(x) ;
|
||||
#endif
|
||||
|
||||
// initialize newly allocated memory with something non zero
|
||||
#define PARANOID_KMALLOC 1
|
||||
// check for double free, and fill freed memory with 0xdeadbeef
|
||||
#define PARANOID_KFREE 1
|
||||
// validate sanity of the heap after each operation (slow!)
|
||||
#define PARANOID_VALIDATION 0
|
||||
|
||||
typedef struct heap_page_s {
|
||||
uint16 index;
|
||||
uint16 bin_index : 5;
|
||||
uint16 free_count : 10;
|
||||
uint16 in_use : 1;
|
||||
heap_page_s * next;
|
||||
heap_page_s * prev;
|
||||
uint16 empty_index;
|
||||
addr_t * free_list;
|
||||
} heap_page;
|
||||
|
||||
// used for bin == bin_count allocations
|
||||
#define allocation_id free_count
|
||||
|
||||
typedef struct heap_bin_s {
|
||||
uint32 element_size;
|
||||
uint16 max_free_count;
|
||||
heap_page * page_list; // sorted so that the desired page is always first
|
||||
} heap_bin;
|
||||
|
||||
typedef struct heap_allocator_s {
|
||||
addr_t base;
|
||||
size_t size;
|
||||
mutex lock;
|
||||
vint32 large_alloc_id;
|
||||
|
||||
uint32 bin_count;
|
||||
uint32 page_count;
|
||||
heap_page * free_pages;
|
||||
|
||||
heap_bin * bins;
|
||||
heap_page * page_table;
|
||||
|
||||
heap_allocator_s * next;
|
||||
} heap_allocator;
|
||||
|
||||
static heap_allocator *sHeapList = NULL;
|
||||
static heap_allocator *sLastGrowRequest = NULL;
|
||||
static sem_id sHeapGrowSem = -1;
|
||||
static sem_id sHeapGrownNotify = -1;
|
||||
|
||||
|
||||
static void
|
||||
dump_page(heap_page *page)
|
||||
{
|
||||
uint32 count = 0;
|
||||
for (addr_t *temp = page->free_list; temp != NULL; temp = (addr_t *)*temp)
|
||||
count++;
|
||||
|
||||
dprintf("\t\tpage %p: bin_index: %u; free_count: %u; empty_index: %u; free_list %p (%lu entr%s)\n",
|
||||
page, page->bin_index, page->free_count, page->empty_index,
|
||||
page->free_list, count, count == 1 ? "y" : "ies");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
dump_bin(heap_bin *bin)
|
||||
{
|
||||
dprintf("\telement_size: %lu; max_free_count: %u; page_list %p;\n",
|
||||
bin->element_size, bin->max_free_count, bin->page_list);
|
||||
|
||||
for (heap_page *temp = bin->page_list; temp != NULL; temp = temp->next)
|
||||
dump_page(temp);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
dump_allocator(heap_allocator *heap)
|
||||
{
|
||||
uint32 count = 0;
|
||||
for (heap_page *page = heap->free_pages; page != NULL; page = page->next)
|
||||
count++;
|
||||
|
||||
dprintf("allocator %p: base: 0x%08lx; size: %lu; bin_count: %lu; free_pages: %p (%lu entr%s)\n", heap,
|
||||
heap->base, heap->size, heap->bin_count, heap->free_pages, count,
|
||||
count == 1 ? "y" : "ies");
|
||||
|
||||
for (uint32 i = 0; i < heap->bin_count; i++)
|
||||
dump_bin(&heap->bins[i]);
|
||||
|
||||
dprintf("\n");
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
dump_heap_list(int argc, char **argv)
|
||||
{
|
||||
heap_allocator *heap = sHeapList;
|
||||
while (heap) {
|
||||
dump_allocator(heap);
|
||||
heap = heap->next;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#if PARANOID_VALIDATION
|
||||
static void
|
||||
heap_validate_heap(heap_allocator *heap)
|
||||
{
|
||||
mutex_lock(&heap->lock);
|
||||
|
||||
// validate the free pages list
|
||||
uint32 freePageCount = 0;
|
||||
heap_page *lastPage = NULL;
|
||||
heap_page *page = heap->free_pages;
|
||||
while (page) {
|
||||
if ((addr_t)page < (addr_t)&heap->page_table[0]
|
||||
|| (addr_t)page >= (addr_t)&heap->page_table[heap->page_count])
|
||||
panic("free page is not part of the page table\n");
|
||||
|
||||
if (page->index >= heap->page_count)
|
||||
panic("free page has invalid index\n");
|
||||
|
||||
if ((addr_t)&heap->page_table[page->index] != (addr_t)page)
|
||||
panic("free page index does not lead to target page\n");
|
||||
|
||||
if (page->prev != lastPage)
|
||||
panic("free page entry has invalid prev link\n");
|
||||
|
||||
if (page->in_use)
|
||||
panic("free page marked as in use\n");
|
||||
|
||||
lastPage = page;
|
||||
page = page->next;
|
||||
freePageCount++;
|
||||
}
|
||||
|
||||
// validate the page table
|
||||
uint32 usedPageCount = 0;
|
||||
for (uint32 i = 0; i < heap->page_count; i++) {
|
||||
if (heap->page_table[i].in_use)
|
||||
usedPageCount++;
|
||||
}
|
||||
|
||||
if (freePageCount + usedPageCount != heap->page_count) {
|
||||
panic("free pages and used pages do not add up (%lu + %lu != %lu)\n",
|
||||
freePageCount, usedPageCount, heap->page_count);
|
||||
}
|
||||
|
||||
// validate the bins
|
||||
for (uint32 i = 0; i < heap->bin_count; i++) {
|
||||
heap_bin *bin = &heap->bins[i];
|
||||
|
||||
lastPage = NULL;
|
||||
page = bin->page_list;
|
||||
int32 lastFreeCount = 0;
|
||||
while (page) {
|
||||
if ((addr_t)page < (addr_t)&heap->page_table[0]
|
||||
|| (addr_t)page >= (addr_t)&heap->page_table[heap->page_count])
|
||||
panic("used page is not part of the page table\n");
|
||||
|
||||
if (page->index >= heap->page_count)
|
||||
panic("used page has invalid index\n");
|
||||
|
||||
if ((addr_t)&heap->page_table[page->index] != (addr_t)page)
|
||||
panic("used page index does not lead to target page\n");
|
||||
|
||||
if (page->prev != lastPage)
|
||||
panic("used page entry has invalid prev link (%p vs %p bin %lu)\n",
|
||||
page->prev, lastPage, i);
|
||||
|
||||
if (!page->in_use)
|
||||
panic("used page marked as not in use\n");
|
||||
|
||||
if (page->bin_index != i)
|
||||
panic("used page with bin index %u in page list of bin %lu\n",
|
||||
page->bin_index, i);
|
||||
|
||||
if (page->free_count < lastFreeCount)
|
||||
panic("ordering of bin page list broken\n");
|
||||
|
||||
// validate the free list
|
||||
uint32 freeSlotsCount = 0;
|
||||
addr_t *element = page->free_list;
|
||||
addr_t pageBase = heap->base + page->index * B_PAGE_SIZE;
|
||||
while (element) {
|
||||
if ((addr_t)element < pageBase
|
||||
|| (addr_t)element >= pageBase + B_PAGE_SIZE)
|
||||
panic("free list entry out of page range\n");
|
||||
|
||||
if (((addr_t)element - pageBase) % bin->element_size != 0)
|
||||
panic("free list entry not on a element boundary\n");
|
||||
|
||||
element = (addr_t *)*element;
|
||||
freeSlotsCount++;
|
||||
}
|
||||
|
||||
uint32 slotCount = bin->max_free_count;
|
||||
if (page->empty_index > slotCount)
|
||||
panic("empty index beyond slot count (%u with %lu slots)\n",
|
||||
page->empty_index, slotCount);
|
||||
|
||||
freeSlotsCount += (slotCount - page->empty_index);
|
||||
if (freeSlotsCount > slotCount)
|
||||
panic("more free slots than fit into the page\n");
|
||||
|
||||
lastPage = page;
|
||||
lastFreeCount = page->free_count;
|
||||
page = page->next;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&heap->lock);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
heap_allocator *
|
||||
heap_attach(addr_t base, size_t size, bool postSem)
|
||||
{
|
||||
heap_allocator *heap = (heap_allocator *)base;
|
||||
base += sizeof(heap_allocator);
|
||||
size -= sizeof(heap_allocator);
|
||||
|
||||
size_t binSizes[] = { 16, 32, 64, 96, 128, 192, 256, 384, 512, 1024, 2048, B_PAGE_SIZE };
|
||||
uint32 binCount = sizeof(binSizes) / sizeof(binSizes[0]);
|
||||
heap->bin_count = binCount;
|
||||
heap->bins = (heap_bin *)base;
|
||||
base += binCount * sizeof(heap_bin);
|
||||
size -= binCount * sizeof(heap_bin);
|
||||
|
||||
for (uint32 i = 0; i < binCount; i++) {
|
||||
heap_bin *bin = &heap->bins[i];
|
||||
bin->element_size = binSizes[i];
|
||||
bin->max_free_count = B_PAGE_SIZE / binSizes[i];
|
||||
bin->page_list = NULL;
|
||||
}
|
||||
|
||||
uint32 pageCount = size / B_PAGE_SIZE;
|
||||
size_t pageTableSize = pageCount * sizeof(heap_page);
|
||||
heap->page_table = (heap_page *)base;
|
||||
base += pageTableSize;
|
||||
size -= pageTableSize;
|
||||
|
||||
// the rest is now actually usable memory (rounded to the next page)
|
||||
heap->base = (addr_t)(base + B_PAGE_SIZE - 1) / B_PAGE_SIZE * B_PAGE_SIZE;
|
||||
heap->size = (size_t)(size / B_PAGE_SIZE) * B_PAGE_SIZE;
|
||||
|
||||
// now we know the real page count
|
||||
pageCount = heap->size / B_PAGE_SIZE;
|
||||
heap->page_count = pageCount;
|
||||
|
||||
// zero out the heap alloc table at the base of the heap
|
||||
memset((void *)heap->page_table, 0, pageTableSize);
|
||||
for (uint32 i = 0; i < pageCount; i++)
|
||||
heap->page_table[i].index = i;
|
||||
|
||||
// add all pages up into the free pages list
|
||||
for (uint32 i = 1; i < pageCount; i++) {
|
||||
heap->page_table[i - 1].next = &heap->page_table[i];
|
||||
heap->page_table[i].prev = &heap->page_table[i - 1];
|
||||
}
|
||||
heap->free_pages = &heap->page_table[0];
|
||||
heap->page_table[0].prev = NULL;
|
||||
|
||||
if (postSem) {
|
||||
if (mutex_init(&heap->lock, "heap_mutex") < 0) {
|
||||
panic("heap_attach(): error creating heap mutex\n");
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
// pre-init the mutex to at least fall through any semaphore calls
|
||||
heap->lock.sem = -1;
|
||||
heap->lock.holder = -1;
|
||||
}
|
||||
|
||||
heap->next = NULL;
|
||||
dprintf("heap_attach: attached to %p - usable range 0x%08lx - 0x%08lx\n",
|
||||
heap, heap->base, heap->base + heap->size);
|
||||
return heap;
|
||||
}
|
||||
|
||||
|
||||
static inline uint32
|
||||
heap_next_alloc_id(heap_allocator *heap)
|
||||
{
|
||||
return atomic_add(&heap->large_alloc_id, 1) & ((1 << 9) - 1);
|
||||
}
|
||||
|
||||
|
||||
static inline void
|
||||
heap_link_page(heap_page *page, heap_page **list)
|
||||
{
|
||||
page->prev = NULL;
|
||||
page->next = *list;
|
||||
if (page->next)
|
||||
page->next->prev = page;
|
||||
*list = page;
|
||||
}
|
||||
|
||||
|
||||
static inline void
|
||||
heap_unlink_page(heap_page *page, heap_page **list)
|
||||
{
|
||||
if (page->prev)
|
||||
page->prev->next = page->next;
|
||||
if (page->next)
|
||||
page->next->prev = page->prev;
|
||||
if (list && *list == page) {
|
||||
*list = page->next;
|
||||
if (page->next)
|
||||
page->next->prev = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void *
|
||||
heap_raw_alloc(heap_allocator *heap, size_t size, uint32 binIndex)
|
||||
{
|
||||
heap_bin *bin = NULL;
|
||||
if (binIndex < heap->bin_count)
|
||||
bin = &heap->bins[binIndex];
|
||||
|
||||
if (bin && bin->page_list != NULL) {
|
||||
// we have a page where we have a free slot
|
||||
void *address = NULL;
|
||||
heap_page *page = bin->page_list;
|
||||
if (page->free_list) {
|
||||
// there's a previously freed entry we can use
|
||||
address = page->free_list;
|
||||
page->free_list = (addr_t *)*page->free_list;
|
||||
} else {
|
||||
// the page hasn't been fully allocated so use the next empty_index
|
||||
address = (void *)(heap->base + page->index * B_PAGE_SIZE
|
||||
+ page->empty_index * bin->element_size);
|
||||
page->empty_index++;
|
||||
}
|
||||
|
||||
page->free_count--;
|
||||
if (page->free_count == 0) {
|
||||
// the page is now full so we remove it from the page_list
|
||||
bin->page_list = page->next;
|
||||
if (page->next)
|
||||
page->next->prev = NULL;
|
||||
page->next = page->prev = NULL;
|
||||
}
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
// we don't have anything free right away, we must allocate a new page
|
||||
if (heap->free_pages == NULL) {
|
||||
// there are no free pages anymore, we ran out of memory
|
||||
TRACE(("heap %p: no free pages to allocate %lu bytes\n", heap, size));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (bin) {
|
||||
// small allocation, just grab the next free page
|
||||
heap_page *page = heap->free_pages;
|
||||
heap->free_pages = page->next;
|
||||
if (page->next)
|
||||
page->next->prev = NULL;
|
||||
|
||||
page->in_use = 1;
|
||||
page->bin_index = binIndex;
|
||||
page->free_count = bin->max_free_count - 1;
|
||||
page->empty_index = 1;
|
||||
page->free_list = NULL;
|
||||
page->next = page->prev = NULL;
|
||||
|
||||
if (page->free_count > 0) {
|
||||
// by design there are no other pages in the bins page list
|
||||
bin->page_list = page;
|
||||
}
|
||||
|
||||
// we return the first slot in this page
|
||||
return (void *)(heap->base + page->index * B_PAGE_SIZE);
|
||||
}
|
||||
|
||||
// large allocation, we must search for contiguous slots
|
||||
bool found = false;
|
||||
int32 first = -1;
|
||||
for (uint32 i = 0; i < heap->page_count; i++) {
|
||||
if (heap->page_table[i].in_use) {
|
||||
first = -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (first > 0) {
|
||||
if ((1 + i - first) * B_PAGE_SIZE >= size) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
} else
|
||||
first = i;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
TRACE(("heap %p: found no contiguous pages to allocate %ld bytes\n", heap, size));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint32 allocationID = heap_next_alloc_id(heap);
|
||||
uint32 pageCount = (size + B_PAGE_SIZE - 1) / B_PAGE_SIZE;
|
||||
for (uint32 i = first; i < first + pageCount; i++) {
|
||||
heap_page *page = &heap->page_table[i];
|
||||
page->in_use = 1;
|
||||
page->bin_index = binIndex;
|
||||
|
||||
heap_unlink_page(page, &heap->free_pages);
|
||||
|
||||
page->next = page->prev = NULL;
|
||||
page->free_list = NULL;
|
||||
page->allocation_id = allocationID;
|
||||
}
|
||||
|
||||
return (void *)(heap->base + first * B_PAGE_SIZE);
|
||||
}
|
||||
|
||||
|
||||
#if DEBUG
|
||||
static bool
|
||||
is_valid_alignment(size_t number)
|
||||
{
|
||||
// this cryptic line accepts zero and all powers of two
|
||||
return ((~number + 1) | ((number << 1) - 1)) == ~0UL;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
static void *
|
||||
heap_memalign(heap_allocator *heap, size_t alignment, size_t size,
|
||||
bool *shouldGrow)
|
||||
{
|
||||
TRACE(("memalign(alignment = %lu, size = %lu)\n", alignment, size));
|
||||
|
||||
#if DEBUG
|
||||
if (!is_valid_alignment(alignment))
|
||||
panic("memalign() with an alignment which is not a power of 2\n");
|
||||
#endif
|
||||
|
||||
mutex_lock(&heap->lock);
|
||||
|
||||
// ToDo: that code "aligns" the buffer because the bins are always
|
||||
// aligned on their bin size
|
||||
if (size < alignment)
|
||||
size = alignment;
|
||||
|
||||
uint32 binIndex;
|
||||
for (binIndex = 0; binIndex < heap->bin_count; binIndex++) {
|
||||
if (size <= heap->bins[binIndex].element_size)
|
||||
break;
|
||||
}
|
||||
|
||||
void *address = heap_raw_alloc(heap, size, binIndex);
|
||||
|
||||
TRACE(("memalign(): asked to allocate %lu bytes, returning pointer %p\n", size, address));
|
||||
|
||||
if (heap->next == NULL) {
|
||||
// suggest growing if we are the last heap and we have
|
||||
// less than three free pages left
|
||||
*shouldGrow = (heap->free_pages == NULL
|
||||
|| heap->free_pages->next == NULL
|
||||
|| heap->free_pages->next->next == NULL);
|
||||
}
|
||||
|
||||
mutex_unlock(&heap->lock);
|
||||
if (address == NULL)
|
||||
return address;
|
||||
|
||||
#if PARANOID_KFREE
|
||||
// make sure 0xdeadbeef is cleared if we do not overwrite the memory
|
||||
// and the user does not clear it
|
||||
((uint32 *)address)[1] = 0xcccccccc;
|
||||
#endif
|
||||
|
||||
#if PARANOID_KMALLOC
|
||||
memset(address, 0xcc, size);
|
||||
#endif
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
heap_free(heap_allocator *heap, void *address)
|
||||
{
|
||||
if (address == NULL)
|
||||
return B_OK;
|
||||
|
||||
if ((addr_t)address < heap->base
|
||||
|| (addr_t)address >= heap->base + heap->size) {
|
||||
// this address does not belong to us
|
||||
return B_ENTRY_NOT_FOUND;
|
||||
}
|
||||
|
||||
mutex_lock(&heap->lock);
|
||||
|
||||
TRACE(("free(): asked to free at ptr = %p\n", address));
|
||||
|
||||
heap_page *page = &heap->page_table[((addr_t)address - heap->base) / B_PAGE_SIZE];
|
||||
|
||||
TRACE(("free(): page %p: bin_index %d, free_count %d\n", page, page->bin_index, page->free_count));
|
||||
|
||||
if (page->bin_index > heap->bin_count) {
|
||||
panic("free(): page %p: invalid bin_index %d\n", page, page->bin_index);
|
||||
mutex_unlock(&heap->lock);
|
||||
return B_ERROR;
|
||||
}
|
||||
|
||||
if (page->bin_index < heap->bin_count) {
|
||||
// small allocation
|
||||
heap_bin *bin = &heap->bins[page->bin_index];
|
||||
if (((addr_t)address - heap->base - page->index * B_PAGE_SIZE) % bin->element_size != 0) {
|
||||
panic("free(): passed invalid pointer %p supposed to be in bin for element size %ld\n", address, bin->element_size);
|
||||
mutex_unlock(&heap->lock);
|
||||
return B_ERROR;
|
||||
}
|
||||
|
||||
#if PARANOID_KFREE
|
||||
if (((uint32 *)address)[1] == 0xdeadbeef) {
|
||||
// This block looks like it was freed already, walk the free list
|
||||
// on this page to make sure this address doesn't exist.
|
||||
for (addr_t *temp = page->free_list; temp != NULL; temp = (addr_t *)*temp) {
|
||||
if (temp == address) {
|
||||
panic("free(): address %p already exists in page free list\n", address);
|
||||
mutex_unlock(&heap->lock);
|
||||
return B_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32 *dead = (uint32 *)address;
|
||||
if (bin->element_size % 4 != 0) {
|
||||
panic("free(): didn't expect a bin element size that is not a multiple of 4\n");
|
||||
mutex_unlock(&heap->lock);
|
||||
return B_ERROR;
|
||||
}
|
||||
|
||||
// the first 4 bytes are overwritten with the next free list pointer later
|
||||
for (uint32 i = 1; i < bin->element_size / sizeof(uint32); i++)
|
||||
dead[i] = 0xdeadbeef;
|
||||
#endif
|
||||
|
||||
// add the address to the page free list
|
||||
*(addr_t *)address = (addr_t)page->free_list;
|
||||
page->free_list = (addr_t *)address;
|
||||
page->free_count++;
|
||||
|
||||
if (page->free_count == bin->max_free_count) {
|
||||
// we are now empty, remove the page from the bin list
|
||||
heap_unlink_page(page, &bin->page_list);
|
||||
page->in_use = 0;
|
||||
heap_link_page(page, &heap->free_pages);
|
||||
} else if (page->free_count == 1) {
|
||||
// we need to add ourselfs to the page list of the bin
|
||||
heap_link_page(page, &bin->page_list);
|
||||
} else {
|
||||
// we might need to move back in the free pages list
|
||||
if (page->next && page->next->free_count < page->free_count) {
|
||||
// move ourselfs so the list stays ordered
|
||||
heap_page *insert = page->next;
|
||||
while (insert->next
|
||||
&& insert->next->free_count < page->free_count)
|
||||
insert = insert->next;
|
||||
|
||||
heap_unlink_page(page, &bin->page_list);
|
||||
|
||||
page->prev = insert;
|
||||
page->next = insert->next;
|
||||
if (page->next)
|
||||
page->next->prev = page;
|
||||
insert->next = page;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// large allocation, just return the pages to the page free list
|
||||
uint32 allocationID = page->allocation_id;
|
||||
uint32 maxPages = heap->page_count - page->index;
|
||||
for (uint32 i = 0; i < maxPages; i++) {
|
||||
// loop until we find the end of this allocation
|
||||
if (!page[i].in_use || page[i].bin_index != heap->bin_count
|
||||
|| page[i].allocation_id != allocationID)
|
||||
break;
|
||||
|
||||
// this page still belongs to the same allocation
|
||||
page[i].in_use = 0;
|
||||
page[i].allocation_id = 0;
|
||||
|
||||
// return it to the free list
|
||||
heap_link_page(&page[i], &heap->free_pages);
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&heap->lock);
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
heap_realloc(heap_allocator *heap, void *address, void **newAddress,
|
||||
size_t newSize)
|
||||
{
|
||||
if ((addr_t)address < heap->base
|
||||
|| (addr_t)address >= heap->base + heap->size) {
|
||||
// this address does not belong to us
|
||||
return B_ENTRY_NOT_FOUND;
|
||||
}
|
||||
|
||||
mutex_lock(&heap->lock);
|
||||
TRACE(("realloc(address = %p, newSize = %lu)\n", address, newSize));
|
||||
|
||||
heap_page *page = &heap->page_table[((addr_t)address - heap->base) / B_PAGE_SIZE];
|
||||
if (page->bin_index > heap->bin_count) {
|
||||
panic("realloc(): page %p: invalid bin_index %d\n", page, page->bin_index);
|
||||
mutex_unlock(&heap->lock);
|
||||
return B_ERROR;
|
||||
}
|
||||
|
||||
// find out the size of the old allocation first
|
||||
size_t minSize = 0;
|
||||
size_t maxSize = 0;
|
||||
if (page->bin_index < heap->bin_count) {
|
||||
// this was a small allocation
|
||||
heap_bin *bin = &heap->bins[page->bin_index];
|
||||
maxSize = bin->element_size;
|
||||
if (page->bin_index > 0)
|
||||
minSize = heap->bins[page->bin_index - 1].element_size + 1;
|
||||
} else {
|
||||
// this was a large allocation
|
||||
uint32 allocationID = page->allocation_id;
|
||||
uint32 maxPages = heap->page_count - page->index;
|
||||
maxSize = B_PAGE_SIZE;
|
||||
for (uint32 i = 1; i < maxPages; i++) {
|
||||
if (!page[i].in_use || page[i].bin_index != heap->bin_count
|
||||
|| page[i].allocation_id != allocationID)
|
||||
break;
|
||||
|
||||
minSize += B_PAGE_SIZE;
|
||||
maxSize += B_PAGE_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&heap->lock);
|
||||
|
||||
// does the new allocation simply fit in the old allocation?
|
||||
if (newSize > minSize && newSize <= maxSize) {
|
||||
*newAddress = address;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
// if not, allocate a new chunk of memory
|
||||
*newAddress = malloc(newSize);
|
||||
if (*newAddress == NULL) {
|
||||
// we tried but it didn't work out, but still the operation is done
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
// copy the old data and free the old allocation
|
||||
memcpy(*newAddress, address, min_c(maxSize, newSize));
|
||||
free(address);
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark -
|
||||
|
||||
|
||||
static int32
|
||||
heap_grow_thread(void *)
|
||||
{
|
||||
heap_allocator *heap = sHeapList;
|
||||
while (true) {
|
||||
// wait for a request to grow the heap list
|
||||
if (acquire_sem(sHeapGrowSem) < B_OK)
|
||||
continue;
|
||||
|
||||
// find the last heap
|
||||
while (heap->next)
|
||||
heap = heap->next;
|
||||
|
||||
if (sLastGrowRequest != heap) {
|
||||
// we have already grown since the latest request, just ignore
|
||||
continue;
|
||||
}
|
||||
|
||||
TRACE(("heap_grower: kernel heap will run out of memory soon, allocating new one\n"));
|
||||
void *heapAddress = NULL;
|
||||
area_id heapArea = create_area("additional heap", &heapAddress,
|
||||
B_ANY_KERNEL_BLOCK_ADDRESS, HEAP_GROW_SIZE, B_FULL_LOCK,
|
||||
B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA);
|
||||
if (heapArea < B_OK) {
|
||||
panic("heap_grower: couldn't allocate additional heap area\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
heap_allocator *newHeap = heap_attach((addr_t)heapAddress,
|
||||
HEAP_GROW_SIZE, true);
|
||||
if (newHeap == NULL) {
|
||||
panic("heap_grower: could not attach additional heap!\n");
|
||||
delete_area(heapArea);
|
||||
continue;
|
||||
}
|
||||
|
||||
#if PARANOID_VALIDATION
|
||||
heap_validate_heap(newHeap);
|
||||
#endif
|
||||
heap->next = newHeap;
|
||||
TRACE(("heap_grower: new heap linked in\n"));
|
||||
|
||||
// notify anyone waiting for this request
|
||||
release_sem_etc(sHeapGrownNotify, -1, B_RELEASE_ALL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
heap_init(addr_t base, size_t size)
|
||||
{
|
||||
sHeapList = heap_attach(base, size, false);
|
||||
|
||||
// set up some debug commands
|
||||
add_debugger_command("heap", &dump_heap_list, "dump stats about the kernel heap(s)");
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
heap_init_post_sem()
|
||||
{
|
||||
// create the lock for the initial heap
|
||||
if (mutex_init(&sHeapList->lock, "heap_mutex") < B_OK) {
|
||||
panic("heap_init_post_sem(): error creating heap mutex\n");
|
||||
return B_ERROR;
|
||||
}
|
||||
|
||||
sHeapGrowSem = create_sem(0, "heap_grow_sem");
|
||||
if (sHeapGrowSem < 0) {
|
||||
panic("heap_init_post_sem(): failed to create heap grow sem\n");
|
||||
return B_ERROR;
|
||||
}
|
||||
|
||||
sHeapGrownNotify = create_sem(0, "heap_grown_notify");
|
||||
if (sHeapGrownNotify < 0) {
|
||||
panic("heap_init_post_sem(): failed to create heap grown notify sem\n");
|
||||
return B_ERROR;
|
||||
}
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
heap_init_post_thread()
|
||||
{
|
||||
thread_id thread = spawn_kernel_thread(heap_grow_thread, "heap grower",
|
||||
B_URGENT_PRIORITY, NULL);
|
||||
if (thread < 0) {
|
||||
panic("heap_init_post_thread(): cannot create heap grow thread\n");
|
||||
return B_ERROR;
|
||||
}
|
||||
|
||||
send_signal_etc(thread, SIGCONT, B_DO_NOT_RESCHEDULE);
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark -
|
||||
|
||||
|
||||
void *
|
||||
memalign(size_t alignment, size_t size)
|
||||
{
|
||||
if (!kernel_startup && !are_interrupts_enabled()) {
|
||||
panic("memalign(): called with interrupts disabled\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (size > (HEAP_GROW_SIZE * 3) / 4) {
|
||||
// don't even attempt such a huge allocation
|
||||
panic("heap: huge allocation of %lu bytes asked!\n", size);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
heap_allocator *heap = sHeapList;
|
||||
while (heap) {
|
||||
bool shouldGrow = false;
|
||||
void *result = heap_memalign(heap, alignment, size, &shouldGrow);
|
||||
if (heap->next == NULL && (shouldGrow || result == NULL)) {
|
||||
// the last heap will or has run out of memory, notify the grower
|
||||
sLastGrowRequest = heap;
|
||||
if (result == NULL) {
|
||||
// urgent request, do the request and wait for at max 250ms
|
||||
release_sem(sHeapGrowSem);
|
||||
acquire_sem_etc(sHeapGrownNotify, 1, B_RELATIVE_TIMEOUT, 250000);
|
||||
} else {
|
||||
// not so urgent, just notify the grower
|
||||
release_sem_etc(sHeapGrowSem, 1, B_DO_NOT_RESCHEDULE);
|
||||
}
|
||||
}
|
||||
|
||||
if (result == NULL) {
|
||||
heap = heap->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
#if PARANOID_VALIDATION
|
||||
heap_validate_heap(heap);
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
panic("heap: kernel heap has run out of memory\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void *
|
||||
malloc(size_t size)
|
||||
{
|
||||
return memalign(0, size);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
free(void *address)
|
||||
{
|
||||
if (!kernel_startup && !are_interrupts_enabled()) {
|
||||
panic("free(): called with interrupts disabled\n");
|
||||
return;
|
||||
}
|
||||
|
||||
heap_allocator *heap = sHeapList;
|
||||
while (heap) {
|
||||
if (heap_free(heap, address) == B_OK) {
|
||||
#if PARANOID_VALIDATION
|
||||
heap_validate_heap(heap);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
heap = heap->next;
|
||||
}
|
||||
|
||||
panic("free(): free failed for address %p\n", address);
|
||||
}
|
||||
|
||||
|
||||
void *
|
||||
realloc(void *address, size_t newSize)
|
||||
{
|
||||
if (!kernel_startup && !are_interrupts_enabled()) {
|
||||
panic("realloc(): called with interrupts disabled\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (address == NULL)
|
||||
return malloc(newSize);
|
||||
|
||||
if (newSize == 0) {
|
||||
free(address);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
heap_allocator *heap = sHeapList;
|
||||
while (heap) {
|
||||
void *newAddress = NULL;
|
||||
if (heap_realloc(heap, address, &newAddress, newSize) == B_OK) {
|
||||
#if PARANOID_VALIDATION
|
||||
heap_validate_heap(heap);
|
||||
#endif
|
||||
return newAddress;
|
||||
}
|
||||
|
||||
heap = heap->next;
|
||||
}
|
||||
|
||||
panic("realloc(): failed to realloc address %p to size %lu\n", address, newSize);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void *
|
||||
calloc(size_t numElements, size_t size)
|
||||
{
|
||||
void *address = memalign(0, numElements * size);
|
||||
if (address != NULL)
|
||||
memset(address, 0, numElements * size);
|
||||
|
||||
return address;
|
||||
}
|
||||
@@ -25,7 +25,7 @@
|
||||
#include <vm_cache.h>
|
||||
#include <vm_low_memory.h>
|
||||
#include <file_cache.h>
|
||||
#include <memheap.h>
|
||||
#include <heap.h>
|
||||
#include <condition_variable.h>
|
||||
#include <debug.h>
|
||||
#include <console.h>
|
||||
@@ -3480,16 +3480,8 @@ vm_init(kernel_args *args)
|
||||
vm_page_init_num_pages(args);
|
||||
sAvailableMemory = vm_page_num_pages() * B_PAGE_SIZE;
|
||||
|
||||
// reduce the heap size if we have not so much RAM
|
||||
size_t heapSize = HEAP_SIZE;
|
||||
if (sAvailableMemory < 100 * 1024 * 1024)
|
||||
heapSize /= 4;
|
||||
else if (sAvailableMemory < 200 * 1024 * 1024)
|
||||
heapSize /= 2;
|
||||
else if (sAvailableMemory >= 1024 * 1024 * 1024)
|
||||
heapSize *= 2;
|
||||
|
||||
// map in the new heap and initialize it
|
||||
size_t heapSize = INITIAL_HEAP_SIZE;
|
||||
addr_t heapBase = vm_allocate_early(args, heapSize, heapSize,
|
||||
B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA);
|
||||
TRACE(("heap at 0x%lx\n", heapBase));
|
||||
@@ -3607,8 +3599,7 @@ vm_init_post_sem(kernel_args *args)
|
||||
mutex_init(&sMappingLock, "page mappings");
|
||||
|
||||
slab_init_post_sem();
|
||||
|
||||
return heap_init_post_sem(args);
|
||||
return heap_init_post_sem();
|
||||
}
|
||||
|
||||
|
||||
@@ -3618,8 +3609,7 @@ vm_init_post_thread(kernel_args *args)
|
||||
vm_page_init_post_thread(args);
|
||||
vm_daemon_init();
|
||||
vm_low_memory_init_post_thread();
|
||||
|
||||
return heap_init_post_thread(args);
|
||||
return heap_init_post_thread();
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user