BUAA_OS lab2 難點梳理


BUAA_OS lab2 難點梳理

實驗重點

所列出的實驗重點為筆者在進行lab2過程中認為需要深刻理解的部分。

  1. 進行內存訪問的流程

  2. 熟悉mips內存映射布局,即理解mmu.h內圖

  3. 二級頁表的理解和實現

以下將參考指導書邏輯,對於重難點進行梳理。

 

內存訪問

首先,簡易梳理內存訪問流程。

  1. TLB根據虛擬地址查找

  2. 若存在,在cache中查找;若不存在,按照頁表查詢,再查cache,更新tlb

  3. 若cache命中則ok;若未命中,進行頁面替換

     

內存布局及初始化步驟的理解

lab2主要涉及的內存布局圖如下:

  • kuseg:用戶態可用地址,需要mmu進行地址轉換

  • kseg0:內核地址,轉換不需要mmu,只需要將最高位清0

與內存布局密切相關的,就是初始化部分的各個函數,包括創建二級頁表的部分。

我們以mips_vm_init()展開理解初始化的各個步驟。

 1  void mips_vm_init()
 2  {
 3      extern char end[];
 4      extern int mCONTEXT;
 5      extern struct Env *envs;
 6  7      Pde *pgdir;
 8      u_int n;
 9 10      /* Step 1: Allocate a page for page directory(first level page table). */
11      pgdir = alloc(BY2PG, BY2PG, 1);
12      printf("to memory %x for struct page directory.\n", freemem);
13      mCONTEXT = (int)pgdir;
14 15      boot_pgdir = pgdir;
16 17      /* Step 2: Allocate proper size of physical memory for global array `pages`,
18       * for physical memory management. Then, map virtual address `UPAGES` to
19       * physical address `pages` allocated before. For consideration of alignment,
20       * you should round up the memory size before map. */
21      pages = (struct Page *)alloc(npage * sizeof(struct Page), BY2PG, 1);
22      printf("to memory %x for struct Pages.\n", freemem);
23      n = ROUND(npage * sizeof(struct Page), BY2PG);
24      boot_map_segment(pgdir, UPAGES, n, PADDR(pages), PTE_R);;
25      /* Step 3, Allocate proper size of physical memory for global array `envs`,
26       * for process management. Then map the physical address to `UENVS`. */
27      envs = (struct Env *)alloc(NENV * sizeof(struct Env), BY2PG, 1);
28      n = ROUND(NENV * sizeof(struct Env), BY2PG);
29      boot_map_segment(pgdir, UENVS, n, PADDR(envs), PTE_R);
30 31      printf("pmap.c:\t mips vm init success\n");
32  }
mips_vm_init
  • 首先,調用alloc函數為pgdir開出一塊空間。在此需要理解,alloc函數的本質就是將freemem上移,以表示預留空間。在執行完這一條alloc后,freemem的值由end[](0x80400000)增加為0x80401000

  • 然后,調用alloc函數為pages開出一塊空間。需要注意的是,pages是用來記錄各物理頁信息的Page結構體數組,可以根據某Page在pages中的偏移量,間接求出對應的物理頁地址。此時,freemem再次增加。與pgdir不同的是,緊接着又調用了boot_map_segment()函數。其作用下文中再敘述。

  • 最后,與第二步相似,為envs先alloc再map。

 

接下來,我們看一下boot_map_segment()是用來干啥的。

 1  void boot_map_segment(Pde *pgdir, u_long va, u_long size, u_long pa, int perm)
 2  {
 3      int i, va_temp;
 4      Pte *pgtable_entry;
 5      
 6      /* Step 1: Check if `size` is a multiple of BY2PG. */
 7      
 8      if(size%BY2PG!=0){
 9          return;
10      }
11      
12       /* Step 2: Map virtual address space to physical address. */
13      /* Hint: Use `boot_pgdir_walk` to get the page table entry of virtual address `va`. */
14      
15      for(i=0;i<size;i+=BY2PG){
16          pgtable_entry=boot_pgdir_walk(pgdir,va+i,1);
17          *pgtable_entry=(pa+i)|perm|PTE_V;
18      }
19      return;
20  }
boot_map_segment

可以看出,boot_map_segment()的作用就是將[va, va+size)的虛擬地址和[pa, pa+size)的物理地址建立映射關系。通俗來講,就是將虛擬地址va對應的頁表項寫入需要對應的pa的值,並設置標志位。

具體實現為,通過boot_pgdir_walk()獲取地址為va+i對應的頁表項,然后修改它的值。

