OS_lab2课下实验随笔

LAB2-1

我们的操作系统结构:

/*
 o     4G ----------->  +----------------------------+------------0x100000000
 o                      |       ...                  |  kseg3
 o                      +----------------------------+------------0xe000 0000
 o                      |       ...                  |  kseg2
 o                      +----------------------------+------------0xc000 0000
 o                      |   Interrupts & Exception   |  kseg1
 o                      +----------------------------+------------0xa000 0000
 o                      |      Invalid memory        |   /|\
 o                      +----------------------------+----|-------Physics Memory Max
 o                      |       ...                  |  kseg0
 o  VPT,KSTACKTOP-----> +----------------------------+----|-------0x8040 0000-------end
 o                      |       Kernel Stack         |    | KSTKSIZE            /|\
 o                      +----------------------------+----|------                |
 o                      |       Kernel Text          |    |                    PDMAP
 o      KERNBASE -----> +----------------------------+----|-------0x8001 0000    |
 o                      |   Interrupts & Exception   |   \|/                    \|/
 o      ULIM     -----> +----------------------------+------------0x8000 0000-------
 o                      |         User VPT           |     PDMAP                /|\
 o      UVPT     -----> +----------------------------+------------0x7fc0 0000    |
 o                      |         PAGES              |     PDMAP                 |
 o      UPAGES   -----> +----------------------------+------------0x7f80 0000    |
 o                      |         ENVS               |     PDMAP                 |
 o  UTOP,UENVS   -----> +----------------------------+------------0x7f40 0000    |
 o  UXSTACKTOP -/       |     user exception stack   |     BY2PG                 |
 o                      +----------------------------+------------0x7f3f f000    |
 o                      |       Invalid memory       |     BY2PG                 |
 o      USTACKTOP ----> +----------------------------+------------0x7f3f e000    |
 o                      |     normal user stack      |     BY2PG                 |
 o                      +----------------------------+------------0x7f3f d000    |
 a                      |                            |                           |
 a                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~                           |
 a                      .                            .                           |
 a                      .                            .                         kuseg
 a                      .                            .                           |
 a                      |~~~~~~~~~~~~~~~~~~~~~~~~~~~~|                           |
 a                      |                            |                           |
 o       UTEXT   -----> +----------------------------+                           |
 o                      |                            |     2 * PDMAP            \|/
 a     0 ------------>  +----------------------------+ -----------------------------
 o
*/

MOS 中 维护 npage 个内存控制块,也就是 Page 结构体。每一个内存控制块对应一页的物理内存,注意并不是这个结构体就是对应的物理内存,而是用这个结构体来管理这块内存的分配。

//获取Page pp的页号
page2ppn(struct Page *pp)
{
        return pp - pages;
}

//获取Page pp的物理实地址
static inline u_long
page2pa(struct Page *pp)
{
        return page2ppn(pp) << PGSHIFT;
}

//获取物理页地址为pa的Page pp地址
static inline struct Page *
pa2page(u_long pa)
{
        if (PPN(pa) >= npage) {
                panic("pa2page called with invalid pa: %x", pa);
        }

        return &pages[PPN(pa)];
}

//获取Page pp的虚地址
static inline u_long
page2kva(struct Page *pp)
{
        return KADDR(page2pa(pp));
}

// page number field of address
#define PPN(va)         (((u_long)(va))>>12)
#define VPN(va)         PPN(va)

// translates from kernel virtual address to physical address.
#define PADDR(kva)                                              \
        ({                                                              \
                u_long a = (u_long) (kva);                              \
                if (a < ULIM)                                   \
                        panic("PADDR called with invalid kva %08lx", a);\
                a - ULIM;                                               \
        })

// translates from physical address to kernel virtual address.
#define KADDR(pa)                                               \
        ({                                                              \
                u_long ppn = PPN(pa);                                   \
                if (ppn >= npage)                                       \
                        panic("KADDR called with invalid pa %08lx", (u_long)pa);\
                (pa) + ULIM;                                    \
        })

链表宏的定义位于 include/queue.h,其实现的是 双向链表,下面将对一些主要的宏进行解释:

  • LIST_HEAD(name, type),创建一个名称为 name 链表的头部结构体, 包含一个指向 type 类型结构体的指针,这个指针可以指向链表的首个元素。
