[自制操作系统] 连续页分配释放&kmalloc/kfree

本文将在JOS上实现连续内存、释放,提供内核的kmallockfree,并在分配frambuffer的时候进行测试。
Github : https://github.com/He11oLiu/MOS

lab2中实现的内存管理只是针对单页建立freelistlist中用链表连接起来的都是代表单页的结构体struct PageInfo。且每次释放页,都是丢在这个free_list的头。这样有几个问题:

  • 不能分配大于4k的连续空间(后面做frambuf的时候要用到)
  • 不断地加到空闲列表的头会使内存空间十分的混乱。不利于内存管理。

所以先要设计一种能够支持分配连续空间的机制。

一种简单的实现

最简单的想法就是保持现有的不动,freelist保证从高地址到低地址。

这要求在page_free的时候做一下手脚,放到合适的位置。

npages_alloc的时候,找到连续的空闲的页即可。

Free

既然最主要的是在free的时候需要维护freelist按照地址的大小排列,那么就先简单将page_free重新写一下,找到合适的位置再进行插入操作。

特别要注意是否刚好应该插入到free list的头的情况:

如果刚好是最高的地址,那么就需要修改page_free_list

    if (page2pa(page_free_list) < page2pa(pp))
    {
        cur = page_free_list;
        page_free_list = pp;
        pp->pp_link = cur;
        return;
    }

否则需要遍历来查找位置插入

    cur = page_free_list;
    prev = page_free_list;
    while (page2pa(cur) > page2pa(pp))
    {
        prev = cur;
        if ((cur = cur->pp_link) == NULL)
            break;
    }
    prev->pp_link = pp;
    pp->pp_link = cur;

写完简单的free之后,我们可以确保freelist的顺序问题了。

npages_alloc

再来看主要的alloc,其核心思想则是检查是否刚好有连续的空间能够分配出去,这里用consecutive来记录累计连续的页数。

通过pageInfopages的数组的偏移即可知道其对应的地址,如果这个偏移是连续的,则代表着一块连续的空间:

(int)(cur - pages) == (int)(prev - pages) - 1

其中cur为当前遍历到的pageInfo,而prev是上次遍历的,通过上面的表达式可以判断是否为连续。

如果找到了合适的一块空间,则需要

  • 维护freelist,将这块空间前的最后一页连接到分配走的后面一页。

    同样注意是否有存在需要换头的情况

        if (pp == page_free_list)
            page_free_list = cur;
        else
            pp_prev->pp_link = cur;
  • 初始化页属性与空间

        if (alloc_flags & ALLOC_ZERO)
            memset(page2kva(prev), 0, n * PGSIZE);
        // clear pp link
        for (i = 0; i < n; i++)
            (prev + i)->pp_link = NULL;
        return prev;

    完整的npages_alloc见下:

struct PageInfo *npages_alloc(unsigned int n, int alloc_flags)
{
    struct PageInfo *cur;
    struct PageInfo *prev;
    struct PageInfo *pp;
    struct PageInfo *pp_prev;
    unsigned int i;
    unsigned int consecutive = 1;
    if (page_free_list == NULL)
        return NULL;
    pp = page_free_list;
    pp_prev = page_free_list;
    prev = page_free_list;
    cur = page_free_list->pp_link;

    while (consecutive < n && cur != NULL)
    {
        if ((int)(cur - pages) != (int)(prev - pages) - 1)
        {
            consecutive = 1;
            pp_prev = prev;
            pp = cur;
        }
        else
            consecutive++;
        prev = cur;
        cur = cur->pp_link;
    }
    if (consecutive == n)
    {
        // alloc flags
        if (alloc_flags & ALLOC_ZERO)
            memset(page2kva(prev), 0, n * PGSIZE);
        // update page_free_list
        if (pp == page_free_list)
            page_free_list = cur;
        else
            pp_prev->pp_link = cur;
        // clear pp link
        for (i = 0; i < n; i++)
            (prev + i)->pp_link = NULL;
        return prev;
    }
    return NULL;
}
kmalloc

实现了npages_alloc,再来实现malloc就简单了,主要两个问题

  • 需要分配多少页? 通过ROUNDUP后再除以页大小即可。
  • 其对应的虚拟地址是多少? 利用page2kva转换。

完整的kmalloc如下。

void *kmalloc(size_t size)
{
    struct PageInfo *pp;
    int npages;
    size = ROUNDUP(size, PGSIZE);
    npages = size / PGSIZE;
    if ((pp = npages_alloc(npages, 1)) == NULL)
        return NULL;
    return page2kva(pp);
}

此时已经可以测试是否基本正确。

npages_free

为了实现free,还需要实现npages_free,这个和之前实现的思路相同。

主要注意如何连接起freelist

    prev->pp_link = pp + n - 1;
    pp->pp_link = cur;
    for (i = 1; i < n; i++)
        (pp + i)->pp_link = pp + i - 1;

其中prev是合适位置的之前一个,cur是合适位置的下一个。

npages_free完整实现见下。

