本文將在JOS
上實現連續內存、釋放,提供內核的kmalloc
與kfree
,並在分配frambuffer
的時候進行測試。
Github : https://github.com/He11oLiu/MOSgit
在lab2
中實現的內存管理只是針對單頁創建freelist
,list
中用鏈表鏈接起來的都是表明單頁的結構體struct PageInfo
。且每次釋放頁,都是丟在這個free_list
的頭。這樣有幾個問題:github
4k
的連續空間(後面作frambuf
的時候要用到)因此先要設計一種可以支持分配連續空間的機制。數組
最簡單的想法就是保持現有的不動,freelist
保證從高地址到低地址。markdown
這要求在page_free
的時候作一下手腳,放到合適的位置。測試
在npages_alloc
的時候,找到連續的空閒的頁便可。ui
既然最主要的是在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
的順序問題了。
再來看主要的alloc
,其核心思想則是檢查是否恰好有連續的空間可以分配出去,這裏用consecutive
來記錄累計連續的頁數。
經過pageInfo
在pages
的數組的偏移便可知道其對應的地址,若是這個偏移是連續的,則表明着一塊連續的空間:
(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;
}
實現了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);
}
此時已經能夠測試是否基本正確。
爲了實現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
除了第一個所有取消。
首先是單頁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
以前在寫顯存的雙緩衝的時候,委曲求全選擇了靜態分配。這裏從新使用動態分配並進行測試:
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
則是空閒頁鏈接起來的一片區域,直到下一個被使用的頁。
這將涉及到兩個重要的信息:
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;
};
分配一個大小爲n pages
的頁,要遍歷free list
。
這裏的free list
保存的再也不是一頁頁的鏈表,而是Free area
的鏈表。
找到第一個freesize > n
的區域,分配出去,而且設置後面的一頁爲新的Area
頭。
釋放一個大小爲n pages
的頁,也要遍歷free list
,找到地址在其前的進的看能不能加入它的area
,找到地址在其後的,看看可否加入。不能的話須要插入一個新的獨立的area
。
相似的思路在ucore
中寫過了,在JOS
中因爲init
的時候比較複雜,鏈表的連接問題比較嚴重,這裏就不嘗試了。關於ucore
的實現可見個人CSDN 博客。