diff options
Diffstat (limited to 'common/vmem.c')
| -rw-r--r-- | common/vmem.c | 100 |
1 files changed, 80 insertions, 20 deletions
diff --git a/common/vmem.c b/common/vmem.c index 3795d0b..70ef24e 100644 --- a/common/vmem.c +++ b/common/vmem.c @@ -177,6 +177,32 @@ static size_t po_align(size_t s) return 0; } +static struct sp_mem *sp_find_used_closest(struct sp_reg_root *r, vm_t start) +{ + struct sp_mem *closest = 0; + size_t md = (size_t)(-1); + struct sp_node *n = sp_root(r->used_regions); + while(n){ + struct sp_mem *t = mem_container(n); + size_t d = ABS((ssize_t)start - (ssize_t)t->start); + + if(d == 0) /* exact match */ + return t; + + if(d < md){ /* closest so far */ + closest = t; + md = d; + } + + if(start < t->start) + n = sp_left(n); + else + n = sp_right(n); + } + + return closest; +} + /* should probably document this a bit better but in short, look for the "best" * free block, meaning one that is hopefully aligned so as to allow us to later * map it to higher order pages. If no block is found such that that is @@ -211,27 +237,14 @@ static struct sp_mem *sp_find_free_best(struct sp_reg_root *r, size_t size, size return quick_best; } -/* apparently Linux doesn't necessarily give a shit about mmap hints, so I'll - * just ignore them for now. Note that alloc_region should only be used when - * mmap is called with MAP_ANON, all other situations should be handled in some - * fs server */ -vm_t alloc_region(struct sp_reg_root *r, - size_t size, size_t *actual_size) +static size_t sp_use_region(struct sp_reg_root *r, struct sp_mem *m, + size_t pages, size_t align) { - *actual_size = align_up(size, BASE_PAGE_SIZE); - size_t pages = __page(*actual_size); - - /* find best fitting, alignment etc. */ - size_t align = 0; - struct sp_mem *m = sp_find_free_best(r, pages, &align); - if(!m) - return 0; - sp_remove(&sp_root(r->free_regions), &m->sp_n); vm_t pre_start = m->start; vm_t pre_end = pre_start + align; - + vm_t start = pre_end; vm_t end = start + pages; @@ -243,8 +256,6 @@ vm_t alloc_region(struct sp_reg_root *r, m->prev = n; if(n->prev) n->prev->next = n; - - sp_free_insert_region(r, n); } if(post_start != post_end){ @@ -252,8 +263,6 @@ vm_t alloc_region(struct sp_reg_root *r, m->next = n; if(n->next) n->next->prev = n; - - sp_free_insert_region(r, n); } m->end = end; @@ -263,6 +272,57 @@ vm_t alloc_region(struct sp_reg_root *r, return __addr(start); } +/* apparently Linux doesn't necessarily give a shit about mmap hints, so I'll + * just ignore them for now. Note that alloc_region should only be used when + * mmap is called with MAP_ANON, all other situations should be handled in some + * fs server */ +vm_t alloc_region(struct sp_reg_root *r, + size_t size, size_t *actual_size) +{ + *actual_size = align_up(size, BASE_PAGE_SIZE); + size_t pages = __page(*actual_size); + + /* find best fitting, alignment etc. */ + size_t align = 0; + struct sp_mem *m = sp_find_free_best(r, pages, &align); + if(!m) + return 0; + + return sp_use_region(r, m, pages, align); +} + + +vm_t alloc_fixed_region(struct sp_reg_root *r, + vm_t start, size_t size, size_t *actual_size) +{ + *actual_size = align_up(size, BASE_PAGE_SIZE); + size_t pages = __page(*actual_size); + start = __page(start); + + struct sp_mem *m = sp_find_used_closest(r, start); + if(!m) + return 0; + + /* locate actual region where start is between the region start and end */ + while(!((m->start <= start) && (start <= m->end))){ + if(start < m->start) + m = m->next; + else + m = m->prev; + } + + /* if region is already in use, forget it */ + if(is_region_used(m->flags)) + return 0; + + /* region is too small */ + if(start + pages > m->end) + return 0; + + /* actually start marking region used */ + return sp_use_region(r, m, pages, start - m->start); +} + static void __sp_try_coalesce_prev(struct sp_reg_root *r, struct sp_mem *m) { while(m){ |