void npages_free(struct PageInfo *pp, unsigned int n)
{
    struct PageInfo *cur, *prev;
    unsigned int i;
    for (i = 0; i < n; i++)
    {
        if ((pp + i)->pp_ref)
            panic("npages_free error: (pp+%d)->pp_ref != 0", i);
        if ((pp + i)->pp_link != NULL)
            panic("npages_free error: (pp+%d)->pp_link != NULL", i);
    }
    if (page2pa(page_free_list) < page2pa(pp))
    {
        cur = page_free_list;
        page_free_list = pp + n - 1;
        pp->pp_link = cur;
        for (i = 1; i < n; i++)
            (pp + i)->pp_link = pp + i - 1;
        return;
    }
    cur = page_free_list;
    prev = page_free_list;
    while (page2pa(cur) > page2pa(pp))
    {
        prev = cur;
        if ((cur = cur->pp_link) == NULL)
            break;
    }
    // test use
    cprintf("find prev %d cur %d\n", (int)(prev - pages), (int)(cur - pages));

    prev->pp_link = pp + n - 1;
    pp->pp_link = cur;
    for (i = 1; i < n; i++)
        (pp + i)->pp_link = pp + i - 1;
    return;
}
kfree

kmalloc类似,算出大小释放即可

void kfree(void *kva, size_t size)
{
    struct PageInfo *pp = pa2page(PADDR(kva));
    int npages;
    size = ROUNDUP(size, PGSIZE);
    npages = size / PGSIZE;
    npages_free(pp, npages);
}
兼容单页分配

为了和已经写的兼容,直接调用npages_xx即可

struct PageInfo *page_alloc(int alloc_flags)
{
    return npages_alloc(1, alloc_flags);
}
void page_free(struct PageInfo *pp)
{
    npages_free(pp, 1);
}
测试

首先取消有关内存的几个check,有几个原因导致:

  • free后不是放在头的
  • check page中做了mmio映射,从而kern pgdir中对应的pd就是PTE_P

所以check除了第一个全部取消。

freelist free后顺序

首先是单页free后的顺序测试

    struct PageInfo *alloc1 = page_alloc(1);
    struct PageInfo *alloc2 = page_alloc(1);
    struct PageInfo *alloc3 = page_alloc(1);
    cprintf("alloc 1 at %d\n", (int)(alloc1 - pages));
    cprintf("alloc 2 at %d\n", (int)(alloc2 - pages));
    cprintf("alloc 3 at %d\n", (int)(alloc3 - pages));
    page_free(alloc1);
    page_free(alloc3);

刚才free中写了测试语句,输出如下:

alloc 1 at 1023
alloc 2 at 1022
alloc 3 at 1021
find prev 1023 cur 1020
malloc/free 测试

之前在写显存的双缓冲的时候,委曲求全选择了静态分配。这里重新使用动态分配并进行测试:

void init_framebuffer(){
    void *malloc_free_test;
    if((framebuffer = (uint8_t *) kmalloc((size_t)(graph.scrnx*graph.scrny)))== NULL)
        panic("kmalloc error!");
    malloc_free_test = framebuffer;
    kfree(framebuffer,(size_t)(graph.scrnx*graph.scrny));    
    if((framebuffer = (uint8_t *) kmalloc((size_t)(graph.scrnx*graph.scrny)))== NULL)
        panic("kmalloc error!");
    if(malloc_free_test == framebuffer)
        cprintf("kmalloc/kfree check success\n");
    else
        panic("kmalloc/kfree error!\n");

    // framebuffer = tmpbuf;
    if(framebuffer == NULL)
        panic("Not enough memory for framebuffer!");
}

测试输出正确。

更高效的空闲链表设计

上面说过,原来的空闲链表是连接的一个一个页的信息。但是由于JOS在设计的时候希望能够每个页有一个对应的页信息。并利用此来从pageinfo找到kva。所以设计的新的pageinfo结构体仍然是每个页拥有一个。

Free Area

新增一个概念:Free Area。所谓Free Area则是空闲页连接起来的一片区域,直到下一个被使用的页。

这将涉及到两个重要的信息:

  • FreeArea的第一个页是谁
  • FreeArea的大小是多少

以及分配,释放的策略的不同。这里使用First fit的策略来分配页。

新的PageInfo结构体

设计的新的PageInfo结构体:

#define FIRSTPAGE 0x1
struct PageInfo {
    // Old:Next page on the free list.
    // New:Fist page in next free area.
    struct PageInfo *pp_link;
    // some infomation about this page
    uint8_t flags;
    // size of this free area.
    uint32_t  freesize;
    // pp_ref is the count of pointers (usually in page table entries)
    // to this page, for pages allocated using page_alloc.
    // Pages allocated at boot time using pmap.c's
    // boot_alloc do not have valid reference count fields.
    uint16_t pp_ref;
};
具体分配策略与释放策略
npages_alloc

分配一个大小为n pages的页,要遍历free list

这里的free list保存的不再是一页页的链表,而是Free area的链表。

找到第一个freesize > n的区域,分配出去,并且设置后面的一页为新的Area头。

npages_free

释放一个大小为n pages的页,也要遍历free list,找到地址在其前的进的看能不能加入它的area,找到地址在其后的,看看能否加入。不能的话需要插入一个新的独立的area

类似的思路在ucore中写过了,在JOS中由于init的时候比较复杂,链表的链接问题比较严重,这里就不尝试了。关于ucore的实现可见我的CSDN 博客

转载于:https://www.cnblogs.com/he11o-liu/p/7503216.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值