ucoreOS_lab2 實驗報告

所有的實驗報告將會在 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/

練習0:填寫已有實驗

lab2 會依賴 lab1 ,我們需要把做的 lab1 的代碼填到 lab2 中缺失的位置上面。練習 0 就是一個工具的利用。這裏我使用的是 Linux 下的系統已預裝好的 Meld Diff Viewer 工具。具體操作流程如下圖所示:

step_by_step

我們只需要將已經完成的 lab1 和待完成的 lab2 兩個文件夾導入進來,然後點擊 compare 就行了。

compare

然後軟件就會自動分析兩份代碼的不同,然後就一個個比較比較複製過去就行了,在軟件裏面是可以支持打開對比複製了,點擊 Copy Right 即可。當然 bin 目錄和 obj 目錄下都是 make 生成的,就不用複製了,其他需要修改的地方主要有以下三個文件,通過對比複製完成即可:

kern/debug/kdebug.c
kern/init/init.c
kern/trap/trap.c

練習1:實現 first-fit 連續物理內存分配算法(需要編程)

在沒有其它技術支持的情況下,我們在分配內存空間的時候,分配給一個進程的地址空間必須是連續的。爲了提高利用效率,我們希望分配出來的空間有適度的選擇。這些動態分配算法實際上就是你在去做選擇的做法。而選擇完之後呢,每個進程可能用的時間長短不一樣,有的進程先結束,有的進程後結束,這個時候,有可能先結束的會留出一些空間,後面一個在分配的時候又會再去找,這個過程的執行就會在這過程中留下一些碎片,這些碎片對於我們後續的內存分配是有一定影響的,那我們就從如何去找你要的空閒分區和如何來處理不能利用的這些小的空閒分區兩個角度來看內存分配算法。

  • 1、First Fit(最先匹配算法)

該算法從空閒分區鏈首開始查找,直至找到一個能滿足其大小要求的空閒分區爲止。然後再按照作業的大小,從該分區中劃出一塊內存分配給請求者,餘下的空閒分區仍留在空閒分區鏈中。

    • 優點:該算法傾向於使用內存中低地址部分的空閒區,在高地址部分的空閒區很少被利用,從而保留了高地址部分的大空閒區。顯然爲以後到達的大作業分配大的內存空間創造了條件。
    • 缺點:低地址部分不斷被劃分,留下許多難以利用、很小的空閒區,而每次查找又都從低地址部分開始,會增加查找的開銷。
  • 2、Next Fit(循環首次匹配算法)

該算法是由首次適應算法演變而成的。在爲進程分配內存空間時,不再每次從鏈首開始查找,直至找到一個能滿足要求的空閒分區,並從中劃出一塊來分給作業。

    • 優點:使內存中的空閒分區分佈的更爲均勻,減少了查找時的系統開銷。
    • 缺點:缺乏大的空閒分區,從而導致不能裝入大型作業。
  • 3、Best Fit(最佳匹配算法)

該算法總是把既能滿足要求,又是最小的空閒分區分配給作業。爲了加速查找,該算法要求將所有的空閒區按其大小排序後,以遞增順序形成一個空白鏈。這樣每次找到的第一個滿足要求的空閒區,必然是最優的。孤立地看,該算法似乎是最優的,但事實上並不一定。因爲每次分配後剩餘的空間一定是最小的,在存儲器中將留下許多難以利用的小空閒區。同時每次分配後必須重新排序,這也帶來了一定的開銷。

    • 優點:每次分配給文件的都是最合適該文件大小的分區。
    • 缺點:內存中留下許多難以利用的小的空閒區。
  • 4、Worst Fit(最差匹配算法)

該算法按大小遞減的順序形成空閒區鏈,分配時直接從空閒區鏈的第一個空閒區中分配(不能滿足需要則不分配)。很顯然,如果第一個空閒分區不能滿足,那麼再沒有空閒分區能滿足需要。這種分配方法初看起來不太 合理,但它也有很強的直觀吸引力:在大空閒區中放入程序後,剩下的空閒區常常也很大,於是還能裝下一個較大的新程序。

最壞適應算法與最佳適應算法的排序正好相反,它的隊列指針總是指向最大的空閒區,在進行分配時,總是從最大的空閒區開始查尋。

該算法克服了最佳適應算法留下的許多小的碎片的不足,但保留大的空閒區的可能性減小了,而且空閒區回收也和最佳適應算法一樣複雜。

    • 優點:給文件分配分區後剩下的空閒區不至於太小,產生碎片的機率最小,對中小型文件分配分區操作有利。
    • 缺點:使存儲器中缺乏大的空閒區,對大型文件的分區分配不利。

我們要先熟悉兩個數據結構,第一個就是如下所示的每一個物理頁的屬性結構。

