You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
506 lines
12 KiB
506 lines
12 KiB
#include <stdint.h>
|
|
#include <stddef.h>
|
|
|
|
#include "heap.h"
|
|
#include "../lib/sprintf.h"
|
|
#include "../memory/vmap.h"
|
|
#include "../memory/paging.h"
|
|
#include "../lib/assert.h"
|
|
#include "../lib/string.h"
|
|
#include "../lib/panic.h"
|
|
#include "../lib/logging.h"
|
|
|
|
|
|
//#define DEBUG_HEAP
|
|
|
|
#ifdef DEBUG_HEAP
|
|
#define log_heap(...) log_warn(__VA_ARGS__)
|
|
#else
|
|
#define log_heap(...)
|
|
#endif
|
|
|
|
|
|
|
|
#define MIN_EXPAND_SIZE 1024
|
|
#define MIN_SEGMENT_SIZE 32
|
|
|
|
#define MAX(X,Y) (X >= Y ? X : Y)
|
|
|
|
typedef struct seg_header {
|
|
struct seg_header *next;
|
|
uint32_t size;
|
|
|
|
// boolean variable
|
|
uint32_t free;
|
|
} seg_header;
|
|
|
|
|
|
// assert that the header is 8-byte alligned
|
|
// this is important so that every allocation
|
|
// is 8-byte alligned too
|
|
static_assert(sizeof(seg_header) % 8 == 0);
|
|
|
|
// segment headers represent a linked list
|
|
// going downward in address space
|
|
|
|
/**
|
|
* 0 | node0
|
|
* | ////
|
|
* | ////
|
|
* | ////
|
|
* | node1
|
|
* | ////
|
|
* | ////
|
|
* | ////
|
|
* ...
|
|
* | nodeN
|
|
* | free
|
|
* | free
|
|
* | free
|
|
* BRK ----
|
|
*
|
|
* node n -> node n-1
|
|
*
|
|
*
|
|
* split:
|
|
* node n+1 -> node n
|
|
*
|
|
* node n+1 -> new_node
|
|
* new_node -> node n
|
|
* new_node.size = node n.size - SIZE - sizeof(node)
|
|
*
|
|
*/
|
|
|
|
static void *heap_begin = (void *)KERNEL_HEAP_BEGIN;
|
|
static size_t heap_size = 0;
|
|
static size_t n_allocations = 0;
|
|
// sum of the available heap ranges,
|
|
// without the last free segment:
|
|
// indice of how fragmented
|
|
// the heap is
|
|
// TODO: use this to unfragment the whole
|
|
// heap when this number is too big
|
|
|
|
//static size_t fragmented_available_size = 0;
|
|
|
|
static seg_header* current_segment = NULL;
|
|
|
|
|
|
#ifndef NDEBUG
|
|
static void heap_assert_seg(const seg_header* const seg) {
|
|
assert(seg);
|
|
if((uint64_t)seg < KERNEL_HEAP_BEGIN ||
|
|
(uint64_t)seg >= KERNEL_HEAP_BEGIN+heap_size) {
|
|
log_warn("heap_assert_seg error: seg=%lx", seg);
|
|
panic("heap_assert_seg(...): arg is not in heap");
|
|
}
|
|
|
|
if((uint64_t)seg+seg->size > KERNEL_HEAP_BEGIN+heap_size) {
|
|
log_warn("heap_assert_seg error: seg=%lx = {.next=%lx, .size=%lx, .free=%lx}",
|
|
seg, seg->next, seg->size, seg->free);
|
|
|
|
panic("heap_assert_seg(...): arg is not in heap");
|
|
}
|
|
if((seg->free & ~1u) != 0) {
|
|
log_warn("heap_assert_seg error: seg=%lx = {.next=%lx, .size=%lx, .free=%lx}",
|
|
seg, seg->next, seg->size, seg->free);
|
|
panic("heap_assert_seg(...): bad header (invalid free value)");
|
|
}
|
|
|
|
if(seg->next != NULL && ((uint64_t)seg->next < KERNEL_HEAP_BEGIN
|
|
|| (uint64_t)seg->next >= KERNEL_HEAP_BEGIN+heap_size)) {
|
|
log_warn("heap_assert_seg error: seg=%lx = {.next=%lx, .size=%lx, .free=%lx}",
|
|
seg, seg->next, seg->size, seg->free);
|
|
panic("heap_assert_seg(...): bad header (invalid next value)");
|
|
}
|
|
}
|
|
|
|
#else
|
|
#define heap_assert_seg(X)
|
|
#endif
|
|
|
|
|
|
size_t heap_get_n_allocation(void) {
|
|
return n_allocations;
|
|
}
|
|
|
|
/**
|
|
* expand the heap by size bytes
|
|
*/
|
|
static void expand_heap(size_t size) {
|
|
size_t new_heap_pages_size = (heap_size + size + 0xfff) >> 12;
|
|
size_t old_heap_pages_size = (heap_size + 0xfff) >> 12;
|
|
|
|
|
|
// alloc extra pages if needed
|
|
if(new_heap_pages_size != old_heap_pages_size) {
|
|
alloc_pages(
|
|
heap_begin + (old_heap_pages_size << 12),
|
|
new_heap_pages_size - old_heap_pages_size,
|
|
PRESENT_ENTRY | PL_XD | PL_RW // execute disable pages
|
|
);
|
|
}
|
|
|
|
// create a new segment in the extra space
|
|
seg_header* new_segment = heap_begin + heap_size;
|
|
|
|
|
|
new_segment->next = current_segment;
|
|
new_segment->size = size - sizeof(seg_header);
|
|
new_segment->free = 1;
|
|
|
|
current_segment = new_segment;
|
|
|
|
|
|
heap_size += size;
|
|
|
|
log_heap("kernel heap extended to %lu KB", heap_size / 1024);
|
|
}
|
|
|
|
|
|
size_t heap_get_size(void) {
|
|
return heap_size;
|
|
}
|
|
|
|
|
|
void* heap_get_brk(void) {
|
|
return heap_begin + heap_size;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* current_segment shouldn't be NULL
|
|
*
|
|
*/
|
|
static void defragment(void) {
|
|
assert(current_segment != NULL);
|
|
|
|
seg_header* pred2 = NULL;
|
|
seg_header* pred = current_segment;
|
|
|
|
for(seg_header* seg = current_segment->next;
|
|
seg != NULL;
|
|
) {
|
|
heap_assert_seg(seg);
|
|
/**
|
|
* || seg || pred |
|
|
* ||
|
|
* \/
|
|
* || SEG |
|
|
*
|
|
*
|
|
*/
|
|
// we can merge these two nodes
|
|
if(seg->free && pred->free) {
|
|
if(!pred2)
|
|
// pred is the head
|
|
current_segment = seg;
|
|
else
|
|
pred2->next = seg;
|
|
|
|
seg->size += sizeof(seg_header) + pred->size;
|
|
|
|
// pred2 doesn't change!
|
|
pred = seg;
|
|
seg = seg->next;
|
|
continue;
|
|
}
|
|
else {
|
|
pred2 = pred;
|
|
pred = seg;
|
|
seg = seg->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
// return the node preceding the argument
|
|
// in the linked list
|
|
// O(n) sequential research
|
|
static seg_header* find_pred(seg_header* node) {
|
|
|
|
for(seg_header* seg = current_segment;
|
|
seg != NULL;
|
|
seg = seg->next) {
|
|
if(seg->next == node)
|
|
return node;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// try to merge the node with the next one
|
|
// we suppose that the argument is free
|
|
static inline void try_merge(seg_header* free_node) {
|
|
assert(free_node->free);
|
|
|
|
seg_header* next = free_node->next;
|
|
|
|
if(!next)
|
|
return;
|
|
|
|
/*
|
|
* insert a new segment between to_split and pred
|
|
* return the new segment
|
|
*
|
|
* | next | free_node |
|
|
* | F R E E | F R E E |
|
|
* | size1 | size0 |
|
|
*
|
|
* ||
|
|
* \/
|
|
*
|
|
* | next |
|
|
* | F R E E |
|
|
* | size0 + size1 |
|
|
*
|
|
**/
|
|
// we can merge!
|
|
if(next->free) {
|
|
next->size += free_node->size;
|
|
|
|
// if the argument is the head
|
|
// of the list, the head should
|
|
// become the
|
|
if(free_node == current_segment) {
|
|
current_segment = next;
|
|
return;
|
|
}
|
|
|
|
// if not, we must find the preceding
|
|
// node in the linked list
|
|
seg_header* seg = find_pred(free_node);
|
|
|
|
// now make it point on the
|
|
// new merged node which is
|
|
// the 'next' node
|
|
seg->next = next;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* insert a new segment between to_split and pred
|
|
* return the new segment
|
|
* | to_split |
|
|
* | F R E E |
|
|
* | size0 |
|
|
* ||
|
|
* \/
|
|
*
|
|
* | to_split | new |
|
|
* | F R E E | F R E E |
|
|
* | size |size0-size|
|
|
**/
|
|
static seg_header* split_segment(seg_header* pred, seg_header* tosplit, size_t size) {
|
|
seg_header* new_segment = (void*)tosplit + sizeof(seg_header) + size;
|
|
|
|
if(pred != NULL)
|
|
pred->next = new_segment;
|
|
|
|
new_segment->next = tosplit;
|
|
new_segment->free = 1;
|
|
new_segment->size = tosplit->size // size to split
|
|
- size // size reserved
|
|
- sizeof(seg_header); // new segment header
|
|
|
|
|
|
tosplit->size = size;
|
|
|
|
return new_segment;
|
|
}
|
|
|
|
|
|
void heap_init(void) {
|
|
log_heap("init kernel heap...");
|
|
|
|
|
|
expand_heap(MIN_EXPAND_SIZE);
|
|
}
|
|
|
|
|
|
void* __attribute__((noinline)) malloc(size_t size) {
|
|
log_heap("malloc(%u)", size);
|
|
//assert(current_segment->free == 1);
|
|
|
|
// align the size to assure that
|
|
// the whole structure is alligned
|
|
size = ((size + 7 ) / 8) * 8;
|
|
if(size < MIN_SEGMENT_SIZE)
|
|
size = MIN_SEGMENT_SIZE;
|
|
|
|
// search for a big enough pool
|
|
seg_header* seg = current_segment;
|
|
seg_header* pred = NULL;
|
|
|
|
|
|
while(1) {
|
|
if(seg == NULL) {
|
|
break;
|
|
}
|
|
//log_heap("%lx -> %lx", seg, seg->next);
|
|
|
|
heap_assert_seg(seg);
|
|
|
|
if(!seg->free || seg->size < size) {
|
|
// this segment is not right, check the next one
|
|
pred = seg;
|
|
|
|
// keep the preceding node in memory
|
|
// we need it when spliting the current
|
|
// segment
|
|
seg = seg->next;
|
|
|
|
continue;
|
|
}
|
|
|
|
// we found a satisfying segment!
|
|
if(seg->size >= size + sizeof(seg_header) + MIN_SEGMENT_SIZE) {
|
|
// we don't take the whole space
|
|
|
|
// get the inserted segment
|
|
seg_header* new_seg = split_segment(pred, seg, size);
|
|
|
|
if(pred == NULL) {
|
|
// seg == current_segment
|
|
|
|
// the 'current' segment should
|
|
// be the last in the list.
|
|
// => advance the current one
|
|
current_segment = new_seg;
|
|
}
|
|
else {
|
|
// put the new node in the linked list
|
|
pred->next = new_seg;
|
|
}
|
|
|
|
// mark seg as allocated,
|
|
// leaving the new node free
|
|
}
|
|
// else, the segment is not big enough to
|
|
// be splitted, we mark it allocated as is,
|
|
// wasting a bit of memory
|
|
|
|
seg->free = 0;
|
|
|
|
// one allocation
|
|
n_allocations++;
|
|
|
|
log_heap(" --> %lx", (void*)seg+sizeof(seg_header));
|
|
|
|
return (void *)seg + sizeof(seg_header);
|
|
}
|
|
|
|
// no available segment in the heap
|
|
// let's expand
|
|
|
|
expand_heap(MAX(size+sizeof(seg_header), MIN_EXPAND_SIZE));
|
|
// retrty now that we are sure that the memory is avaiable
|
|
return malloc(size);
|
|
}
|
|
|
|
|
|
// O(n) in case of freeing (size < oldsize)
|
|
void* realloc(void* ptr, size_t size) {
|
|
if(ptr == NULL)
|
|
return malloc(size);
|
|
if(size == 0) {
|
|
free(ptr);
|
|
return NULL;
|
|
}
|
|
|
|
seg_header* header = ptr - sizeof(seg_header);
|
|
|
|
uint32_t header_size = header->size;
|
|
|
|
if(size < header_size) {
|
|
// its not worth reallocating
|
|
if(size > header_size / 2
|
|
|| header_size-size < MIN_SEGMENT_SIZE * 2)
|
|
return ptr;
|
|
|
|
}
|
|
|
|
unsigned cpsize = header_size;
|
|
if(cpsize > size)
|
|
cpsize = size;
|
|
|
|
void* new_ptr = malloc(size);
|
|
memcpy(new_ptr, ptr, cpsize);
|
|
|
|
free(ptr);
|
|
// malloc increments the number of allocations
|
|
// but we just reallocated
|
|
// n_allocations--;
|
|
|
|
return new_ptr;
|
|
}
|
|
|
|
|
|
// O(1) free
|
|
void __attribute__((noinline)) free(void *ptr) {
|
|
log_heap("free(%lx)", ptr);
|
|
|
|
seg_header* header = ptr - sizeof(seg_header);
|
|
|
|
heap_assert_seg(header);
|
|
|
|
assert(header->free == 0);
|
|
|
|
|
|
header->free = 1;
|
|
|
|
static int i = 0;
|
|
|
|
// defragment the heap every N frees
|
|
if((i++ % 32) == 0)
|
|
defragment();
|
|
|
|
n_allocations--;
|
|
}
|
|
|
|
|
|
void heap_defragment(void) {
|
|
defragment();
|
|
}
|
|
|
|
|
|
#ifndef NDEBUG
|
|
//#ifdef DEBUG_HEAP
|
|
|
|
void heap_print(void) {
|
|
for(seg_header* seg = current_segment;
|
|
seg != NULL;
|
|
seg = seg->next) {
|
|
log_debug("%lx size=%x,free=%u", seg,seg->size, seg->free);
|
|
}
|
|
}
|
|
|
|
|
|
void malloc_test(void) {
|
|
|
|
void* arr[128] = {0};
|
|
|
|
uint64_t size = 5;
|
|
|
|
for(int k = 0; k < 3; k++) {
|
|
for(int j = 0; j < 10; j++) {
|
|
for(int i = 0; i < 128; i++) {
|
|
arr[i] = realloc(arr[i], size % (1024*1024));
|
|
size = (16807 * size) % ((1lu << 31) - 1);
|
|
}
|
|
|
|
}
|
|
|
|
for(int i = 0; i < 128; i++) {
|
|
free(arr[i]);
|
|
arr[i] = 0;
|
|
}
|
|
}
|
|
|
|
defragment();
|
|
heap_print();
|
|
}
|
|
|
|
//#endif
|
|
#endif
|
|
|