#define LIST_HEAD(name, type)                                                   \
        struct name {                                                           \
                struct type *lh_first;  /* first element */                     \
        }

  • LIST_ENTRY(type),作为一个特殊的「类型」出现,它的本质是一个链表项,包括指向下一个元素的指针 le_next,以及指向前一个元素链表项 le_next 的指针 le_prevle_prev 是一个指针的指针,它的作用是当删除一个元素时,更改前一个元素链表项的 le_next
#define LIST_ENTRY(type)                                                        \
        struct {                                                                \
                struct type *le_next;   /* next element */                      \
                struct type **le_prev;  /* address of previous next element */  \
        }

  • LIST_EMPTY(head),判断头部结构体 head 对应的链表是否为空。
  • LIST_FIRST(head),将返回头部结构体 head 对应的链表的首个元素。
  • LIST_INIT(head) 是将头部结构体 head 对应的链表初始化,等价于将首个元素清空。
  • LIST_NEXT(elm, field),结构体 elm 包含的名为 field 的数据,类型是一个链表项 LIST_ENTRY(type), 返回其指向的下一个元素。下面出现的 field 含义均和此相同。
  • LIST_INSERT_AFTER(listelm, elm, field),将 elm 插到已有元素 listelm 之后。
#define LIST_INSERT_AFTER(listelm, elm, field) do {     \
                        LIST_NEXT((elm),field) = LIST_NEXT((listelm),field); \
                        if (LIST_NEXT((listelm),field) != NULL) {   \
                                LIST_NEXT((listelm),field)->field.le_prev = &LIST_NEXT((elm),field); \
                        } \
                        LIST_NEXT((listelm),field) = (elm);  \
                        (elm)->field.le_prev = &LIST_NEXT((listelm),field);  \
                } while (0)
  • LIST_INSERT_BEFORE(listelm, elm, field),将 elm 插到已有元素 listelm 之前。
#define LIST_INSERT_BEFORE(listelm, elm, field) do {                            \
                (elm)->field.le_prev = (listelm)->field.le_prev;                \
                LIST_NEXT((elm), field) = (listelm);                            \
                *(listelm)->field.le_prev = (elm);                              \
                (listelm)->field.le_prev = &LIST_NEXT((elm), field);            \
        } while (0)

LIST_INSERT_AFTER和LIST_INSERT_BEFORE操作分别如蓝线红线所示:

//修改字体颜色
$\textcolor{red}{这里输入你要改变颜色的文字} $ 
<font color = "blue">这里输入你要改变颜色的文字</font>

image

  • LIST_INSERT_HEAD(head, elm, field),将 elm 插到头部结构体 head 对应链表的头部。
  • LIST_INSERT_TAIL(head, elm, field),将 elm 插到头部结构体 head 对应链表的尾部。
    • 注意需要判断list是否为空( 蓝线第二步)
  #define LIST_INSERT_TAIL(head, elm, field) do { \
     if (LIST_FIRST((head)) == NULL) { \
         LIST_INSERT_HEAD((head),(elm),field); \
     }else { \
         LIST_NEXT((elm),field) = LIST_FIRST((head)); \
         /* should save the last listelm */ \
         while(LIST_NEXT(LIST_NEXT((elm),field),field) != NULL) {  \
            LIST_NEXT((elm), field) = LIST_NEXT(LIST_NEXT((elm),field),field); \
         } /* now LIST_NEXT((elm),head) iss the lastest listelm */  \
      	 LIST_NEXT(LIST_NEXT((elm), field), field) = (elm); \
         (elm)->field.le_prev = &LIST_NEXT(LIST_NEXT((elm),field),field); \
         LIST_NEXT((elm),field) = NULL; \
         /* aslo can use LIST_INSERT_AFTER((LIST_NEXT((elm), field)),elm,field); */ \
    } \
} while (0)
  • LIST_REMOVE(elm, field),将 elm 从对应链表中删除。

Page_init:(free_pahe_list页面的初始化)