struct Page {
    int ref;                        // 頁幀的 引用計數
    uint32_t flags;                 // 頁幀的狀態 Reserve 表示是否被內核保留 另一個是 表示是否 可分配
    unsigned int property;          // 記錄連續空閒頁塊的數量 只在第一塊進行設置
    list_entry_t page_link;         // 用於將所有的頁幀串在一個雙向鏈表中 這個地方很有趣 直接將 Page 這個結構體加入鏈表中會有點浪費空間 因此在 Page 中設置一個鏈表的結點 將其結點加入到鏈表中 還原的方法是將 鏈表中的 page_link 的地址 減去它所在的結構體中的偏移 就得到了 Page 的起始地址
};

該結構四個成員變量意義如下:

  • 1、ref 表示這樣頁被頁表的引用記數,這裏應該就是映射此物理頁的虛擬頁個數。一旦某頁表中有一個頁表項設置了虛擬頁到這個 Page 管理的物理頁的映射關係,就會把 Pageref 加一。反之,若是解除,那就減一。
  • 2、 flags 表示此物理頁的狀態標記,有兩個標誌位,第一個表示是否被保留,如果被保留了則設爲 1(比如內核代碼佔用的空間)。第二個表示此頁是否是 free 的。如果設置爲 1 ,表示這頁是 free 的,可以被分配;如果設置爲 0 ,表示這頁已經被分配出去了,不能被再二次分配。
  • 3、property 用來記錄某連續內存空閒塊的大小,這裏需要注意的是用到此成員變量的這個 Page 一定是連續內存塊的開始地址(第一頁的地址)。
  • 4、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;
  • free_list 是一個 list_entry 結構的雙向鏈表指針
  • nr_free 則記錄當前空閒頁的個數

首先我們需要完成的是 /home/moocos/moocos/ucore_lab/labcodes_answer/lab2_result/kern/mm/default_pmm.c 中的default_initdefault_init_memmapdefault_alloc_pagesdefault_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 算法中,有兩個地方需要 時間複雜度:鏈表查找和有序鏈表插入。對於其中的有序鏈表插入,在特殊情況下是可以優化的。當一個剛被釋放的內存塊來說,如果它的鄰接空間都是空閒的,那麼就不需要進行線性時間複雜度的鏈表插入操作,而是直接併入鄰接空間,時間複雜度爲常數。爲了判斷鄰接空間是否爲空閒狀態,空閒塊的信息除了保存在第一個頁面之外,還需要在最後一頁保存信息,這樣新的空閒塊只需要檢查鄰接的兩個頁面就能判斷鄰接空間塊的狀態。

練習2:實現尋找虛擬地址對應的頁表項(需要編程)

這裏我們需要實現的是 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)];
}

1、請描述頁目錄項(Page Directory Entry)和頁表項(Page Table Entry)中每個組成部分的含義以及對 ucore 而言的潛在用處。

page_dir

從低到高,分別是:

  • P (Present) 位:表示該頁保存在物理內存中。
  • R (Read/Write) 位:表示該頁可讀可寫。
  • U (User) 位:表示該頁可以被任何權限用戶訪問。
  • W (Write Through) 位:表示 CPU 可以直寫回內存。
  • D (Cache Disable) 位:表示不需要被 CPU 緩存。
  • A (Access) 位:表示該頁被寫過。
  • S (Size) 位:表示一個頁 4MB 。
  • 9-11 位保留給 OS 使用。
  • 12-31 位指明 PTE 基質地址。

page_table

從低到高,分別是:

  • 0-3 位同 PDE。
  • C (Cache Disable) 位:同 PDE D 位。
  • A (Access) 位:同 PDE 。
  • D (Dirty) 位:表示該頁被寫過。
  • G (Global) 位:表示在 CR3 寄存器更新時無需刷新 TLB 中關於該頁的地址。
  • 9-11 位保留給 OS 使用。
  • 12-31 位指明物理頁基址。

因爲頁的映射是以物理頁面爲單位進行,所以頁面對應的物理地址總是按照 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 頁表起始物理地址/頁起始物理地址

2、如果ucore執行過程中訪問內存,出現了頁訪問異常,請問硬件要做哪些事情?

會進行換頁操作。首先 CPU 將產生頁訪問異常的線性地址放到 cr2 寄存器中,然後就是和普通的中斷一樣,保護現場,將寄存器的值壓入棧中,設置錯誤代碼 error_code,觸發 Page Fault 異常,然後壓入 error_code 中斷服務例程,將外存的數據換到內存中來,最後退出中斷,回到進入中斷前的狀態。

練習3:釋放某虛地址所在的頁並取消對應二級頁表項的映射(需要編程)

這裏主要是 page_remove_pte 的補全及完善。

思路主要就是先判斷該頁被引用的次數,如果只被引用了一次,那麼直接釋放掉這頁, 否則就刪掉二級頁表的該表項,即該頁的入口。

