全部的實驗報告將會在 Github 同步更新,更多內容請移步至Github:https://github.com/AngelKitty/review_the_national_post-graduate_entrance_examination/blob/master/books_and_notes/professional_courses/operating_system/sources/ucore_os_lab/docs/lab_report/html
lab2
會依賴 lab1
,咱們須要把作的 lab1
的代碼填到 lab2
中缺失的位置上面。練習 0 就是一個工具的利用。這裏我使用的是 Linux
下的系統已預裝好的 Meld Diff Viewer
工具。具體操做流程以下圖所示:node
咱們只須要將已經完成的 lab1
和待完成的 lab2
兩個文件夾導入進來,而後點擊 compare
就好了。git
而後軟件就會自動分析兩份代碼的不一樣,而後就一個個比較比較複製過去就好了,在軟件裏面是能夠支持打開對比複製了,點擊 Copy Right
便可。固然 bin
目錄和 obj
目錄下都是 make
生成的,就不用複製了,其餘須要修改的地方主要有如下三個文件,經過對比複製完成便可:github
kern/debug/kdebug.c kern/init/init.c kern/trap/trap.c
在沒有其它技術支持的狀況下,咱們在分配內存空間的時候,分配給一個進程的地址空間必須是連續的。爲了提升利用效率,咱們但願分配出來的空間有適度的選擇。這些動態分配算法實際上就是你在去作選擇的作法。而選擇完以後呢,每一個進程可能用的時間長短不同,有的進程先結束,有的進程後結束,這個時候,有可能先結束的會留出一些空間,後面一個在分配的時候又會再去找,這個過程的執行就會在這過程當中留下一些碎片,這些碎片對於咱們後續的內存分配是有必定影響的,那咱們就從如何去找你要的空閒分區和如何來處理不能利用的這些小的空閒分區兩個角度來看內存分配算法。算法
該算法從空閒分區鏈首開始查找,直至找到一個能知足其大小要求的空閒分區爲止。而後再按照做業的大小,從該分區中劃出一塊內存分配給請求者,餘下的空閒分區仍留在空閒分區鏈中。shell
該算法是由首次適應算法演變而成的。在爲進程分配內存空間時,再也不每次從鏈首開始查找,直至找到一個能知足要求的空閒分區,並從中劃出一塊來分給做業。編程
該算法老是把既能知足要求,又是最小的空閒分區分配給做業。爲了加速查找,該算法要求將全部的空閒區按其大小排序後,以遞增順序造成一個空白鏈。這樣每次找到的第一個知足要求的空閒區,必然是最優的。孤立地看,該算法彷佛是最優的,但事實上並不必定。由於每次分配後剩餘的空間必定是最小的,在存儲器中將留下許多難以利用的小空閒區。同時每次分配後必須從新排序,這也帶來了必定的開銷。數組
該算法按大小遞減的順序造成空閒區鏈,分配時直接從空閒區鏈的第一個空閒區中分配(不能知足須要則不分配)。很顯然,若是第一個空閒分區不能知足,那麼再沒有空閒分區能知足須要。這種分配方法初看起來不太 合理,但它也有很強的直觀吸引力:在大空閒區中放入程序後,剩下的空閒區經常也很大,因而還能裝下一個較大的新程序。緩存
最壞適應算法與最佳適應算法的排序正好相反,它的隊列指針老是指向最大的空閒區,在進行分配時,老是從最大的空閒區開始查尋。數據結構
該算法克服了最佳適應算法留下的許多小的碎片的不足,但保留大的空閒區的可能性減少了,並且空閒區回收也和最佳適應算法同樣複雜。
咱們要先熟悉兩個數據結構,第一個就是以下所示的每個物理頁的屬性結構。
struct Page { int ref; // 頁幀的 引用計數 uint32_t flags; // 頁幀的狀態 Reserve 表示是否被內核保留 另外一個是 表示是否 可分配 unsigned int property; // 記錄連續空閒頁塊的數量 只在第一塊進行設置 list_entry_t page_link; // 用於將全部的頁幀串在一個雙向鏈表中 這個地方頗有趣 直接將 Page 這個結構體加入鏈表中會有點浪費空間 所以在 Page 中設置一個鏈表的結點 將其結點加入到鏈表中 還原的方法是將 鏈表中的 page_link 的地址 減去它所在的結構體中的偏移 就獲得了 Page 的起始地址 };
該結構四個成員變量意義以下:
ref
表示這樣頁被頁表的引用記數,這裏應該就是映射此物理頁的虛擬頁個數。一旦某頁表中有一個頁表項設置了虛擬頁到這個 Page
管理的物理頁的映射關係,就會把 Page
的 ref
加一。反之,如果解除,那就減一。flags
表示此物理頁的狀態標記,有兩個標誌位,第一個表示是否被保留,若是被保留了則設爲 1
(好比內核代碼佔用的空間)。第二個表示此頁是不是 free
的。若是設置爲 1
,表示這頁是 free
的,能夠被分配;若是設置爲 0
,表示這頁已經被分配出去了,不能被再二次分配。property
用來記錄某連續內存空閒塊的大小,這裏須要注意的是用到此成員變量的這個 Page
必定是連續內存塊的開始地址(第一頁的地址)。page_link
是便於把多個連續內存空閒塊連接在一塊兒的雙向鏈表指針,連續內存空閒塊利用這個頁的成員變量 page_link
來連接比它地址小和大的其餘連續內存空閒塊。而後是下面這個結構。一個雙向鏈表,負責管理全部的連續內存空閒塊,便於分配和釋放。
typedef struct { list_entry_t free_list; // the list header unsigned int nr_free; // # of free pages in this free list } free_area_t;
首先咱們須要完成的是 /home/moocos/moocos/ucore_lab/labcodes_answer/lab2_result/kern/mm/default_pmm.c
中的default_init
,default_init_memmap
,default_alloc_pages
,default_free_pages
這幾個函數的修改。
先來看看 default_init
函數,該函數它已經實現好了,不用作修改:
/* default_init: you can reuse the demo default_init fun to init the free_list and set nr_free to 0. free_list is used to record the free mem blocks. nr_free is the total number for free mem blocks. */ // 初始化空閒頁塊鏈表 static void default_init(void) { list_init(&free_list); nr_free = 0;// 空閒頁塊一開始是0個 }
而後是 default_init_memmap
,這個函數是用來初始化空閒頁鏈表的,初始化每個空閒頁,而後計算空閒頁的總數。
初始化每一個物理頁面記錄,而後將所有的可分配物理頁視爲一大塊空閒塊加入空閒表。
咱們能夠有以下實現過程:
/* default_init_memmap: CALL GRAPH: kern_init --> pmm_init-->page_init-->init_memmap--> pmm_manager->init_memmap * This fun is used to init a free block (with parameter: addr_base, page_number). * First you should init each page (in memlayout.h) in this free block, include: * p->flags should be set bit PG_property (means this page is valid. In pmm_init fun (in pmm.c), * the bit PG_reserved is setted in p->flags) * if this page is free and is not the first page of free block, p->property should be set to 0. * if this page is free and is the first page of free block, p->property should be set to total num of block. * p->ref should be 0, because now p is free and no reference. * We can use p->page_link to link this page to free_list, (such as: list_add_before(&free_list, &(p->page_link)); ) * Finally, we should sum the number of free mem block: nr_free+=n **/ // 初始化n個空閒頁塊 static void default_init_memmap(struct Page *base, size_t n) { assert(n > 0); struct Page *p = base; for (; p != base + n; p ++) { assert(PageReserved(p));//確認本頁是否爲保留頁 //設置標誌位 p->flags = p->property = 0; set_page_ref(p, 0);//清空引用 } base->property = n; //連續內存空閒塊的大小爲n,屬於物理頁管理鏈表,頭一個空閒頁塊 要設置數量 SetPageProperty(base); nr_free += n; //說明連續有n個空閒塊,屬於空閒鏈表 list_add_before(&free_list, &(p->page_link));//插入空閒頁的鏈表裏面,初始化完每一個空閒頁後,將其要插入到鏈表每次都插入到節點前面,由於是按地址排序 }
接着是 default_alloc_memmap
,主要就是從空閒頁塊的鏈表中去遍歷,找到第一塊大小大於 n
的塊,而後分配出來,把它從空閒頁鏈表中除去,而後若是有多餘的,把分完剩下的部分再次加入會空閒頁鏈表中便可。
首次適配算法要求按照地址從小到大查找空間,因此要求空閒表中的空閒空間按照地址從小到大排序。這樣,首次適配算法查詢空閒空間的方法就是從鏈表頭部開始找到第一個符合要求的空間,將這個空間從空閒表中刪除。空閒空間在分配完要求數量的物理頁以後可能會有剩餘,那麼須要將剩餘的部分做爲新的空閒空間插入到原空間位置(這樣才能保證空閒表中空閒空間地址遞增)
實現過程以下:
// 分配n個頁塊 * 設計思路: 分配空間的函數中進行了以下修改,由於free_list始終是排序的,分配的page塊有剩餘空間,那麼只需把 剩餘空閒塊節點插入到當前節點的前一個節點的後面(或者是當前節點後一個節點的前面)便可 if (page->property > n) { struct Page *p = page + n; p->property = page->property - n; // 將page的property改成n page->property = n; // 因爲是排好序的鏈表,只須要在le的前一個節點後面插入便可 list_add(list_prev(le), &(p->page_link)); // list_add_before(list_next(le), &(p->page_link)); } 1.第一種狀況(找不到知足需求的可供分配的空閒塊(全部的size均 < n)) 2.第二種狀況(恰好有知足大小的空閒塊) 執行分配前 -------------- -------------- ------------- | size < n | <---> | size = n | <---> | size > n | -------------- -------------- ------------- 執行分配後 -------------- ------------- | size < n | <---> | size > n | -------------- ------------- 3.第三種狀況(不存在恰好知足大小的空閒塊,但存在比其大的空閒塊) 執行分配前 -------------- ------------ -------------- | size < n | <---> | size > n | <---> | size > n1 | -------------- ------------ -------------- 執行分配後 -------------- --------------------- -------------- | size < n | <---> | size = size - n | <---> | size > n1 | -------------- --------------------- -------------- -------------------------------------------------------------------------------------------- /*code*/ static struct Page * default_alloc_pages(size_t n) { assert(n > 0); if (n > nr_free) { //若是全部的空閒頁的加起來的大小都不夠,那直接返回NULL return NULL; } struct Page *page = NULL; list_entry_t *le = &free_list;//從空閒塊鏈表的頭指針開始 // 查找 n 個或以上 空閒頁塊 若找到 則判斷是否大過 n 則將其拆分 並將拆分後的剩下的空閒頁塊加回到鏈表中 while ((le = list_next(le)) != &free_list) {//依次往下尋找直到回到頭指針處,即已經遍歷一次 // 此處 le2page 就是將 le 的地址 - page_link 在 Page 的偏移 從而找到 Page 的地址 struct Page *p = le2page(le, page_link);//將地址轉換成頁的結構 if (p->property >= n) {//因爲是first-fit,則遇到的第一個大於N的塊就選中便可 page = p; break; } } if (page != NULL) { if (page->property > n) { struct Page *p = page + n; p->property = page->property - n;//若是選中的第一個連續的塊大於n,只取其中的大小爲n的塊 SetPageProperty(p); // 將多出來的插入到 被分配掉的頁塊 後面 list_add(&(page->page_link), &(p->page_link)); } // 最後在空閒頁鏈表中刪除掉原來的空閒頁 list_del(&(page->page_link)); nr_free -= n;//當前空閒頁的數目減n ClearPageProperty(page); } return page; }
最後是 default_free_pages
,將須要釋放的空間標記爲空以後,須要找到空閒表中合適的位置。因爲空閒表中的記錄都是按照物理頁地址排序的,因此若是插入位置的前驅或者後繼恰好和釋放後的空間鄰接,那麼須要將新的空間與先後鄰接的空間合併造成更大的空間。
實現過程以下:
// 釋放掉 n 個 頁塊 * 設計思路: 1.尋找插入位置(插入到地址 > base的空閒塊鏈表節點前) // 1.尋找插入點 list_entry_t *le = LIST_HEAD; struct Page * node = NULL; while ((le = list_next(le)) != LIST_HEAD) { node = le2page(le, page_link); if (node > base) { break; } } 2.進行地址比對,已肯定插入位置及處理方式 分析: 循環結束狀況及處理方式分爲以下3種 (1).空閒鏈表爲空(只有頭結點),直接添加到頭結點後面就能夠 if (node == NULL) { list_add(&free_list, &(base->page_link)); } (2).查到鏈表尾均未發現比即將插入到空閒連表地址大的空閒塊。 a.先插入到鏈表尾部 b.嘗試與前一個節點進行合併 list_entry_t *prev = list_prev(le); if (node < base) { // 所需插入的節點爲末節點 // 先插入到空閒鏈表中 list_add(prev, &(base->page_link)); // 進行前向合併 if (node + node->property == base) { node->property += base->property; ClearPageProperty(base); list_del(&(base->page_link)); } } (3).找到比須要插入空閒塊地址大的空閒塊節點而跳出循環 a.先插入到找到的節點前面 b.嘗試與後一個節點進行合併 c.若是前一個節點不爲頭結點,則嘗試與前一個節點進行合併 // 所需插入節點不爲末節點 // 先插入到空閒鏈表中 list_add_before(le, &(base->page_link)); // 進行後向合併 if (base + base->property == node) { base->property += node->property; ClearPageProperty(node); // 摘除後向節點 list_del(le); } // 進行前向合併 if (prev != LIST_HEAD) { // 前拼接 node = le2page(prev, page_link); if (node + node ->property == base) { node->property += base->property; ClearPageProperty(base); // 摘掉當前節點 list_del(&(base->page_link)); } } 3.更新空閒鏈表可用空閒塊數量 nr_free += n; -------------------------------------------------------------------------------------------- /*code*/ static void default_free_pages(struct Page *base, size_t n) { assert(n > 0); struct Page *p = base; for (; p != base + n; p ++) { assert(!PageReserved(p) && !PageProperty(p)); p->flags = 0;//修改標誌位 set_page_ref(p, 0); } base->property = n;//設置連續大小爲n SetPageProperty(base); list_entry_t *le = list_next(&free_list); // 合併到合適的頁塊中 while (le != &free_list) { p = le2page(le, page_link);//獲取鏈表對應的Page le = list_next(le); if (base + base->property == p) { base->property += p->property; ClearPageProperty(p); list_del(&(p->page_link)); } else if (p + p->property == base) { p->property += base->property; ClearPageProperty(base); base = p; list_del(&(p->page_link)); } } nr_free += n; le = list_next(&free_list); // 將合併好的合適的頁塊添加回空閒頁塊鏈表 while (le != &free_list) { p = le2page(le, page_link); if (base + base->property <= p) { break; } le = list_next(le); } list_add_before(le, &(base->page_link));//將每一空閒塊對應的鏈表插入空閒鏈表中 }
你的First Fit算法是否有進一步的改進空間?
在上面的 First Fit
算法中,有兩個地方須要
這裏咱們須要實現的是 get_pte
函數,函數找到一個虛地址對應的二級頁表項的內核虛地址,若是此二級頁表項不存在,則分配一個包含此項的二級頁表。
因爲咱們已經具備了一個物理內存頁管理器 default_pmm_manager
,咱們就能夠用它來得到所需的空閒物理頁。
在二級頁表結構中,頁目錄表佔 4KB 空間,ucore 就可經過 default_pmm_manager 的 default_alloc_pages 函數得到一個空閒物理頁,這個頁的起始物理地址就是頁目錄表的起始地址。同理,ucore 也經過這種方式得到各個頁表所需的空間。頁表的空間大小取決與頁表要管理的物理頁數 n,一個頁表項(32位,即4字節)可管理一個物理頁,頁表須要佔 n/1024 個物理頁空間(向上取整)。這樣頁目錄表和頁表所佔的總大小約爲 4096+4∗n 字節。
根據LAZY,這裏咱們並無一開始就存在全部的二級頁表,而是等到須要的時候再添加對應的二級頁表。當創建從一級頁表到二級頁表的映射時,須要注意設置控制位。這裏應該設置同時設置上 PTE_U、PTE_W 和 PTE_P(定義可在mm/mmu.h)。若是原來就有二級頁表,或者新創建了頁表,則只需返回對應項的地址便可。
若是 create 參數爲 0,則 get_pte 返回 NULL;若是 create 參數不爲 0,則 get_pte 須要申請一個新的物理頁。
頁目錄項內容 = (頁表起始物理地址 & ~0x0FFF) | PTE_U | PTE_W | PTE_P
或者(分配的地址是4K對齊的,即低12位爲0,無需 & ~0x0FFF也行)
頁目錄項內容 = 頁表起始物理地址 | PTE_U | PTE_W | PTE_P
整理後的目錄項以下:
數據類型 | 說明 |
---|---|
pde_t | 全稱爲page directory entry,也就是一級頁表的表項(注意:pgdir 實際不是表項,而是一級頁表自己,pgdir 給出頁表起始地址。)。高22位存儲該目錄項所對應的頁表起始物理地址,其中高10位爲在頁目錄項表中的索引,中10位爲在頁表中的索引,低12位用於存儲標識信息(權限等) |
pte_t | 全稱爲page table entry,表示二級頁表的表項。高22爲存儲該頁表項所對應的頁面起始物理地址,低12位存儲標識信息(權限等) |
uintptr_t | 表示爲線性地址,因爲段式管理只作直接映射,因此它也是邏輯地址。 |
PTE_U | 位3,表示用戶態的軟件能夠讀取對應地址的物理內存頁內容 |
PTE_W | 位2,表示物理內存頁內容可寫 |
PTE_P | 位1,表示物理內存頁存在 |
實現過程以下:
* 設計思路: 提供一個虛擬地址,而後根據這個虛擬地址的高 10 位,找到頁目錄表中的 PDE 項。前20位是頁表項 (二級頁表) 的線性地址,後 12 位爲屬性,而後判斷一下 PDE 是否存在(就是判斷 P 位)。不存在,則獲取一個物理頁,而後將這個物理頁的線性地址寫入到 PDE 中,最後返回 PTE 項。簡而言之就是根據所給的虛擬地址,構造一個 PTE 項。 // 目錄表中目錄項的起始地址 pdep // 目錄表中目錄項的值 *pdep // 頁表的起始物理地址 (*pdep & ~0xFFF) 即 PDE_ADDR(*pdep) // 頁表的起始內核虛擬地址 KADDR((*pdep & ~0xFFF)) // la在頁表項的偏移量爲 PTX(la) // 頁表項的起始物理地址爲 (pte_t *)KADDR((*pdep & ~0xFFF)) + PTX(la) -------------------------------------------------------------------------------------------- /*code*/ pte_t *get_pte(pde_t *pgdir, uintptr_t la, bool create) { pde_t *pdep = &pgdir[PDX(la)]; // 找到 PDE 這裏的 pgdir 能夠看作是頁目錄表的基址 if (!(*pdep & PTE_P)) { // 看看 PDE 指向的頁表是否存在 struct Page* page = alloc_page(); // 不存在就申請一頁物理頁 /* 這裏說多幾句 經過 default_alloc_pages() 分配的頁 的地址 並非真正的頁分配的地址 實際上只是 Page 這個結構體所在的地址而已 故而須要 經過使用 page2pa() 將 Page 這個結構體 的地址 轉換成真正的物理頁地址的線性地址 而後須要注意的是 不管是 * 或是 memset 都是對虛擬地址進行操做的 因此須要將 真正的物理頁地址再轉換成 內核虛擬地址 */ if (!create || page == NULL) { //不存在且不須要建立,返回NULL return NULL; } set_page_ref(page, 1); //設置此頁被引用一次 uintptr_t pa = page2pa(page);//獲得 page 管理的那一頁的物理地址 memset(KADDR(pa), 0, PGSIZE); // 將這一頁清空 此時將線性地址轉換爲內核虛擬地址 *pdep = pa | PTE_U | PTE_W | PTE_P; // 設置 PDE 權限 } return &((pte_t *)KADDR(PDE_ADDR(*pdep)))[PTX(la)]; }
一、請描述頁目錄項(Page Directory Entry)和頁表項(Page Table Entry)中每一個組成部分的含義以及對 ucore 而言的潛在用處。
從低到高,分別是:
從低到高,分別是:
由於頁的映射是以物理頁面爲單位進行,因此頁面對應的物理地址老是按照 4096 字節對齊的,物理地址低 0-11 位老是零,因此在頁目錄項和頁表項中,低 0-11 位能夠用於做爲標誌字段使用。
位 | 意義 |
---|---|
0 | 表項有效標誌(PTE_U) |
1 | 可寫標誌(PTE_W) |
2 | 用戶訪問權限標誌(PTE_P) |
3 | 寫入標誌(PTE_PWT) |
4 | 禁用緩存標誌(PTE_PCD) |
5 | 訪問標誌(PTE_A) |
6 | 髒頁標誌(PTE_D) |
7 | 頁大小標誌(PTE_PS) |
8 | 零位標誌(PTE_MBZ) |
11 | 軟件可用標誌(PTE_AVAIL) |
12-31 | 頁表起始物理地址/頁起始物理地址 |
二、若是ucore執行過程當中訪問內存,出現了頁訪問異常,請問硬件要作哪些事情?
會進行換頁操做。首先 CPU 將產生頁訪問異常的線性地址放到 cr2 寄存器中,而後就是和普通的中斷同樣,保護現場,將寄存器的值壓入棧中,設置錯誤代碼 error_code,觸發 Page Fault 異常,而後壓入 error_code 中斷服務例程,將外存的數據換到內存中來,最後退出中斷,回到進入中斷前的狀態。
這裏主要是 page_remove_pte
的補全及完善。
思路主要就是先判斷該頁被引用的次數,若是隻被引用了一次,那麼直接釋放掉這頁, 不然就刪掉二級頁表的該表項,即該頁的入口。
取消頁表映射過程以下:
實現過程以下:
* 設計思路: 首先判斷一下 ptep 是否是合法——檢查一下 Present 位就是了。 而後經過註釋中所說的,經過 pte2page(*ptep) 獲取相應頁,減小引用計數並決定是否釋放頁。 最後把 TLB 中該頁的緩存刷掉就能夠了。 -------------------------------------------------------------------------------------------- /*code*/ static inline void page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep) { if ((*ptep & PTE_P)) { //判斷頁表中該表項是否存在 struct Page *page = pte2page(*ptep);// 將頁表項轉換爲頁數據結構 if (page_ref_dec(page) == 0) { // 判斷是否只被引用了一次,若引用計數減一後爲0,則釋放該物理頁 free_page(page); } *ptep = 0; // //若是被屢次引用,則不能釋放此頁,只用釋放二級頁表的表項,清空 PTE tlb_invalidate(pgdir, la); // 刷新 tlb } }
運行結果以下:
一、數據結構Page的全局變量(實際上是一個數組)的每一項與頁表中的頁目錄項和頁表項有無對應關係?若是有,其對應關係是啥?
存在對應關係,從pmm.h中的一系列轉換函數及KADDR、PADDR等宏定義中可知(page爲pages中的一個項): PDX: 頁目錄表索引 PTX: 頁表索引 PPN: 線性地址的高20位,即PDX + PTX PA: 物理地址 KA - KERNBASE KA: 內核虛地址 PA + KERNBASE page - pages = PPN PPN << 12 = PA &pages[PPN(pa)] = page
其實就是 Page 全局數組中以 Page Directory Index 和 Page Table Index 的組合 PPN (Physical Page Number) 爲索引的那一項。
二、若是但願虛擬地址與物理地址相等,則須要如何修改lab2,完成此事? 鼓勵經過編程來具體完成這個問題
* 設計思路: 1.必須先使內核的連接地址等於加載地址(以前嘗試過完成分頁機制後進行重映射的方法,後查看kernel.asm發現 內核文件的連接地址均爲0xc開頭的) 2.修改tool/kernel.ld文件, 將. = 0xC0100000修改成. = 0x00100000 3.修改kern/mm/memlayout.h中的KERNBASE宏定義爲0x00000000 4.去除pmm_init中的臨時映射及取消臨時映射機制 5.移除與使用非物理地址相等的虛擬地址的檢驗函數相關語句,即boodir[0] = 0或某種形式的 assert(boot_pgdir == 0)
實現過程以下:
① 修改連接腳本,將內核起始虛擬地址修改成0x100000
;
/* tools/kernel.ld **/ -------------------------------------------------------------------------------------------- /*code*/ SECTIONS { /* Load the kernel at this address: "." means the current address */ . = 0x100000; ...
② 修改虛擬內存空間起始地址爲0
/* kern/mm/memlayout.h **/ -------------------------------------------------------------------------------------------- /*code*/ /* All physical memory mapped at this address */ #define KERNBASE 0x00000000
③ 註釋掉取消0~4M區域內存頁映射的代碼
/* kern/mm/pmm.c **/ -------------------------------------------------------------------------------------------- /*code*/ //disable the map of virtual_addr 0~4M // boot_pgdir[0] = 0; //now the basic virtual memory map(see memalyout.h) is established. //check the correctness of the basic virtual memory map. // check_boot_pgdir();
在 Buddy System 中,空間塊之間的關係造成一顆徹底二叉樹,對於一顆有着 n 葉子的徹底二叉樹來講,全部節點的總數爲
初始化空閒鏈表
Buddy System 並不須要鏈表,可是爲了在調式的時候方便訪問全部空閒空間,仍是將全部的空閒空間加入鏈表中。
肯定分配空間大小
假設咱們獲得了大小爲 n 的空間,咱們須要在此基礎上創建 Buddy System,通過初始化後,Buddy System 管理的頁數爲
節點信息區:節點信息區能夠用來保存每一個節點對應子樹中可用空間的信息,用於在分配內存的時候便於檢查子樹中是否有足夠的空間來知足請求大小。在 32 位操做系統中,最大頁數不會超過 4GB/4KB=1M,全部使用一個 32 位整數便可表示每一個節點的信息。因此節點信息區的大小爲
虛擬分配區:佔用
實際分配區:顯然實際能夠獲得的內存大小不大可能恰好等於節點信息區大小+分配空間區大小。若是節點信息區大小+分配空間區大小<=內存大小,那麼實際能夠分配的區域就等於
做爲操做系統,天然但願實際使用的區域越大越好,不妨分類討論。
當內存小於等於512頁:此時不管如何節點信息都會佔用一頁,因此提升內存利率的方法就是將實際內存大小減一後向上取整(文中整數意爲2的冪)。
當內存大於512頁:不難證實,對於內存大小 \(n\) 來講,最佳虛擬分配區大小每每是 n 向下取整或者向上取整的數值,因此候選項也就是隻有兩個,因此能夠先考慮向下取整。對於
初始化節點信息
虛擬分配區可能會大於實際分配區,因此一開始須要將虛擬分配區中沒有實際分配區對應的空間標記爲已經分配進行屏蔽。另當前區塊的虛擬空間大小爲
以虛擬分配區 16 頁、實際分配區 14 頁爲例,初始化後以下:
Buddy System 要求分配空間爲 2 的冪,因此首先將請求的頁數向上對齊到 2 的冪。
接下來從二叉樹的根節點(1號節點)開始查找知足要求的節點。對於每次檢查的節點:
算法中加粗的部分主要爲了減小碎片而增長的額外優化。
當一個空間被分配以後,這個空間對應節點的全部父節點的可用空間表都會受到影響,須要自地向上從新更新可用空間信息。
Buddy System 要求分配空間爲 2 的冪,因此一樣首先將請求的頁數向上對齊到2的冪。
在進行釋放以前,須要肯定要釋放的空間對應的節點,而後將空間標記爲可用。接下來進行自底向上的操做:
實現過程以下:
因爲不能在堆上分配內存,沒法使用二級鏈表,故 buddy 分配算法使用一級雙向鏈表進行組織,該算法配合 最佳適應算法進行查找獲取,可在必定程度上減少內存碎片,爲程序騰出大的空閒空間。 設計思路: 1.新建buddy_pmm.h及buddy_pmm.c文件用於實現pmm_manager規定的接口(函數指針) 2.將pmm.c的init_pmm_manager的實現改成以下,經過Makefile加入預編譯宏_USE_PMM_BUDDY實現 向Buddy內存管理分配算法的切換,其中_USE_DEBUG宏用於輸出調試信息 static void init_pmm_manager(void) { #ifdef _USE_PMM_BUDDY pmm_manager = &buddy_pmm_manager; #else pmm_manager = &default_pmm_manager; #endif cprintf("memory management: %s\n", pmm_manager->name); pmm_manager->init(); } Makefile: # memory management algorithm ifdef DEFS DEFS := endif DEFS += -D_USE_PMM_BUDDY DEFS += -D_USE_DEBUG 3.添加用於輔助夥伴分配算法的數學函數,主要是指數函數、對數函數及對數取整函數 // 求以base爲底數,n爲指數的值 int32_t pow(int32_t base, int32_t n); int32_t pow2(int32_t n); // 求以base爲底數,n爲真數的對數函數,其要求n必須爲base的正整數次冪 size_t log(int32_t base, int32_t n); size_t log2(int32_t n); // 對數向上、向下取整函數簇 size_t log_round_up(int32_t base, int32_t n); size_t log_round_down(int32_t base, int32_t n); size_t log2_round_up(int32_t n); size_t log2_round_down(int32_t n); 4.依次實現相關函數指針,並初始化buddy_pmm_manager數據結構 主要數據結構以下: typedef struct Buddy { // 夥伴大小(2的次冪),在頭結點表示最大的2的次冪空閒塊 size_t power; // 夥伴的可用頁面大小(for speed up purpose),頭結點爲可用頁面數量,也方便比較 size_t property; // 用於組織buddy節點, 和page共享 list_entry_t node; } buddy_t, *buddy_ptr_t; // buddy buddy_t _buddy; 其中初始化函數以下: static void buddy_init_memmap(struct Page *base, size_t n) { // 可用空閒塊必須大於0 assert(n > 0); #ifdef _USE_DEBUG cprintf("avialable pages: %d\n", n); #endif // 設置可用空間大小 struct Page *p = base; _buddy.property += n; // 指數計數變量 register volatile int cnt; // 進行切分插入 size_t size; struct Page * bp; while (n > 0) { // 求剩下部分的最大2次冪 cnt = log2_round_down(n); size = pow2(cnt); n -= size; // 記錄最大的2的次冪(初始化時只須要記錄一次,後面再進行修改) if (_buddy.power == 0) { _buddy.power = cnt; } // 更新空閒塊頭指針 bp = p; for (; p != bp + size; p++) { assert(PageReserved(p)); p->flags = p->property = 0; set_page_ref(p, 0); } bp->property = size; SetPageProperty(bp); // 加入頭結點後(按從小到大的順序組織) list_add(get_buddy_head(), &(bp->page_link)); // 拆分完成,跳出 if (n == 0) { break; } } #ifdef _USE_DEBUG traverse_free_page_list(); #endif } -------------------------------------------------------------------------------------------- /* buddy.h **/ -------------------------------------------------------------------------------------------- /*code*/ #ifndef __KERN_MM_BUDDY_H__ #define __KERN_MM_BUDDY_H__ #include <pmm.h> extern const struct pmm_manager buddy_pmm_manager; #endif /* ! __KERN_MM_BUDDY_H__ */ -------------------------------------------------------------------------------------------- /* buddy.c **/ -------------------------------------------------------------------------------------------- /*code*/ #include <pmm.h> #include <list.h> #include <string.h> #include <buddy.h> free_area_t free_area; #define free_list (free_area.free_list) #define nr_free (free_area.nr_free) // Global block static size_t buddy_physical_size; static size_t buddy_virtual_size; static size_t buddy_segment_size; static size_t buddy_alloc_size; static size_t *buddy_segment; static struct Page *buddy_physical; static struct Page *buddy_alloc; #define MIN(a,b) ((a)<(b)?(a):(b)) // Buddy operate #define BUDDY_ROOT (1) #define BUDDY_LEFT(a) ((a)<<1) #define BUDDY_RIGHT(a) (((a)<<1)+1) #define BUDDY_PARENT(a) ((a)>>1) #define BUDDY_LENGTH(a) (buddy_virtual_size/UINT32_ROUND_DOWN(a)) #define BUDDY_BEGIN(a) (UINT32_REMAINDER(a)*BUDDY_LENGTH(a)) #define BUDDY_END(a) ((UINT32_REMAINDER(a)+1)*BUDDY_LENGTH(a)) #define BUDDY_BLOCK(a,b) (buddy_virtual_size/((b)-(a))+(a)/((b)-(a))) #define BUDDY_EMPTY(a) (buddy_segment[(a)] == BUDDY_LENGTH(a)) // Bitwise operate #define UINT32_SHR_OR(a,n) ((a)|((a)>>(n))) #define UINT32_MASK(a) (UINT32_SHR_OR(UINT32_SHR_OR(UINT32_SHR_OR(UINT32_SHR_OR(UINT32_SHR_OR(a,1),2),4),8),16)) #define UINT32_REMAINDER(a) ((a)&(UINT32_MASK(a)>>1)) #define UINT32_ROUND_UP(a) (UINT32_REMAINDER(a)?(((a)-UINT32_REMAINDER(a))<<1):(a)) #define UINT32_ROUND_DOWN(a) (UINT32_REMAINDER(a)?((a)-UINT32_REMAINDER(a)):(a)) static void buddy_init_size(size_t n) { assert(n > 1); buddy_physical_size = n; if (n < 512) { buddy_virtual_size = UINT32_ROUND_UP(n-1); buddy_segment_size = 1; } else { buddy_virtual_size = UINT32_ROUND_DOWN(n); buddy_segment_size = buddy_virtual_size*sizeof(size_t)*2/PGSIZE; if (n > buddy_virtual_size + (buddy_segment_size<<1)) { buddy_virtual_size <<= 1; buddy_segment_size <<= 1; } } buddy_alloc_size = MIN(buddy_virtual_size, buddy_physical_size-buddy_segment_size); } static void buddy_init_segment(struct Page *base) { // Init address buddy_physical = base; buddy_segment = KADDR(page2pa(base)); buddy_alloc = base + buddy_segment_size; memset(buddy_segment, 0, buddy_segment_size*PGSIZE); // Init segment nr_free += buddy_alloc_size; size_t block = BUDDY_ROOT; size_t alloc_size = buddy_alloc_size; size_t virtual_size = buddy_virtual_size; buddy_segment[block] = alloc_size; while (alloc_size > 0 && alloc_size < virtual_size) { virtual_size >>= 1; if (alloc_size > virtual_size) { // Add left to free list struct Page *page = &buddy_alloc[BUDDY_BEGIN(block)]; page->property = virtual_size; list_add(&(free_list), &(page->page_link)); buddy_segment[BUDDY_LEFT(block)] = virtual_size; // Switch ro right alloc_size -= virtual_size; buddy_segment[BUDDY_RIGHT(block)] = alloc_size; block = BUDDY_RIGHT(block); } else { // Switch to left buddy_segment[BUDDY_LEFT(block)] = alloc_size; buddy_segment[BUDDY_RIGHT(block)] = 0; block = BUDDY_LEFT(block); } } if (alloc_size > 0) { struct Page *page = &buddy_alloc[BUDDY_BEGIN(block)]; page->property = alloc_size; list_add(&(free_list), &(page->page_link)); } } static void buddy_init(void) { list_init(&free_list); nr_free = 0; } static void buddy_init_memmap(struct Page *base, size_t n) { assert(n > 0); // Init pages for (struct Page *p = base; p < base + n; p++) { assert(PageReserved(p)); p->flags = p->property = 0; } // Init size buddy_init_size(n); // Init segment buddy_init_segment(base); } static struct Page * buddy_alloc_pages(size_t n) { assert(n > 0); struct Page *page; size_t block = BUDDY_ROOT; size_t length = UINT32_ROUND_UP(n); // Find block while (length <= buddy_segment[block] && length < BUDDY_LENGTH(block)) { size_t left = BUDDY_LEFT(block); size_t right = BUDDY_RIGHT(block); if (BUDDY_EMPTY(block)) { // Split size_t begin = BUDDY_BEGIN(block); size_t end = BUDDY_END(block); size_t mid = (begin+end)>>1; list_del(&(buddy_alloc[begin].page_link)); buddy_alloc[begin].property >>= 1; buddy_alloc[mid].property = buddy_alloc[begin].property; buddy_segment[left] = buddy_segment[block]>>1; buddy_segment[right] = buddy_segment[block]>>1; list_add(&free_list, &(buddy_alloc[begin].page_link)); list_add(&free_list, &(buddy_alloc[mid].page_link)); block = left; } else if (length & buddy_segment[left]) { // Find in left (optimize) block = left; } else if (length & buddy_segment[right]) { // Find in right (optimize) block = right; } else if (length <= buddy_segment[left]) { // Find in left block = left; } else if (length <= buddy_segment[right]) {// Find in right block = right; } else { // Shouldn't be here assert(0); } } // Allocate if (length > buddy_segment[block]) return NULL; page = &(buddy_alloc[BUDDY_BEGIN(block)]); list_del(&(page->page_link)); buddy_segment[block] = 0; nr_free -= length; // Update buddy segment while (block != BUDDY_ROOT) { block = BUDDY_PARENT(block); buddy_segment[block] = buddy_segment[BUDDY_LEFT(block)] | buddy_segment[BUDDY_RIGHT(block)]; } return page; } static void buddy_free_pages(struct Page *base, size_t n) { assert(n > 0); struct Page *p = base; size_t length = UINT32_ROUND_UP(n); // Find buddy id size_t begin = (base-buddy_alloc); size_t end = begin + length; size_t block = BUDDY_BLOCK(begin, end); // Release block for (; p != base + n; p ++) { assert(!PageReserved(p)); p->flags = 0; set_page_ref(p, 0); } base->property = length; list_add(&(free_list), &(base->page_link)); nr_free += length; buddy_segment[block] = length; // Upadte & merge while (block != BUDDY_ROOT) { block = BUDDY_PARENT(block); size_t left = BUDDY_LEFT(block); size_t right = BUDDY_RIGHT(block); if (BUDDY_EMPTY(left) && BUDDY_EMPTY(right)) { // Merge size_t lbegin = BUDDY_BEGIN(left); size_t rbegin = BUDDY_BEGIN(right); list_del(&(buddy_alloc[lbegin].page_link)); list_del(&(buddy_alloc[rbegin].page_link)); buddy_segment[block] = buddy_segment[left]<<1; buddy_alloc[lbegin].property = buddy_segment[left]<<1; list_add(&(free_list), &(buddy_alloc[lbegin].page_link)); } else { // Update buddy_segment[block] = buddy_segment[BUDDY_LEFT(block)] | buddy_segment[BUDDY_RIGHT(block)]; } } } static size_t buddy_nr_free_pages(void) { return nr_free; } static void macro_check(void) { // Block operate check assert(BUDDY_ROOT == 1); assert(BUDDY_LEFT(3) == 6); assert(BUDDY_RIGHT(3) == 7); assert(BUDDY_PARENT(6) == 3); assert(BUDDY_PARENT(7) == 3); size_t buddy_virtual_size_store = buddy_virtual_size; size_t buddy_segment_root_store = buddy_segment[BUDDY_ROOT]; buddy_virtual_size = 16; buddy_segment[BUDDY_ROOT] = 16; assert(BUDDY_LENGTH(6) == 4); assert(BUDDY_BEGIN(6) == 8); assert(BUDDY_END(6) == 12); assert(BUDDY_BLOCK(8, 12) == 6); assert(BUDDY_EMPTY(BUDDY_ROOT)); buddy_virtual_size = buddy_virtual_size_store; buddy_segment[BUDDY_ROOT] = buddy_segment_root_store; // Bitwise operate check assert(UINT32_SHR_OR(0xCC, 2) == 0xFF); assert(UINT32_MASK(0x4000) == 0x7FFF); assert(UINT32_REMAINDER(0x4321) == 0x321); assert(UINT32_ROUND_UP(0x2321) == 0x4000); assert(UINT32_ROUND_UP(0x2000) == 0x2000); assert(UINT32_ROUND_DOWN(0x4321) == 0x4000); assert(UINT32_ROUND_DOWN(0x4000) == 0x4000); } static void size_check(void) { size_t buddy_physical_size_store = buddy_physical_size; buddy_init_size(200); assert(buddy_virtual_size == 256); buddy_init_size(1024); assert(buddy_virtual_size == 1024); buddy_init_size(1026); assert(buddy_virtual_size == 1024); buddy_init_size(1028); assert(buddy_virtual_size == 1024); buddy_init_size(1030); assert(buddy_virtual_size == 2048); buddy_init_size(buddy_physical_size_store); } static void segment_check(void) { // Check buddy segment size_t total = 0, count = 0; for (size_t block = BUDDY_ROOT; block < (buddy_virtual_size<<1); block++) if (BUDDY_EMPTY(block)) total += BUDDY_LENGTH(block); else if (block < buddy_virtual_size) assert(buddy_segment[block] == (buddy_segment[BUDDY_LEFT(block)] | buddy_segment[BUDDY_RIGHT(block)])); assert(total == nr_free_pages()); // Check free list total = 0, count = 0; list_entry_t *le = &free_list; while ((le = list_next(le)) != &free_list) { struct Page *p = le2page(le, page_link); count ++, total += p->property; } assert(total == nr_free_pages()); } static void alloc_check(void) { // Build buddy system for test size_t buddy_physical_size_store = buddy_physical_size; for (struct Page *p = buddy_physical; p < buddy_physical + 1026; p++) SetPageReserved(p); buddy_init(); buddy_init_memmap(buddy_physical, 1026); // Check allocation struct Page *p0, *p1, *p2, *p3; p0 = p1 = p2 = NULL; assert((p0 = alloc_page()) != NULL); assert((p1 = alloc_page()) != NULL); assert((p2 = alloc_page()) != NULL); assert((p3 = alloc_page()) != NULL); assert(p0 + 1 == p1); assert(p1 + 1 == p2); assert(p2 + 1 == p3); assert(page_ref(p0) == 0 && page_ref(p1) == 0 && page_ref(p2) == 0 && page_ref(p3) == 0); assert(page2pa(p0) < npage * PGSIZE); assert(page2pa(p1) < npage * PGSIZE); assert(page2pa(p2) < npage * PGSIZE); assert(page2pa(p3) < npage * PGSIZE); list_entry_t *le = &free_list; while ((le = list_next(le)) != &free_list) { struct Page *p = le2page(le, page_link); assert(buddy_alloc_pages(p->property) != NULL); } assert(alloc_page() == NULL); // Check release free_page(p0); free_page(p1); free_page(p2); assert(nr_free == 3); assert((p1 = alloc_page()) != NULL); assert((p0 = alloc_pages(2)) != NULL); assert(p0 + 2 == p1); assert(alloc_page() == NULL); free_pages(p0, 2); free_page(p1); free_page(p3); struct Page *p; assert((p = alloc_pages(4)) == p0); assert(alloc_page() == NULL); assert(nr_free == 0); // Restore buddy system for (struct Page *p = buddy_physical; p < buddy_physical + buddy_physical_size_store; p++) SetPageReserved(p); buddy_init(); buddy_init_memmap(buddy_physical, buddy_physical_size_store); } static void default_check(void) { // Check buddy system macro_check(); size_check(); segment_check(); alloc_check(); } const struct pmm_manager buddy_pmm_manager = { .name = "buddy_pmm_manager", .init = buddy_init, .init_memmap = buddy_init_memmap, .alloc_pages = buddy_alloc_pages, .free_pages = buddy_free_pages, .nr_free_pages = buddy_nr_free_pages, .check = default_check, };
實際上 Slub 分配算法是很是複雜的,須要考慮緩存對齊、NUMA 等很是多的問題,做爲實驗性質的操做系統就不考慮這些複雜因素了。簡化的 Slub 算法結合了 Slab 算法和 Slub 算法的部分特徵,使用了一些比較右技巧性的實現方法。具體的簡化爲:
在操做系統中常常會用到大量相同的數據對象,例如互斥鎖、條件變量等等,同種數據對象的初始化方法、銷燬方法、佔用內存大小都是同樣的,若是操做系統可以將全部的數據對象進行統一管理,能夠提升內存利用率,同時也避免了反覆初始化對象的開銷。
每種對象由倉庫(感受cache在這裏翻譯爲倉庫更好)進行統一管理:
struct kmem_cache_t { list_entry_t slabs_full; // 全滿Slab鏈表 list_entry_t slabs_partial; // 部分空閒Slab鏈表 list_entry_t slabs_free; // 全空閒Slab鏈表 uint16_t objsize; // 對象大小 uint16_t num; // 每一個Slab保存的對象數目 void (*ctor)(void*, struct kmem_cache_t *, size_t); // 構造函數 void (*dtor)(void*, struct kmem_cache_t *, size_t); // 析構函數 char name[CACHE_NAMELEN]; // 倉庫名稱 list_entry_t cache_link; // 倉庫鏈表 };
因爲限制 Slab 大小爲一頁,因此數據對象和每頁對象數據不會超過
在上面的Buddy System中,一個物理頁被分配以後,Page 結構中除了 ref 以外的成員都沒有其餘用處了,能夠把Slab的元數據保存在這些內存中:
struct slab_t { int ref; // 頁的引用次數(保留) struct kmem_cache_t *cachep; // 倉庫對象指針 uint16_t inuse; // 已經分配對象數目 int16_t free; // 下一個空閒對象偏移量 list_entry_t slab_link; // Slab鏈表 };
爲了方便空閒區域的管理,Slab 對應的內存頁分爲兩部分:保存空閒信息的 bufcnt 以及可用內存區域 buf。
對象數據不會超過2048,因此 bufctl 中每一個條目爲 16 位整數。bufctl 中每一個「格子」都對應着一個對象內存區域,不難發現,bufctl 保存的是一個隱式鏈表,格子中保存的內容就是下一個空閒區域的偏移,-1 表示不存在更多空閒區,slab_t 中的 free 就是鏈表頭部。
除了能夠自行管理倉庫以外,操做系統每每也提供了一些常見大小的倉庫,本文實現中內置了 8 個倉庫,倉庫對象大小爲:8B、16B、32B、64B、128B、256B、512B、1024B。
void *kmem_cache_grow(struct kmem_cache_t *cachep);
申請一頁內存,初始化空閒鏈表 bufctl,構造 buf 中的對象,更新 Slab 元數據,最後將新的 Slab 加入到倉庫的空閒Slab表中。
void kmem_slab_destroy(struct kmem_cache_t *cachep, struct slab_t *slab);
析構 buf 中的對象後將內存頁歸還。
void kmem_int();
初始化 kmem_cache_t 倉庫:因爲 kmem_cache_t 也是由 Slab 算法分配的,因此須要預先手動初始化一個kmem_cache_t 倉庫;
初始化內置倉庫:初始化 8 個固定大小的內置倉庫。
kmem_cache_create(const char *name, size_t size, void (*ctor)(void*, struct kmem_cache_t *, size_t),void (*dtor)(void*, struct kmem_cache_t *, size_t));
從 kmem_cache_t 倉庫中得到一個對象,初始化成員,最後將對象加入倉庫鏈表。其中須要注意的就是計算 Slab 中對象的數目,因爲空閒表每一項佔用 2 字節,因此每一個 Slab 的對象數目就是:4096 字節/(2字節+對象大小)。
void kmem_cache_destroy(struct kmem_cache_t *cachep);
釋放倉庫中全部的 Slab,釋放 kmem_cache_t。
void *kmem_cache_alloc(struct kmem_cache_t *cachep);
先查找 slabs_partial,若是沒找到空閒區域則查找 slabs_free,仍是沒找到就申請一個新的 slab。從 slab 分配一個對象後,若是 slab 變滿,那麼將 slab 加入 slabs_full。
void *kmem_cache_zalloc(struct kmem_cache_t *cachep);
使用 kmem_cache_alloc 分配一個對象以後將對象內存區域初始化爲零。
void kmem_cache_free(struct kmem_cache_t *cachep, void *objp);
將對象從 Slab 中釋放,也就是將對象空間加入空閒鏈表,更新 Slab 元信息。若是 Slab 變空,那麼將 Slab 加入slabs_partial 鏈表。
size_t kmem_cache_size(struct kmem_cache_t *cachep);
得到倉庫中對象的大小。
const char *kmem_cache_name(struct kmem_cache_t *cachep);
得到倉庫的名稱。
int kmem_cache_shrink(struct kmem_cache_t *cachep);
將倉庫中 slabs_free 中全部 Slab 釋放。
int kmem_cache_reap();
遍歷倉庫鏈表,對每個倉庫進行 kmem_cache_shrink 操做。
void *kmalloc(size_t size);
找到大小最合適的內置倉庫,申請一個對象。
void kfree(const void *objp);
釋放內置倉庫對象。
size_t ksize(const void *objp);
得到倉庫對象大小。
實現過程以下:
-------------------------------------------------------------------------------------------- /* slub.h **/ -------------------------------------------------------------------------------------------- /*code*/ #ifndef __KERN_MM_SLUB_H__ #define __KERN_MM_SLUB_H__ #include <pmm.h> #include <list.h> #define CACHE_NAMELEN 16 struct kmem_cache_t { list_entry_t slabs_full; list_entry_t slabs_partial; list_entry_t slabs_free; uint16_t objsize; uint16_t num; void (*ctor)(void*, struct kmem_cache_t *, size_t); void (*dtor)(void*, struct kmem_cache_t *, size_t); char name[CACHE_NAMELEN]; list_entry_t cache_link; }; struct kmem_cache_t * kmem_cache_create(const char *name, size_t size, void (*ctor)(void*, struct kmem_cache_t *, size_t), void (*dtor)(void*, struct kmem_cache_t *, size_t)); void kmem_cache_destroy(struct kmem_cache_t *cachep); void *kmem_cache_alloc(struct kmem_cache_t *cachep); void *kmem_cache_zalloc(struct kmem_cache_t *cachep); void kmem_cache_free(struct kmem_cache_t *cachep, void *objp); size_t kmem_cache_size(struct kmem_cache_t *cachep); const char *kmem_cache_name(struct kmem_cache_t *cachep); int kmem_cache_shrink(struct kmem_cache_t *cachep); int kmem_cache_reap(); void *kmalloc(size_t size); void kfree(void *objp); size_t ksize(void *objp); void kmem_int(); #endif /* ! __KERN_MM_SLUB_H__ */ -------------------------------------------------------------------------------------------- /* slub.c **/ -------------------------------------------------------------------------------------------- /*code*/ #include <slub.h> #include <list.h> #include <defs.h> #include <string.h> #include <stdio.h> struct slab_t { int ref; struct kmem_cache_t *cachep; uint16_t inuse; uint16_t free; list_entry_t slab_link; }; // The number of sized cache : 16, 32, 64, 128, 256, 512, 1024, 2048 #define SIZED_CACHE_NUM 8 #define SIZED_CACHE_MIN 16 #define SIZED_CACHE_MAX 2048 #define le2slab(le,link) ((struct slab_t*)le2page((struct Page*)le,link)) #define slab2kva(slab) (page2kva((struct Page*)slab)) static list_entry_t cache_chain; static struct kmem_cache_t cache_cache; static struct kmem_cache_t *sized_caches[SIZED_CACHE_NUM]; static char *cache_cache_name = "cache"; static char *sized_cache_name = "sized"; // kmem_cache_grow - add a free slab static void * kmem_cache_grow(struct kmem_cache_t *cachep) { struct Page *page = alloc_page(); void *kva = page2kva(page); // Init slub meta data struct slab_t *slab = (struct slab_t *) page; slab->cachep = cachep; slab->inuse = slab->free = 0; list_add(&(cachep->slabs_free), &(slab->slab_link)); // Init bufctl int16_t *bufctl = kva; for (int i = 1; i < cachep->num; i++) bufctl[i-1] = i; bufctl[cachep->num-1] = -1; // Init cache void *buf = bufctl + cachep->num; if (cachep->ctor) for (void *p = buf; p < buf + cachep->objsize * cachep->num; p += cachep->objsize) cachep->ctor(p, cachep, cachep->objsize); return slab; } // kmem_slab_destroy - destroy a slab static void kmem_slab_destroy(struct kmem_cache_t *cachep, struct slab_t *slab) { // Destruct cache struct Page *page = (struct Page *) slab; int16_t *bufctl = page2kva(page); void *buf = bufctl + cachep->num; if (cachep->dtor) for (void *p = buf; p < buf + cachep->objsize * cachep->num; p += cachep->objsize) cachep->dtor(p, cachep, cachep->objsize); // Return slub page page->property = page->flags = 0; list_del(&(page->page_link)); free_page(page); } static int kmem_sized_index(size_t size) { // Round up size_t rsize = ROUNDUP(size, 2); if (rsize < SIZED_CACHE_MIN) rsize = SIZED_CACHE_MIN; // Find index int index = 0; for (int t = rsize / 32; t; t /= 2) index ++; return index; } // ! Test code #define TEST_OBJECT_LENTH 2046 #define TEST_OBJECT_CTVAL 0x22 #define TEST_OBJECT_DTVAL 0x11 static const char *test_object_name = "test"; struct test_object { char test_member[TEST_OBJECT_LENTH]; }; static void test_ctor(void* objp, struct kmem_cache_t * cachep, size_t size) { char *p = objp; for (int i = 0; i < size; i++) p[i] = TEST_OBJECT_CTVAL; } static void test_dtor(void* objp, struct kmem_cache_t * cachep, size_t size) { char *p = objp; for (int i = 0; i < size; i++) p[i] = TEST_OBJECT_DTVAL; } static size_t list_length(list_entry_t *listelm) { size_t len = 0; list_entry_t *le = listelm; while ((le = list_next(le)) != listelm) len ++; return len; } static void check_kmem() { assert(sizeof(struct Page) == sizeof(struct slab_t)); size_t fp = nr_free_pages(); // Create a cache struct kmem_cache_t *cp0 = kmem_cache_create(test_object_name, sizeof(struct test_object), test_ctor, test_dtor); assert(cp0 != NULL); assert(kmem_cache_size(cp0) == sizeof(struct test_object)); assert(strcmp(kmem_cache_name(cp0), test_object_name) == 0); // Allocate six objects struct test_object *p0, *p1, *p2, *p3, *p4, *p5; char *p; assert((p0 = kmem_cache_alloc(cp0)) != NULL); assert((p1 = kmem_cache_alloc(cp0)) != NULL); assert((p2 = kmem_cache_alloc(cp0)) != NULL); assert((p3 = kmem_cache_alloc(cp0)) != NULL); assert((p4 = kmem_cache_alloc(cp0)) != NULL); p = (char *) p4; for (int i = 0; i < sizeof(struct test_object); i++) assert(p[i] == TEST_OBJECT_CTVAL); assert((p5 = kmem_cache_zalloc(cp0)) != NULL); p = (char *) p5; for (int i = 0; i < sizeof(struct test_object); i++) assert(p[i] == 0); assert(nr_free_pages()+3 == fp); assert(list_empty(&(cp0->slabs_free))); assert(list_empty(&(cp0->slabs_partial))); assert(list_length(&(cp0->slabs_full)) == 3); // Free three objects kmem_cache_free(cp0, p3); kmem_cache_free(cp0, p4); kmem_cache_free(cp0, p5); assert(list_length(&(cp0->slabs_free)) == 1); assert(list_length(&(cp0->slabs_partial)) == 1); assert(list_length(&(cp0->slabs_full)) == 1); // Shrink cache assert(kmem_cache_shrink(cp0) == 1); assert(nr_free_pages()+2 == fp); assert(list_empty(&(cp0->slabs_free))); p = (char *) p4; for (int i = 0; i < sizeof(struct test_object); i++) assert(p[i] == TEST_OBJECT_DTVAL); // Reap cache kmem_cache_free(cp0, p0); kmem_cache_free(cp0, p1); kmem_cache_free(cp0, p2); assert(kmem_cache_reap() == 2); assert(nr_free_pages() == fp); // Destory a cache kmem_cache_destroy(cp0); // Sized alloc assert((p0 = kmalloc(2048)) != NULL); assert(nr_free_pages()+1 == fp); kfree(p0); assert(kmem_cache_reap() == 1); assert(nr_free_pages() == fp); cprintf("check_kmem() succeeded!\n"); } // ! End of test code // kmem_cache_create - create a kmem_cache struct kmem_cache_t * kmem_cache_create(const char *name, size_t size, void (*ctor)(void*, struct kmem_cache_t *, size_t), void (*dtor)(void*, struct kmem_cache_t *, size_t)) { assert(size <= (PGSIZE - 2)); struct kmem_cache_t *cachep = kmem_cache_alloc(&(cache_cache)); if (cachep != NULL) { cachep->objsize = size; cachep->num = PGSIZE / (sizeof(int16_t) + size); cachep->ctor = ctor; cachep->dtor = dtor; memcpy(cachep->name, name, CACHE_NAMELEN); list_init(&(cachep->slabs_full)); list_init(&(cachep->slabs_partial)); list_init(&(cachep->slabs_free)); list_add(&(cache_chain), &(cachep->cache_link)); } return cachep; } // kmem_cache_destroy - destroy a kmem_cache void kmem_cache_destroy(struct kmem_cache_t *cachep) { list_entry_t *head, *le; // Destory full slabs head = &(cachep->slabs_full); le = list_next(head); while (le != head) { list_entry_t *temp = le; le = list_next(le); kmem_slab_destroy(cachep, le2slab(temp, page_link)); } // Destory partial slabs head = &(cachep->slabs_partial); le = list_next(head); while (le != head) { list_entry_t *temp = le; le = list_next(le); kmem_slab_destroy(cachep, le2slab(temp, page_link)); } // Destory free slabs head = &(cachep->slabs_free); le = list_next(head); while (le != head) { list_entry_t *temp = le; le = list_next(le); kmem_slab_destroy(cachep, le2slab(temp, page_link)); } // Free kmem_cache kmem_cache_free(&(cache_cache), cachep); } // kmem_cache_alloc - allocate an object void * kmem_cache_alloc(struct kmem_cache_t *cachep) { list_entry_t *le = NULL; // Find in partial list if (!list_empty(&(cachep->slabs_partial))) le = list_next(&(cachep->slabs_partial)); // Find in empty list else { if (list_empty(&(cachep->slabs_free)) && kmem_cache_grow(cachep) == NULL) return NULL; le = list_next(&(cachep->slabs_free)); } // Alloc list_del(le); struct slab_t *slab = le2slab(le, page_link); void *kva = slab2kva(slab); int16_t *bufctl = kva; void *buf = bufctl + cachep->num; void *objp = buf + slab->free * cachep->objsize; // Update slab slab->inuse ++; slab->free = bufctl[slab->free]; if (slab->inuse == cachep->num) list_add(&(cachep->slabs_full), le); else list_add(&(cachep->slabs_partial), le); return objp; } // kmem_cache_zalloc - allocate an object and fill it with zero void * kmem_cache_zalloc(struct kmem_cache_t *cachep) { void *objp = kmem_cache_alloc(cachep); memset(objp, 0, cachep->objsize); return objp; } // kmem_cache_free - free an object void kmem_cache_free(struct kmem_cache_t *cachep, void *objp) { // Get slab of object void *base = page2kva(pages); void *kva = ROUNDDOWN(objp, PGSIZE); struct slab_t *slab = (struct slab_t *) &pages[(kva-base)/PGSIZE]; // Get offset in slab int16_t *bufctl = kva; void *buf = bufctl + cachep->num; int offset = (objp - buf) / cachep->objsize; // Update slab list_del(&(slab->slab_link)); bufctl[offset] = slab->free; slab->inuse --; slab->free = offset; if (slab->inuse == 0) list_add(&(cachep->slabs_free), &(slab->slab_link)); else list_add(&(cachep->slabs_partial), &(slab->slab_link)); } // kmem_cache_size - get object size size_t kmem_cache_size(struct kmem_cache_t *cachep) { return cachep->objsize; } // kmem_cache_name - get cache name const char * kmem_cache_name(struct kmem_cache_t *cachep) { return cachep->name; } // kmem_cache_shrink - destroy all slabs in free list int kmem_cache_shrink(struct kmem_cache_t *cachep) { int count = 0; list_entry_t *le = list_next(&(cachep->slabs_free)); while (le != &(cachep->slabs_free)) { list_entry_t *temp = le; le = list_next(le); kmem_slab_destroy(cachep, le2slab(temp, page_link)); count ++; } return count; } // kmem_cache_reap - reap all free slabs int kmem_cache_reap() { int count = 0; list_entry_t *le = &(cache_chain); while ((le = list_next(le)) != &(cache_chain)) count += kmem_cache_shrink(to_struct(le, struct kmem_cache_t, cache_link)); return count; } void * kmalloc(size_t size) { assert(size <= SIZED_CACHE_MAX); return kmem_cache_alloc(sized_caches[kmem_sized_index(size)]); } void kfree(void *objp) { void *base = slab2kva(pages); void *kva = ROUNDDOWN(objp, PGSIZE); struct slab_t *slab = (struct slab_t *) &pages[(kva-base)/PGSIZE]; kmem_cache_free(slab->cachep, objp); } void kmem_int() { // Init cache for kmem_cache cache_cache.objsize = sizeof(struct kmem_cache_t); cache_cache.num = PGSIZE / (sizeof(int16_t) + sizeof(struct kmem_cache_t)); cache_cache.ctor = NULL; cache_cache.dtor = NULL; memcpy(cache_cache.name, cache_cache_name, CACHE_NAMELEN); list_init(&(cache_cache.slabs_full)); list_init(&(cache_cache.slabs_partial)); list_init(&(cache_cache.slabs_free)); list_init(&(cache_chain)); list_add(&(cache_chain), &(cache_cache.cache_link)); // Init sized cache for (int i = 0, size = 16; i < SIZED_CACHE_NUM; i++, size *= 2) sized_caches[i] = kmem_cache_create(sized_cache_name, size, NULL, NULL); check_kmem(); }