void page_init(void)
{
   /* Step 1: Initialize page_free_list. */
   /* Hint: Use macro `LIST_INIT` defined in include/queue.h. */
   LIST_INIT(&page_free_list);
  
   /* Step 2: Align `freemem` up to multiple of BY2PG. */
   freemem = ROUND(freemem,BY2PG);
    
   struct Page *used,*noused;
  
   /freemem以下部分包括内核代码和Page数组:pages[16k]
   /* Step 3: Mark all memory blow `freemem` as used(set `pp_ref` filed to 1) */
   for (used = pages;page2kva(used) < freemem;used++) {
        used -> pp_ref = 1;
   }
  
  /* Step 4: Mark the other memory as free. */
  for (noused = pa2page(PADDR(freemem));page2ppn(noused) < npage;noused++) { 
     		//pa2page need pa,so freemem -> PADDR(freemem)
        noused -> pp_ref = 0;
        LIST_INSERT_HEAD(&page_free_list,noused,pp_link);
   }
}

  • 首先利用链表相关宏初始化 page_free_list
  • 接着将 mips_vm_init() 中用到的空间对应的物理页面的内存控制块的引用次数全部标为 1,使用函数page2kva(used)获取页used对应的虚拟地址
  • 将其它页面对应的结构体中的 pp_ref 标为 0,同时使用链表宏 LIST_INSERT_HEAD 将其插入空闲链表。注意细节:
noused = pa2page(PADDR(freemem))
1.PADDR:将虚拟地址freemem映射为实际地址
2.pa2page(pa) = &pages[PPN(pa)]
PPN获取页pa对应的页号(即freemem << 12)这里因为freemem已经页对齐了,所以pa2page(PADDR(freemem))一定可以获取到第一个没被用的页(freemem向下空闲空间形成碎片)
&pages[i]获取第i个Page的地址,因为这里的noused是指针类型
  

page2ppn(noused) < npage即初始化剩余被未初始化页面,
page2ppn(&pages[i]-pages),即获取i
#include <stdio.h>

int main(int argc, char *argv[]) {
	int a[10] = {1,100};
	int *b = &a[1];
	printf("%d\n",b-a);
	
}
//output : 1
这是编译器的工作

page_alloc(申请一页空间)

int page_alloc(struct Page **pp)
{
    /* Step 1: Get a page from free memory. If fail, return the error code.*/
  	struct Page *ppage_temp;
    if (LIST_EMPTY(&page_free_list)) return -E_NO_MEM;

    ppage_temp = LIST_FIRST(&page_free_list);
    //REMOVE(elm,field) ,look up struct Page,has a son struct named pp_link
    LIST_REMOVE(ppage_temp,pp_link); 
  
    /* Step 2: Initialize this page.
    * Hint: use `bzero`. */
    bzero(page2kva(ppage_temp),BY2PG); //bzero need virtual address
    *pp = ppage_temp;
    return 0;

}

bzero(page2kva(ppage_temp),BY2PG);是为了清空申请出的空页
****page2kva(ppage_temp)表示获取ppage_temp对应的物理页的虚地址****

page_free(释放一页空间)

void page_free(struct Page *pp)
{
    if (pp->pp_ref == 0) {
        LIST_INSERT_HEAD(&page_free_list,pp,pp_link);
        return;
    }else if (pp->pp_ref > 0) {
        return;
    }
        /* Step 1: If there's still virtual address referring to this page, do nothing. */
        /* Step 2: If the `pp_ref` reaches 0, mark this page as free and return. */
        /* If the value of `pp_ref` is less than 0, some error must occurr before,
         * so PANIC !!! */
        panic("cgh:pp->pp_ref is less than zero\n");
}

LAB2-2

二级页表

lab2-pgtable.svg

boot_pgdir_walk

static Pte *boot_pgdir_walk(Pde *pgdir, u_long va, int create),它返回一级页表基地址 pgdir 对应的两级页表结构中,va 这个虚拟地址所在的二级页表项,如果 create 不为 0 且对应的二级页表不存在则会使用 alloc 函数分配一页物理内存用于存放。