取消頁表映射過程如下:

  • 將物理頁的引用數目減一,如果變爲零,那麼釋放頁面;
  • 將頁目錄項清零;
  • 刷新TLB。

實現過程如下:

* 設計思路:
首先判斷一下 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
    }
}

運行結果如下:

make_qemu

1、數據結構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) 爲索引的那一項。

2、如果希望虛擬地址與物理地址相等,則需要如何修改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();

擴展練習Challenge

1、buddy system(夥伴系統)分配算法(需要編程)

buddy-system

初始化

在 Buddy System 中,空間塊之間的關係形成一顆完全二叉樹,對於一顆有着 n 葉子的完全二叉樹來說,所有節點的總數爲 。也就是說,如果 Buddy System 的可分配空間爲 n 頁的話,那麼就需要額外保存 2n-1 個節點信息。

初始化空閒鏈表

Buddy System 並不需要鏈表,但是爲了在調式的時候方便訪問所有空閒空間,還是將所有的空閒空間加入鏈表中。

確定分配空間大小

假設我們得到了大小爲 n 的空間,我們需要在此基礎上建立 Buddy System,經過初始化後,Buddy System 管理的頁數爲 ,那麼大小爲 n 的實際空間可能分爲兩個或者三個部分。

節點信息區:節點信息區可以用來保存每個節點對應子樹中可用空間的信息,用於在分配內存的時候便於檢查子樹中是否有足夠的空間來滿足請求大小。在 32 位操作系統中,最大頁數不會超過 4GB/4KB=1M,所有使用一個 32 位整數即可表示每個節點的信息。所以節點信息區的大小爲 字節,每頁大小爲 4KB,內存佔用按照頁面大小對齊,所以佔用 頁。

虛擬分配區:佔用 頁。

實際分配區:顯然實際可以得到的內存大小不大可能剛好等於節點信息區大小+分配空間區大小。如果節點信息區大小+分配空間區大小<=內存大小,那麼實際可以分配的區域就等於 頁。如果節點信息區大小+分配空間區大小>內存大小,那麼實際可以分配的區域就等於 頁。

作爲操作系統,自然希望實際使用的區域越大越好,不妨分類討論。

當內存小於等於512頁:此時無論如何節點信息都會佔用一頁,所以提高內存利率的方法就是將實際內存大小減一後向上取整(文中整數意爲2的冪)。

當內存大於512頁:不難證明,對於內存大小 \(n\) 來說,最佳虛擬分配區大小往往是 n 向下取整或者向上取整的數值,所以候選項也就是隻有兩個,所以可以先考慮向下取整。對於 中的數 ,向下取整可以得到 :

  • 時,顯然已經是最佳值;
  • 時,擴大虛擬分配區導致節點信息區增加卻沒有使得實際分配區增加,所以當期 m 還是最佳值;
  • 時, 可以擴大實際分配區。

初始化節點信息

虛擬分配區可能會大於實際分配區,所以一開始需要將虛擬分配區中沒有實際分配區對應的空間標記爲已經分配進行屏蔽。另當前區塊的虛擬空間大小爲 ,實際空間大小爲 ,屏蔽的過程如下:

  • 如果 ,將空間初始化爲一個空閒空間,屏蔽過程結束;
  • 如果 ,將空間初始化爲一個已分配空間,屏蔽過程結束;
  • 如果 ,將右半空間初始化爲已分配空間,更新 後繼續對左半空間進行操作;
  • 如果 ,將左半空間初始化爲空閒空間,更新 後繼續對左半空間進行操作。

以虛擬分配區 16 頁、實際分配區 14 頁爲例,初始化後如下:

init

分配過程

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,
};

2、任意大小的內存單元slub分配算法(需要編程)

實際上 Slub 分配算法是非常複雜的,需要考慮緩存對齊、NUMA 等非常多的問題,作爲實驗性質的操作系統就不考慮這些複雜因素了。簡化的 Slub 算法結合了 Slab 算法和 Slub 算法的部分特徵,使用了一些比較右技巧性的實現方法。具體的簡化爲:

  • Slab 大小爲一頁,不允許創建大對象倉庫
  • 複用Page數據結構,將 Slab 元數據保存在 Page 結構體中
數據結構

在操作系統中經常會用到大量相同的數據對象,例如互斥鎖、條件變量等等,同種數據對象的初始化方法、銷燬方法、佔用內存大小都是一樣的,如果操作系統能夠將所有的數據對象進行統一管理,可以提高內存利用率,同時也避免了反覆初始化對象的開銷。

倉庫

每種對象由倉庫(感覺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 大小爲一頁,所以數據對象和每頁對象數據不會超過 ,所以使用 16 位整數保存足夠。然後所有的倉庫鏈接成一個鏈表,方便進行遍歷。

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。

slab

對象數據不會超過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();
}

參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章