From: Matthias Kruk Date: Mon, 2 Dec 2019 08:09:00 +0000 (+0900) Subject: Implement page-directory-level functions to handle heaps X-Git-Url: https://git.corax.cc/?a=commitdiff_plain;h=8e2a4ec206be7e2e57ae54907f8bb9f0c8352c70;p=corax Implement page-directory-level functions to handle heaps - Add pg_dir_sbrk() function to handle heap allocation and size adjustments - Add pg_dir_get_heap() function to locate the base of the heap - Add documentation for pg_dir_map() function --- diff --git a/kernel/arch/paging.c b/kernel/arch/paging.c index 6fb27b8..978cf54 100644 --- a/kernel/arch/paging.c +++ b/kernel/arch/paging.c @@ -62,6 +62,7 @@ static int _pg_dir_add_region(pg_dir_t*, void*, u32_t, u32_t, u32_t, u32_t); int _pg_dir_vpxlate(pg_dir_t*, u32_t, u32_t*); int _pg_dir_pagesize(pg_dir_t*, u32_t, u32_t*); int _pg_dir_xfer(pg_dir_t*, void*, pg_dir_t*, void*, u32_t); +static int _pg_dir_heap_map(pg_dir_t*, u32_t); static void* _phys_alloc(u32_t size, u32_t align) { @@ -769,6 +770,114 @@ cleanup: return(ret_val); } +static int _pg_dir_heap_map(pg_dir_t *pd, u32_t heap_size) +{ + /* + * The pg_dir_heap_map() function is used to add a heap segment + * to a page dir that does not already have one. + * + * It should work as follows: + * 1. Make sure the page dir doesn't already have a heap + * 2. Find the highest segment that is not a stack segment + * 3. Add the heap segment right after the segement located in 2 + */ + + u32_t heap_start; + int ret_val; + int i; + + /* FIXME: Make sure heap_size is aligned to the page directory's page size */ + /* FIXME: Set heap_size to the page directory's page size if set to zero */ + + if(!pd) { + ret_val = -EINVAL; + goto gtfo; + } + + /* FIXME: Lock the page directory */ + + for(i = 0, heap_start = 0, ret_val = 0; + i < CONFIG_PAGING_DIR_MAXREGIONS && ret_val > 0; + i++) { + struct region *reg; + u32_t limit; + + reg = pd->pd_regions[i]; + + switch(reg->reg_type) { + case REGION_HEAP: + /* the page directory apparently already contains a heap */ + ret_val = -EALREADY; + break; + + case REGION_STACK: + case REGION_KSTACK: + /* + * Ignore stack segments in the calculation because they are supposed to + * be at the end of the address space, and not in front of the heap. + */ + break; + + case REGION_TEXT: + case REGION_BSS: + case REGION_DATA: + case REGION_RODATA: + /* + * Calculate the address of the first byte after this region. It *should* + * already be aligned, but nevertheless align it again. 念の為. + */ + limit = ALIGN((u32_t)reg->reg_base + reg->reg_size, + reg->reg_pgsize); + + if(limit > heap_start) { + heap_start = limit; + } + break; + + default: + /* this should absolutely not have happened */ + ret_val = -EBADFD; + break; + } + } + + if(!ret_val) { + /* we found a place to allocate the heap segment */ + + if(heap_start) { + u32_t attrs; + + /* the heap shouldn't be executable, right? */ + attrs = PAGE_ATTR_PRESENT | PAGE_ATTR_WRITABLE | + PAGE_ATTR_USER | PAGE_ATTR_NO_EXEC; + + /* allocate page frames from wherever */ + ret_val = pg_dir_map(pd, NULL, (void*)heap_start, + heap_size, attrs); + + if(!ret_val) { + /* add a struct region so the memory is accounted for */ + ret_val = _pg_dir_add_region(pd, (void*)heap_start, heap_size, + REGION_HEAP, attrs, REGION_PRIV); + + if(ret_val < 0) { + /* oops - ran out of kernel heap space */ + pg_dir_unmap(pd, (void*)heap_start, heap_size); + } + } else { + /* Failed to map the heap. Wat do? */ + ret_val = -ENOMEM; + } + } else { + /* the page directory doesn't appear to contain any regions? */ + ret_val = -EBADFD; + } + } + +gtfo: + return(ret_val); +} + int _clone_kernel_region(pg_dir_t *kdir, region_t *reg, void *data) { pg_dir_t *dir; @@ -1306,3 +1415,97 @@ void* pg_dir_get_pdbr(pg_dir_t *pd) { return(pd->pd_base); } + +void* pg_dir_get_heap(pg_dir_t *pd) +{ + void *ret_val; + int i; + + for(ret_val = NULL, i = 0; i < CONFIG_PAGING_DIR_MAXREGIONS; i++) { + if(pd->pd_regions[i]->reg_type == REGION_HEAP) { + ret_val = pd->pd_regions[i]->reg_base; + break; + } + } + + return(ret_val); +} + +void* pg_dir_sbrk(pg_dir_t *pd, i32_t increment) +{ + struct region *heap; + void *ret_val; + int i; + + /* special case where the heap is not touched */ + if(!increment) { + for(i = 0, ret_val = NULL; i < CONFIG_PAGING_DIR_MAXREGIONS; i++) { + if(pd->pd_regions[i]->reg_type == REGION_HEAP) { + ret_val = pd->pd_regions[i]->reg_base + + pd->pd_regions[i]->reg_size; + break; + } + } + + return(ret_val); + } + + for(heap = NULL, i = 0; i < CONFIG_PAGING_DIR_MAXREGIONS; i++) { + if(pd->pd_regions[i]->reg_type == REGION_HEAP) { + heap = pd->pd_regions[i]; + break; + } + } + + if(!heap) { + u32_t heap_size; + int err; + + heap_size = ALIGN(increment, PAGE_SIZE); + + err = _pg_dir_heap_map(pd, heap_size); + + if(!err) { + /* the new heap has been created with the correct size */ + return(pg_dir_sbrk(pd, 0)); + } else { + /* could not map a heap - how do we get the error to the caller? */ + return(NULL); + } + } else { + u32_t nbytes; + int err; + + if(increment > 0) { + /* round up to the page size */ + nbytes = ALIGN(increment, heap->reg_pgsize); + + err = pg_dir_map(pd, 0, heap->reg_base + heap->reg_size, nbytes, heap->reg_attrs); + + if(!err) { + /* successfully mapped pages - keep track of the increased heap size */ + heap->reg_size += nbytes; + } + } else { /* increment < 0 */ + /* request to reduce the heap size, round down */ + nbytes = (u32_t)-increment & ~(heap->reg_pgsize - 1); + + /* value may be zero, e.g. if the caller wants to free less than a page */ + if(nbytes) { + /* remove nbytes of space at the end of the region */ + err = pg_dir_unmap(pd, heap->reg_base + heap->reg_size - nbytes, nbytes); + + if(!err) { + heap->reg_size -= nbytes; + } + } + } + + /* FIXME: We need to somehow set errno if the mapping failed */ + } + + /* either way return the new/current sbrk value */ + ret_val = heap->reg_base + heap->reg_size; + + return(ret_val); +} diff --git a/kernel/include/arch.h b/kernel/include/arch.h index 9ae6dfa..f275d4f 100644 --- a/kernel/include/arch.h +++ b/kernel/include/arch.h @@ -144,15 +144,45 @@ typedef struct pagedir pg_dir_t; int pg_dir_create(pg_dir_t**); +/* + * pg_dir_map() - Map page frames into a page directory + * + * SYNOPSIS + * int pg_dir_map(pg_dir_t* pd, const void *paddr, const void *vaddr, + * const u32_t bytes, const u32_t attrs); + * + * DESCRIPTION + * The pg_dir_map() function attempts to map page frames (physical pages) into + * the page directory pointed to by `pd'. It will attempt to allocate the page + * frames pointed to by `paddr', or if `paddr' is NULL it will use whatever is + * available. The virtual address where the frames will be mapped has to be + * provided by the caller in `vaddr'. Unlike `paddr', `vaddr' may not be NULL. + * The number of pages that will be mapped depends on the page directory. If + * PAE-mode paging is employed, pg_dir_map() will attempt to map pages of the + * largest-possible size, meaning that it will attempt to map 4M pages if the + * alignment of the addresses and the page directory's page size permitts. + * Any pages that have been mapped into the page directory will have their + * page attributes set according to the value passed in `attrs'. + * The PAGE_ATTR_PRESENT will be set regardless of the value in `attrs'. + * + * RETURN VALUE + * 0 The frames have been successfully mapped + * -EALREADY The virtual addresses are already mapped + * -ENOMEM Not enough unused page frames to be mapped + * -ENOSYS The function is not implemented for this paging mode + * -EFAULT Could not determine the paging mode used by the page directory + */ int pg_dir_map(pg_dir_t*, const void*, const void*, const u32_t, const u32_t); int pg_dir_map_region(pg_dir_t*, pg_dir_t*, region_t*); int pg_dir_clone_region(pg_dir_t*, pg_dir_t*, region_t*); int pg_dir_unmap(pg_dir_t*, const void*, const u32_t); +void* pg_dir_get_heap(pg_dir_t*); void* pg_dir_get_pdbr(pg_dir_t*); int _pg_dir_vpxlate(pg_dir_t*, u32_t, u32_t*); void* pg_dir_get_kstack(pg_dir_t*); void* pg_dir_get_ustack(pg_dir_t*); +void* pg_dir_sbrk(pg_dir_t*, i32_t); int pg_dir_foreach_region(pg_dir_t*, int(*)(pg_dir_t*, struct region*, void*), void*); int pg_dir_memcpy(pg_dir_t*, void*, pg_dir_t*, void*, u32_t);