static Pte *boot_pgdir_walk(Pde *pgdir, u_long va, int create)
{
    Pde *pgdir_entryp;
    Pte *pgtable, *pgtable_entry;
    //*pgdir_en tentryp contains entry os page table,PTE_V,PTE_R
    pgdir_entryp = pgdir + PDX(va);

  	//*pgdir_entryp是pgdir_entryp对应的页表项的内容
  	//create 不为 0  且对应的二级页表不存在则会使用 alloc 函数分配一页物理内存用于存放
    if ((*pgdir_entryp & PTE_V) == 0) { //1 page is null
       if (create) {
          //申请一页空间,这里需要物理空间,(访问二级页表需要)
          *pgdir_entryp = PADDR(alloc(BY2PG,BY2PG,1));
          //对应标志位赋值
          *pgdir_entryp = *pgdir_entryp | PTE_V | PTE_R;
       } else return 0;
       //#define PTE_ADDR(p) p & ~0xFFF 就是获取页目录对应的页表项的物理地址
       //对页表进行操作时处于内核态,因此使用宏 KADDR 获取对应的虚拟地址
       return (Pte *)(KADDR(PTE_ADDR(*pgdir_entryp))) + PTX(va);
    }

boot_map_segment

void boot_map_segment(Pde *pgdir, u_long va, u_long size, u_long pa, int perm),它的作用是将一级页表基地址 pgdir 对应的两级页表结构做区间地址映射,将虚拟地址区间 [va, va + size − 1] 映射到物理地址区间 [pa, pa + size − 1],因为是按页映射,要求 size 必须是页面大小的整数倍。同时为相关页表项的权限为设置为 perm。这个函数调用了 boot_pgdir_walk,它也是在内核启动过程中使用的,前面我们提到过它为 Page 结构体和 Env 结构体进行了映射。

void boot_map_segment(Pde *pgdir, u_long va, u_long size, u_long pa, int perm)
{
        int i, va_temp;
  			//二级页表的入口地址
        Pte *pgtable_entry;
  
    		for (i = 0,size = ROUND(size,BY2PG);i<size;i+=BY2PG) {
       			 pgtable_entry = boot_pgdir_walk(pgdir, va+i, 1);
        		 *pgtable_entry = (pa + i) | perm | PTE_V; 
   		  }
        /* Step 1: Check if `size` is a multiple of BY2PG. */


        /* Step 2: Map virtual address space to physical address. */
        /* Hint: Use `boot_pgdir_walk` to get the page table entry of virtual address `va`. */

}

int pgdir_walk

int pgdir_walk(Pde *pgdir, u_long va, int create, Pte **ppte),这个函数是 boot_pgdir_walk 的非启动版本,只不过后者是在内核启动过程,不允许失败,而该函数是在普通运行过程,如果空间不够允许失败,因此将返回值变为 int,若为 0 代表执行成功否则为一个失败码,同时将原本的返回值 Pte * 放到 ppte 所指的空间上。此外,因为该函数的调用在启动之后,create 不为 0 且对应的二级页表不存在时,它使用 page_alloc 而不使用 alloc 进行物理页面的分配。

int pgdir_walk(Pde *pgdir, u_long va, int create, Pte **ppte)
{
    Pde *pgdir_entryp;
    Pte *pgtable;
    struct Page *ppage;
    int ret;
		/* Step 1: Get the corresponding page directory entry and page table. */
    pgdir_entryp = pgdir + PDX(va);
		
  	 /* Step 2: If the corresponding page table is not exist(valid) and parameter `create`
         * is set, create one. And set the correct permission bits for this new page table.
         * When creating new page table, maybe out of memory. */
  	if ((*pgdir_entryp & PTE_V) == 0) {
        if (create) {
          	//如果页面申请失败,返回失败码
          	//page_alloc(**p),传入指针地址,直接修改指针*p为申请到的页面首地址
            if ((ret = page_alloc(&ppage)) < 0) return ret;
          	//如果申请成功,ppage就是申请页面的首地址
            //page2pa(ppage)返回Page *pp对应的物理页首地址
          	//获取的物理地址填入*pgdir_entryp即可
            *pgdir_entryp = (page2pa(ppage)) | PTE_V | PTE_R;
            ppage->pp_ref++;
        } else {
            *ppte = 0;
            return 0;
        }
    }
  	
   /* Step 3: Set the page table entry to `*ppte` as return value. */
 	 //将*ppte中填入va对应页表项的虚拟地址(KADDR)
 	 *ppte = (Pte *)(KADDR(PTE_ADDR(*pgdir_entryp))) + PTX(va);
   return 0;
}

page_insert

int page_insert(Pde *pgdir, struct Page *pp, u_long va, u_int perm),这个函数的作用是将一级页表基地址 pgdir 对应的两级页表结构中 va 这一虚拟地址映射到内存控制块 pp 对应的物理页面,并将页表项权限为设置为 perm。

int page_insert(Pde *pgdir, struct Page *pp, u_long va, u_int perm)
{
        u_int PERM;
        Pte *pgtable_entry;
        PERM = perm | PTE_V;
   		  int ret;

        /* Step 1: Get corresponding page table entry. */
        pgdir_walk(pgdir, va, 0, &pgtable_entry);

        if (pgtable_entry != 0 && (*pgtable_entry & PTE_V) != 0) {
          			//检查映射到地址是否为pp对应的的物理页,如果不是,需要取消当前地址映射(替换)
                if (pa2page(*pgtable_entry) != pp) {
                  			//if page table is not this element,then remove
                        page_remove(pgdir, va);
                //否则,建立映射关系*pgtable_entry -> Page *pp对应的物理页首地址,设置权限位为PERM
                } else {
                  			//使用 tlb_invalidate 函数可以实现删除特定虚拟地址的映射,每当页表被修改,就需要调用该函数以保证下次访问该虚拟地址时诱发 TLB 重填以保证访存的正确性。
                        tlb_invalidate(pgdir, va);
                        *pgtable_entry = (page2pa(pp) | PERM);
                        return 0;
                }
        }

        /* Step 2: Update TLB. */
    		tlb_invalidate(pgdir, va);
        /* hint: use tlb_invalidate function */
  			//再次使用pgdir_walk,无映射有2种可能,page_remove后无映射,原本无映射,此时可以继续建立映射
  			/* Step 3: Do check, re-get page table entry to validate the insertion. */
  			/* Step 3.1 Check if the page can be insert, if can’t return -E_NO_MEM */
    		if ((ret = pgdir_walk(pgdir,va,1,&pgtable_entry)) < 0) return ret;
  			/* Step 3.2 Insert page and increment the pp_ref */
    		*pgtable_entry = page2pa(pp) | PERM;
    		pp -> pp_ref++;
}

tlb_out

tlb_out该函数根据传入的参数(TLB 的 Key)找到对应的 TLB 表项,并将其清空

先了解有关TLB的相关命令

tlbr:以 Index 寄存器中的值为索引,读出 TLB 中对应的表项到 EntryHi 与 EntryLo。

tlbwi:以 Index 寄存器中的值为索引,将此时 EntryHi 与 EntryLo 的值写到索引指定的 TLB 表项中。

tlbwr:将 EntryHi 与 EntryLo 的数据随机写到一个 TLB 表项中(此处使用 Random 寄存器来“随机”指定表项,Random 寄存器本质上是一个不停运行的循环计数器)。

tlbp:根据 EntryHi 中的 Key(包含 VPN 与 ASID),查找 TLB 中与之对应的表项,并将表项的索引存入 Index 寄存器(若未找到匹配项,则 Index 最高位被置 1)。


 #include <asm/regdef.h>
 #include <asm/cp0regdef.h>
 #include <asm/asm.h>
 LEAF(tlb_out)
     nop
     //首先将CP0_ENTRYHI中原有的值写入k1寄存器
     mfc0    k1,CP0_ENTRYHI
     //将传入的参数(待清空表项的key)写到CP0_ENTRYHI中
     mtc0    a0,CP0_ENTRYHI
     nop
     //根据 EntryHi 中的 Key,查找 TLB 中与之对应的表项,并将表项的索引存入 Index 寄存器,(若未找到匹配项,则 Index 最高位被置 1)。
     tlbp
     //防止冲突
     nop
     nop
     nop
     nop
     //取出CP0_INDEX的值
     mfc0    k0,CP0_INDEX
     //如果是Index 最高位被置 1,表明没找到,跳到 NOFOUND标签处
     //如果找到了,Index 最高位为0,继续执行
     bltz    k0,NOFOUND
     nop
     //为清空做准备,CP0_ENTRYHI,CP0_ENTRYLO全部填入0
     mtc0    zero,CP0_ENTRYHI
     mtc0    zero,CP0_ENTRYLO
     nop
     //根据找到的Index将此时 EntryHi 与 EntryLo 的值写到索引指定的 TLB 表项中,即完成清空
     tlbwi
 NOFOUND:
     //复原现场(将原来的key写会CP0_ENTRYHI)
     mtc0    k1,CP0_ENTRYHI
     //跳回被调用的地方
     j   ra
     nop
 END(tlb_out)
posted @ 2022-04-08 16:39  `Demon  阅读(54)  评论(0编辑  收藏  举报