那么自然而然,我們再來看一下boot_pgdir_walk()是怎么找到va+i對應的頁表項地址的。

 1 static Pte *boot_pgdir_walk(Pde *pgdir, u_long va, int create)
 2  {
 3  4      Pde *pgdir_entryp;
 5      Pte *pgtable, *pgtable_entry;
 6      
 7      /* Step 1: Get the corresponding page directory entry and page table. */
 8      /* Hint: Use KADDR and PTE_ADDR to get the page table from page directory
 9       * entry value. */
10      pgdir_entryp = pgdir+PDX(va);               //獲取一級頁表項的虛擬地址
11      pgtable=KADDR(PTE_ADDR(*pgdir_entryp));     //獲取二級頁表入口的虛擬地址
12      /* Step 2: If the corresponding page table is not exist and parameter `create`
13       * is set, create one. And set the correct permission bits for this new page
14       * table. */
15      if((*pgdir_entryp & PTE_V)==0 && create){   //如果沒有二級頁表,且需要創建
16          pgtable = alloc(BY2PG,BY2PG,1);         //創建二級頁表
17          *pgdir_entryp = PADDR(pgtable)|PTE_V;   //將指向該二級頁表的一級頁表項的值設置為其物理地址
18      }
19      /* Step 3: Get the page table entry for `va`, and return it. */
20      pgtable_entry=pgtable+PTX(va);              //返回指向對應二級頁表項地址的指針
21      return pgtable_entry;
22 23  }
boot_pgdir_walk

該函數的具體行為已體現在注釋中了,不再贅述。

看到這個boot_pgdir_walk()函數在尋找二級頁表項的時候,可能會感覺被虛擬和物理地址的轉換繞暈了,那么就來捋一下它究竟是根據什么地址找到的頁表項吧。

首先需要明確,在想要訪問頁表的時候,無論是一級還是二級,都用的虛擬地址;而一級頁表中存的二級頁表地址和二級頁表中存的頁地址,都是物理地址。

明確這一點之后,以下這句就不難理解了。pgdir中存的是物理地址,但需要轉化成虛擬地址訪問。其他類似。

 pgtable=KADDR(PTE_ADDR(*pgdir_entryp));

另外,我們會發現,在需要訪問的二級頁表不存在時,同樣調用了alloc,上移freemem,為新頁開出空間。這是因為,我們采用的二級頁表是動態的,需要哪個就裝入哪個,而不是將所有二級頁表都放入內存,因為這樣太占空間了。

到此位置,初始化部分就完成一大半了,這時候只需要再調用page_init()函數,將此時freemem以下的部分都設置p->pp_ref=1,即該物理頁被使用了。因此,根據freemem上移的順序,物理內存的最底端為pgdir,其次為pages,envs,后來alloc的頁等等。

 

頁面置換

在完成初始化之后,進行之后的頁面插入、刪除、分配、置換就變得容易多了。接下來,就以page_insert()函數來梳理一下后期相關的頁面操作。

 1 int
 2  page_insert(Pde *pgdir, struct Page *pp, u_long va, u_int perm)
 3  {
 4      u_int PERM;
 5      Pte *pgtable_entry;
 6      PERM = perm | PTE_V;
 7      /* Step 1: Get corresponding page table entry. */
 8      pgdir_walk(pgdir, va, 0, &pgtable_entry);
 9 10      if (pgtable_entry!=0 &&(*pgtable_entry&PTE_V)!= 0) {
11          if (pa2page(*pgtable_entry) != pp) {
12              page_remove(pgdir, va);
13          } else  {
14              tlb_invalidate(pgdir, va);
15              *pgtable_entry = (page2pa(pp) | PERM);
16              return 0;
17          }
18      }
19 20      /* Step 2: Update TLB. */
21 22      /* hint: use tlb_invalidate function */
23      tlb_invalidate(pgdir,va);
24 25      /* Step 3: Do check, re-get page table entry to validate the insertion. */
26 27      int x = pgdir_walk(pgdir, va, 1, &pgtable_entry);
28      /* Step 3.1 Check if the page can be insert, if can鈥檛 return -E_NO_MEM */
29      if(x==-E_NO_MEM){
30          return -E_NO_MEM;
31      }
32  //  printf("0x%x\n",PTE_ADDR(pgdir[0]));
33      /* Step 3.2 Insert page and increment the pp_ref */
34      *pgtable_entry=(page2pa(pp)|PERM);
35      pp->pp_ref+=1;
36      return 0;
37  }
page_insert

第一步是使用pgdir_walk()函數,獲取va所對應的二級頁表項。由此看來,pgdir_walk()與之前初始化部分提到的boot_pgdir_walk()作用基本相同呢。然不同之處在於,在調用page_insert()時,內存初始化部分已經完成,空閑頁表已經使用page_free_list串起來了,因此再分配新頁面的時候,直接取出空頁即可。

之后的步驟,就是對內存訪問的具體步驟的實現了。

 

1 . . 感言

至此,lab2的部分基本就結束了。回想完成lab2的時候,腦子還是一團漿糊,對於許多操作都很不理解。如今回頭寫總結,才發現主要就是對於初始化部分的具體行為理解不清。

另外,lab2對queue.h部分的操作不涉及理解難度,就是指針復(chong)習(xue),注意指針別飛了,就沒有大問題。

(代碼倉庫位於右上角Github)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM