zoukankan      html  css  js  c++  java
  • 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.
    ", 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.
    ", 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:	 mips vm init success
    ");
    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
    ",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)

  • 相关阅读:
    机器博弈中的数据结构与基本方法(三)-----博弈树的搜索之九宫重排问题
    机器博弈中的数据结构与基本方法(二)-----递归及回溯法实例
    机器博弈中的数据结构与基本方法(一)-----总结
    浅析基本AI五子棋算法
    数据结构算法C语言实现(三十一)--- 8.3边界标识法
    数据结构算法C语言实现(三十)--- 7.6最短路径
    数据结构算法C语言实现(二十九)--- 7.5有向无环图及其应用
    数据结构算法C语言实现(二十八)--- 7.4图的连通性问题
    数据结构算法C语言实现(二十七)--- 7.2图的遍历
    数据结构算法C语言实现(二十六)--- 7.2图的存储结构
  • 原文地址:https://www.cnblogs.com/CindyZhou/p/12851976.html
Copyright © 2011-2022 走看看