全部的實驗報告將會在 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/git
lab3
會依賴 lab1
和 lab2
,咱們須要把作的 lab1
和 lab2
的代碼填到 lab3
中缺失的位置上面。練習 0 就是一個工具的利用。這裏我使用的是 Linux
下的系統已預裝好的 Meld Diff Viewer
工具。和 lab2
操做流程同樣,咱們只須要將已經完成的 lab1
和 lab2
與待完成的 lab3
(因爲 lab2
是基於 lab1
基礎上完成的,因此這裏只須要導入 lab2
)分別導入進來,而後點擊 compare
就好了。github
而後軟件就會自動分析兩份代碼的不一樣,而後就一個個比較比較複製過去就好了,在軟件裏面是能夠支持打開對比複製了,點擊 Copy Right
便可。固然 bin
目錄和 obj
目錄下都是 make
生成的,就不用複製了,其餘須要修改的地方主要有如下四個文件,經過對比複製完成便可:算法
default_pmm.c pmm.c trap.c kdebug.c
本實驗要求完成 do_pgfault
函數,做用給未被映射的地址映射上物理頁。編程
具體而言,當啓動分頁機制之後,若是一條指令或數據的虛擬地址所對應的物理頁框不在內存中或者訪問的類型有錯誤(好比寫一個只讀頁或用戶態程序訪問內核態的數據等),就會發生頁錯誤異常。產生頁面異常的緣由主要有:數據結構
0
,即該線性地址與物理地址還沒有創建映射或者已經撤銷);Present
標誌位 =0
,好比在 swap
分區或磁盤文件上)P
標誌 =1
,好比企圖寫只讀頁面).當出現上面狀況之一,那麼就會產生頁面 page fault(#PF)
異常。產生異常的線性地址存儲在 CR2
中,而且將是 page fault
的產生類型保存在 error code
中。app
所以此函數是完成頁錯誤異常處理的主要函數,它根據 CPU
的控制寄存器 CR2
中獲取的頁錯誤異常的虛擬地址,以及根據 error code
的錯誤類型來查找次虛擬地址是否在某個 VMA
的地址範圍內,而且是否知足正確的讀寫權限。若是在此範圍內而且權限也正確,就認爲這是一次合法訪問,但沒有創建虛實對應關係,因此須要分配一個空閒的內存頁,並修改頁表完成虛地址到物理地址的映射,刷新 TLB
,而後調用 iret
中斷,返回並從新執行。若是該虛地址不在某 VMA
範圍內,這認爲是一個非法訪問。框架
那麼咱們的這個 do_pgfault
函數的思路就明顯了。do_pgfault()
函數從 CR2
寄存器中獲取頁錯誤異常的虛擬地址,根據 error code
來查找這個虛擬地址是否在某一個 VMA
的地址範圍內,那麼就給它分配一個物理頁。ide
page_fault 函數不知道哪些是「合法」的虛擬頁,緣由是 ucore
還缺乏必定的數據結構來描述這種不在物理內存中的「合法」虛擬頁。爲此 ucore
經過創建 mm_struct
和 vma_struct
數據結構,描述了 ucore
模擬應用程序運行所需的合法內存空間。當訪問內存產生 page fault 異常時,可得到訪問的內存的方式(讀或寫)以及具體的虛擬內存地址,這樣 ucore
就能夠查詢此地址,看是否屬於 vma_struct
數據結構中描述的合法地址範圍中,若是在,則可根據具體狀況進行請求調頁/頁換入換出處理;若是不在,則報錯。函數
虛擬地址空間和物理地址空間的示意圖以下圖所示:工具
這裏的 VMA 是描述應用程序對虛擬內存「需求」的變量,以下:
struct vma_struct { struct mm_struct *vm_mm; //指向一個比 vma_struct 更高的抽象層次的數據結構 mm_struct uintptr_t vm_start; //vma 的開始地址 uintptr_t vm_end; // vma 的結束地址 uint32_t vm_flags; // 虛擬內存空間的屬性 list_entry_t list_link; //雙向鏈表,按照從小到大的順序把虛擬內存空間連接起來 };
其中,各變量的屬性以下:
vm_start
和 vm_end
描述的是一個合理的地址空間範圍(即嚴格確保 vm_start < vm_end
的關係)
list_link
是一個雙向鏈表,按照從小到大的順序把一系列用 vma_struct
表示的虛擬內存空間連接起來,而且還要求這些鏈起來的 vma_struct
應該是不相交的,即 vma
之間的地址空間無交集。
vm_flags
表示了這個虛擬內存空間的屬性,目前的屬性包括:
define VM_READ 0x00000001 //只讀
define VM_WRITE 0x00000002 //可讀寫
define VM_EXEC 0x00000004 //可執行
vm_mm
是一個指針,指向一個比 vma_struct
更高的抽象層次的數據結構 mm_struct
。
struct mm_struct { list_entry_t mmap_list; //雙向鏈表頭,連接了全部屬於同一頁目錄表的虛擬內存空間 struct vma_struct *mmap_cache; //指向當前正在使用的虛擬內存空間 pde_t *pgdir; //指向的就是 mm_struct數據結構所維護的頁表 int map_count; //記錄 mmap_list 裏面連接的 vma_struct 的個數 void *sm_priv; //指向用來連接記錄頁訪問狀況的鏈表頭 };
其中,各變量的屬性以下:
mmap_list
是雙向鏈表頭,連接了全部屬於同一頁目錄表的虛擬內存空間。mmap_cache
是指向當前正在使用的虛擬內存空間,因爲操做系統執行的「局部性」原理,當前正在用到的虛擬內存空間在接下來的操做中可能還會用到,這時就不須要查鏈表,而是直接使用此指針就可找到下一次要用到的虛擬內存空間。pgdir
所指向的就是 mm_struct
數據結構所維護的頁表。經過訪問 pgdir
能夠查找某虛擬地址對應的頁表項是否存在以及頁表項的屬性等。map_count
記錄 mmap_list
裏面連接的 vma_struct
的個數。sm_priv
指向用來連接記錄頁訪問狀況的鏈表頭,這創建了 mm_struct
和後續要講到的 swap_manager
之間的聯繫。其結構關係如圖所示:
do_pgfault
的調用關係以下圖所示:
實現過程以下:(包含了練習 1 以及 練習 2 的部分實現)
int do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr) { int ret = -E_INVAL; //try to find a vma which include addr struct vma_struct *vma = find_vma(mm, addr);//查詢 vma pgfault_num++; //If the addr is in the range of a mm's vma? if (vma == NULL || vma->vm_start > addr) { cprintf("not valid addr %x, and can not find it in vma\n", addr); goto failed; } //check the error_code switch (error_code & 3) {//錯誤處理 default: /* error code flag : default is 3 ( W/R=1, P=1): write, present */ case 2: /* error code flag : (W/R=1, P=0): write, not present */ if (!(vma->vm_flags & VM_WRITE)) { cprintf("do_pgfault failed: error code flag = write AND not present, but the addr's vma cannot write\n"); goto failed; } break; case 1: /* error code flag : (W/R=0, P=1): read, present */ cprintf("do_pgfault failed: error code flag = read AND present\n"); goto failed; case 0: /* error code flag : (W/R=0, P=0): read, not present */ if (!(vma->vm_flags & (VM_READ | VM_EXEC))) { cprintf("do_pgfault failed: error code flag = read AND not present, but the addr's vma cannot read or exec\n"); goto failed; } } /* IF (write an existed addr ) OR * (write an non_existed addr && addr is writable) OR * (read an non_existed addr && addr is readable) * THEN * continue process */ uint32_t perm = PTE_U; if (vma->vm_flags & VM_WRITE) { perm |= PTE_W; } addr = ROUNDDOWN(addr, PGSIZE); ret = -E_NO_MEM; pte_t *ptep=NULL; /*LAB3 EXERCISE 1: YOUR CODE * Maybe you want help comment, BELOW comments can help you finish the code * * Some Useful MACROs and DEFINEs, you can use them in below implementation. * MACROs or Functions: * get_pte : get an pte and return the kernel virtual address of this pte for la * if the PT contians this pte didn't exist, alloc a page for PT (notice the 3th parameter '1') * pgdir_alloc_page : call alloc_page & page_insert functions to allocate a page size memory & setup * an addr map pa<--->la with linear address la and the PDT pgdir * DEFINES: * VM_WRITE : If vma->vm_flags & VM_WRITE == 1/0, then the vma is writable/non writable * PTE_W 0x002 // page table/directory entry flags bit : Writeable * PTE_U 0x004 // page table/directory entry flags bit : User can access * VARIABLES: * mm->pgdir : the PDT of these vma * */ -------------------------------------------------------------------------------------------- * 設計思路: 首先檢查頁表中是否有相應的表項,若是表項爲空,那麼說明沒有映射過; 而後使用 pgdir_alloc_page 獲取一個物理頁,同時進行錯誤檢查便可。 /*LAB3 EXERCISE 1: YOUR CODE*/ // try to find a pte, if pte's PT(Page Table) isn't existed, then create a PT. // (notice the 3th parameter '1') if ((ptep = get_pte(mm->pgdir, addr, 1)) == NULL) { cprintf("get_pte in do_pgfault failed\n"); goto failed; } //若是頁表不存在,嘗試分配一空閒頁,匹配物理地址與邏輯地址,創建對應關係 if (*ptep == 0) { // if the phy addr isn't exist, then alloc a page & map the phy addr with logical addr if (pgdir_alloc_page(mm->pgdir, addr, perm) == NULL) { //失敗內存不夠退出 cprintf("pgdir_alloc_page in do_pgfault failed\n"); goto failed; } } -------------------------------------------------------------------------------------------- /*LAB3 EXERCISE 2: YOUR CODE * Now we think this pte is a swap entry, we should load data from disk to a page with phy addr, * and map the phy addr with logical addr, trigger swap manager to record the access situation of this page. * * Some Useful MACROs and DEFINEs, you can use them in below implementation. * MACROs or Functions: * swap_in(mm, addr, &page) : alloc a memory page, then according to the swap entry in PTE for addr, * find the addr of disk page, read the content of disk page into this memroy page * page_insert : build the map of phy addr of an Page with the linear addr la * swap_map_swappable : set the page swappable */ -------------------------------------------------------------------------------------------- * 設計思路: 若是 PTE 存在,那麼說明這一頁已經映射過了可是被保存在磁盤中,須要將這一頁內存交換出來: 1.調用 swap_in 將內存頁從磁盤中載入內存; 2.調用 page_insert 創建物理地址與線性地址之間的映射; 3.設置頁對應的虛擬地址,方便交換出內存時將正確的內存數據保存在正確的磁盤位置; 4.調用 swap_map_swappable 將物理頁框加入 FIFO。 /*LAB3 EXERCISE 2: YOUR CODE*/ //頁表項非空,嘗試換入頁面 else { // if this pte is a swap entry, then load data from disk to a page with phy addr // and call page_insert to map the phy addr with logical addr if(swap_init_ok) { struct Page *page=NULL;//根據 mm 結構和 addr 地址,嘗試將硬盤中的內容換入至 page 中 if ((ret = swap_in(mm, addr, &page)) != 0) { cprintf("swap_in in do_pgfault failed\n"); goto failed; } page_insert(mm->pgdir, page, addr, perm);//創建虛擬地址和物理地址之間的對應關係 swap_map_swappable(mm, addr, page, 1);//將此頁面設置爲可交換的 page->pra_vaddr = addr; } else { cprintf("no swap_init_ok but ptep is %x, failed\n",*ptep); goto failed; } } ret = 0; failed: return ret; }
請描述頁目錄項(Page Directory Entry)和頁表項(Page Table Entry)中組成部分對 ucore 實現頁替換算法的潛在用處。
表項中 PTE_A
表示內存頁是否被訪問過,PTE_D
表示內存頁是否被修改過,藉助着兩位標誌位能夠實現 Enhanced Clock 算法。
若是 ucore 的缺頁服務例程在執行過程當中訪問內存,出現了頁訪問異常,請問硬件要作哪些事情?
若是出現了頁訪問異常,那麼硬件將引起頁訪問異常的地址將被保存在 cr2
寄存器中,設置錯誤代碼,而後觸發 Page Fault
異常。
本實驗要求完成 do_pgfault
函數,而且在實現 FIFO
算法的 swap_fifo.c
中完成 map_swappable
和 swap_out_victim
函數,經過對 swap
的測試。根據練習 1,當頁錯誤異常發生時,有多是由於頁面保存在 swap
區或者磁盤文件上形成的,因此咱們須要利用頁面替換算法解決這個問題。
頁面替換主要分爲兩個方面,頁面換入和頁面換出。
do_pgfault()
函數實現;swap_out_vistim()
函數實現。在換入時,須要先檢查產生訪問異常的地址是否屬於某個 VMA
表示的合法虛擬地址,而且保存在硬盤的 swap
文件中(即對應的 PTE
的高 24
位不爲 0
,而最低位爲 0
),則是執行頁換入的時機,將調用 swap_in
函數完成頁面換入。
在換出時,採起的是消極的換出策略,是在調用 alloc_pages
函數獲取空閒頁時,此函數若是發現沒法從物理內存頁分配器(好比 First Fit
)得到空閒頁,就會進一步調用 swap_out
函數 換出某頁,實現一種消極的換出策略。
因爲頁面換入操做已經在練習 1 中實現了,因此這裏咱們主要談頁面換出。
爲了實現各類頁替換算法,咱們須要設計一個頁替換算法的類框架 swap_manager
:
struct swap_manager { const char *name; /* Global initialization for the swap manager */ int (*init) (void); /* Initialize the priv data inside mm_struct */ int (*init_mm) (struct mm_struct *mm); /* Called when tick interrupt occured */ int (*tick_event) (struct mm_struct *mm); /* Called when map a swappable page into the mm_struct */ int (*map_swappable) (struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in); /* When a page is marked as shared, this routine is called to * delete the addr entry from the swap manager */ int (*set_unswappable) (struct mm_struct *mm, uintptr_t addr); /* Try to swap out a page, return then victim */ int (*swap_out_victim) (struct mm_struct *mm, struct Page **ptr_page, int in_tick); /* check the page relpacement algorithm */ int (*check_swap)(void); };
這裏關鍵的兩個函數指針是 map_swappable
和 swap_out_vistim
,map_swappable
函數用於記錄頁訪問狀況相關屬性,swap_out_vistim
函數用於挑選須要換出的頁。顯然 swap_out_vistim
函數依賴於 map_swappable
函數記錄的頁訪問狀況。tick_event
函數指針也很重要,結合定時產生的中斷,能夠實現一種積極的換頁策略。
FIFO
替換算法會維護一個隊列,隊列按照頁面調用的次序排列,越早被加載到內存的頁面會越早被換出。
因爲 FIFO 基於雙向鏈表實現,因此只須要將元素插入到頭節點以前。
實現過程以下:
/* * (3)_fifo_map_swappable: According FIFO PRA, we should link the most recent arrival page at the back of pra_list_head qeueue */ // 做用: 將最近被用到的頁面添加到算法所維護的次序隊列。 static int _fifo_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in) { list_entry_t *head=(list_entry_t*) mm->sm_priv; list_entry_t *entry=&(page->pra_page_link); assert(entry != NULL && head != NULL); //record the page access situlation /*LAB3 EXERCISE 2: YOUR CODE*/ //(1)link the most recent arrival page at the back of the pra_list_head qeueue. list_add(head, entry);//將最近用到的頁面添加到次序的隊尾 return 0; }
將雙向鏈表中頭部節點後面的第一個節點刪除,返回對應的頁地址(虛擬地址)。
實現過程以下:
/* * (4)_fifo_swap_out_victim: According FIFO PRA, we should unlink the earliest arrival page in front of pra_list_head qeueue, * then assign the value of *ptr_page to the addr of this page. */ // 做用: 用來查詢哪一個頁面須要被換出。 static int _fifo_swap_out_victim(struct mm_struct *mm, struct Page ** ptr_page, int in_tick) { list_entry_t *head=(list_entry_t*) mm->sm_priv; assert(head != NULL); assert(in_tick==0); /* Select the victim */ /*LAB3 EXERCISE 2: YOUR CODE*/ //(1) unlink the earliest arrival page in front of pra_list_head qeueue //(2) assign the value of *ptr_page to the addr of this page /* Select the tail */ list_entry_t *le = head->prev;//指出須要被換出的頁 assert(head!=le); struct Page *p = le2page(le, pra_page_link);//le2page 宏能夠根據鏈表元素,得到對應 page 的指針p list_del(le);//將進來最先的頁面從隊列中刪除 assert(p !=NULL); *ptr_page = p;//將這一頁的地址存儲在ptr_page中 return 0; }
最終運行結果以下:
若是要在ucore上實現"extended clock 頁替換算法"請給你的設計方案,現有的 swap_manager 框架是否足以支持在 ucore 中實現此算法?若是是,請給你的設計方案。若是不是,請給出你的新的擴展和基此擴展的設計方案。並須要回答以下問題
- 須要被換出的頁的特徵是什麼?
- 在ucore中如何判斷具備這樣特徵的頁?
- 什麼時候進行換入和換出操做?
對於每一個頁面都有兩個標誌位,分別爲使用位和修改位,記爲<使用,修改>
。換出頁的使用位必須爲0,而且算法優先考慮換出修改位爲零的頁面。
當內存頁被訪問後,MMU 將在對應的頁表項的 PTE_A
這一位設爲1;
當內存頁被修改後,MMU 將在對應的頁表項的 PTE_D
這一位設爲1。
當保存在磁盤中的內存須要被訪問時,須要進行換入操做;
當位於物理頁框中的內存被頁面替換算法選擇時,須要進行換出操做。
Enhanced Clock 算法須要一個環形鏈表和一個指針,這個能夠在原有的雙向鏈表基礎上實現。爲了方便進行循環訪問,將原先的頭部哨兵刪除,這樣全部的頁面造成一個環形鏈表。指向環形鏈表指針也就是 Enhanced Clock 算法中指向下個頁面的指針。
若是環形鏈表爲空,那麼這個頁面就是整個鏈表,將指針指向這個頁面。不然,只須要將頁面插入指針指向的頁面以前便可。
Enhanced Clock 算法最多須要遍歷環形鏈表四次(規定標記爲<訪問,修改>
):
<0,0>
的頁面;<0,1>
,並將訪問過的頁面的訪問位清零;<0,0>
的頁面;<0,1>
的頁面;將PTE中的PTE_A清除後,須要調用
tlb_invalidate
刷新TLB,不然當頁面被再次訪問的時候,PTE中的PTE_A不會被設置。
實現以下:
// swap_clock.h #ifndef __KERN_MM_SWAP_CLOCK_H__ #define __KERN_MM_SWAP_CLOCK_H__ #include <swap.h> extern struct swap_manager swap_manager_clock; #endif -------------------------------------------------------------------------------------------- // swap_clock.c #include <x86.h> #include <stdio.h> #include <string.h> #include <swap.h> #include <swap_clock.h> #include <list.h> #define GET_LIST_ENTRY_PTE(pgdir, le) (get_pte((pgdir), le2page((le), pra_page_link)->pra_vaddr, 0)) #define GET_DIRTY_FLAG(pgdir, le) (*GET_LIST_ENTRY_PTE((pgdir), (le)) & PTE_D) #define GET_ACCESSED_FLAG(pgdir, le) (*GET_LIST_ENTRY_PTE((pgdir), (le)) & PTE_A) #define CLEAR_ACCESSED_FLAG(pgdir, le) do {\ struct Page *page = le2page((le), pra_page_link);\ pte_t *ptep = get_pte((pgdir), page->pra_vaddr, 0);\ *ptep = *ptep & ~PTE_A;\ tlb_invalidate((pgdir), page->pra_vaddr);\ } while (0) static int _clock_init_mm(struct mm_struct *mm) { mm->sm_priv = NULL; return 0; } static int _clock_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in) { list_entry_t *head=(list_entry_t*) mm->sm_priv; list_entry_t *entry=&(page->pra_page_link); assert(entry != NULL); // Insert before pointer if (head == NULL) { list_init(entry); mm->sm_priv = entry; } else { list_add_before(head, entry); } return 0; } static int _clock_swap_out_victim(struct mm_struct *mm, struct Page ** ptr_page, int in_tick) { list_entry_t *head=(list_entry_t*) mm->sm_priv; assert(head != NULL); assert(in_tick==0); list_entry_t *selected = NULL, *p = head; // Search <0,0> do { if (GET_ACCESSED_FLAG(mm->pgdir, p) == 0 && GET_DIRTY_FLAG(mm->pgdir, p) == 0) { selected = p; break; } p = list_next(p); } while (p != head); // Search <0,1> and set 'accessed' to 0 if (selected == NULL) do { if (GET_ACCESSED_FLAG(mm->pgdir, p) == 0 && GET_DIRTY_FLAG(mm->pgdir, p)) { selected = p; break; } CLEAR_ACCESSED_FLAG(mm->pgdir, p); p = list_next(p); } while (p != head); // Search <0,0> again if (selected == NULL) do { if (GET_ACCESSED_FLAG(mm->pgdir, p) == 0 && GET_DIRTY_FLAG(mm->pgdir, p) == 0) { selected = p; break; } p = list_next(p); } while (p != head); // Search <0,1> again if (selected == NULL) do { if (GET_ACCESSED_FLAG(mm->pgdir, p) == 0 && GET_DIRTY_FLAG(mm->pgdir, p)) { selected = p; break; } p = list_next(p); } while (p != head); // Remove pointed element head = selected; if (list_empty(head)) { mm->sm_priv = NULL; } else { mm->sm_priv = list_next(head); list_del(head); } *ptr_page = le2page(head, pra_page_link); return 0; } static int _clock_check_swap(void) { cprintf("write Virt Page c in fifo_check_swap\n"); *(unsigned char *)0x3000 = 0x0c; assert(pgfault_num==4); cprintf("write Virt Page a in fifo_check_swap\n"); *(unsigned char *)0x1000 = 0x0a; assert(pgfault_num==4); cprintf("write Virt Page d in fifo_check_swap\n"); *(unsigned char *)0x4000 = 0x0d; assert(pgfault_num==4); cprintf("write Virt Page b in fifo_check_swap\n"); *(unsigned char *)0x2000 = 0x0b; assert(pgfault_num==4); cprintf("write Virt Page e in fifo_check_swap\n"); *(unsigned char *)0x5000 = 0x0e; assert(pgfault_num==5); cprintf("write Virt Page b in fifo_check_swap\n"); *(unsigned char *)0x2000 = 0x0b; assert(pgfault_num==5); cprintf("write Virt Page a in fifo_check_swap\n"); *(unsigned char *)0x1000 = 0x0a; assert(pgfault_num==6); cprintf("write Virt Page b in fifo_check_swap\n"); *(unsigned char *)0x2000 = 0x0b; assert(pgfault_num==6); cprintf("write Virt Page c in fifo_check_swap\n"); *(unsigned char *)0x3000 = 0x0c; assert(pgfault_num==7); cprintf("write Virt Page d in fifo_check_swap\n"); *(unsigned char *)0x4000 = 0x0d; assert(pgfault_num==8); cprintf("write Virt Page e in fifo_check_swap\n"); *(unsigned char *)0x5000 = 0x0e; assert(pgfault_num==9); cprintf("write Virt Page a in fifo_check_swap\n"); assert(*(unsigned char *)0x1000 == 0x0a); *(unsigned char *)0x1000 = 0x0a; assert(pgfault_num==9); cprintf("read Virt Page b in fifo_check_swap\n"); assert(*(unsigned char *)0x2000 == 0x0b); assert(pgfault_num==10); cprintf("read Virt Page c in fifo_check_swap\n"); assert(*(unsigned char *)0x3000 == 0x0c); assert(pgfault_num==11); cprintf("read Virt Page a in fifo_check_swap\n"); assert(*(unsigned char *)0x1000 == 0x0a); assert(pgfault_num==12); cprintf("read Virt Page d in fifo_check_swap\n"); assert(*(unsigned char *)0x4000 == 0x0d); assert(pgfault_num==13); cprintf("read Virt Page b in fifo_check_swap\n"); *(unsigned char *)0x1000 = 0x0a; assert(*(unsigned char *)0x3000 == 0x0c); assert(*(unsigned char *)0x4000 == 0x0d); assert(*(unsigned char *)0x5000 == 0x0e); assert(*(unsigned char *)0x2000 == 0x0b); assert(pgfault_num==14); return 0; } static int _clock_init(void) { return 0; } static int _clock_set_unswappable(struct mm_struct *mm, uintptr_t addr) { return 0; } _clock_tick_event(struct mm_struct *mm) { return 0; } struct swap_manager swap_manager_clock = { .name = "clock swap manager", .init = &_clock_init, .init_mm = &_clock_init_mm, .tick_event = &_clock_tick_event, .map_swappable = &_clock_map_swappable, .set_unswappable = &_clock_set_unswappable, .swap_out_victim = &_clock_swap_out_victim, .check_swap = &_clock_check_swap, };
注:待完成