[自制操做系統] 連續頁分配釋放&kmalloc/kfree

本文將在JOS上實現連續內存、釋放,提供內核的kmallockfree,並在分配frambuffer的時候進行測試。
Github : https://github.com/He11oLiu/MOSgit

lab2中實現的內存管理只是針對單頁創建freelistlist中用鏈表鏈接起來的都是表明單頁的結構體struct PageInfo。且每次釋放頁,都是丟在這個free_list的頭。這樣有幾個問題:github

  • 不能分配大於4k的連續空間(後面作frambuf的時候要用到)
  • 不斷地加到空閒列表的頭會使內存空間十分的混亂。不利於內存管理。

因此先要設計一種可以支持分配連續空間的機制。數組

一種簡單的實現

最簡單的想法就是保持現有的不動,freelist保證從高地址到低地址。markdown

這要求在page_free的時候作一下手腳,放到合適的位置。測試

npages_alloc的時候,找到連續的空閒的頁便可。ui

Free

既然最主要的是在free的時候須要維護freelist按照地址的大小排列,那麼就先簡單將page_free從新寫一下,找到合適的位置再進行插入操做。 this

特別要注意是否恰好應該插入到free list的頭的狀況:spa

若是恰好是最高的地址,那麼就須要修改page_free_list.net

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 博客

相關文章
相關標籤/搜索