OS-Lab2

一.TLB访存机制

当一个虚拟地址被送到MMU中进行翻译的时候,硬件首先在TLB中寻找包含这个地址的页面,如果它的虚页号在TLB中,并且没有违反保护位,那么就可以直接从TLB中得到相应的物理页号,而不去访问页表;如果发现虚页号在TLB中不存在,那么MMU将进行常规的页表查找,同时通过一定的策略来将这一页的页表项替换到TLB中,之后再次访问这一页的时候就可以直接在TLB中找到。

拿到了物理地址后,可以直接访问内存拿数据,不过会慢,所以,Cache就是部分物理地址到数据的映射。是内存的一部分copy。
在这里插入图片描述

二.二级页表

首先,c语言可以操作任何空间的地址,这个就是虚拟地址。

我们要做的就是模拟二级页表机制,往虚拟地址里面填写物理地址,再通过f(va)−>pa和f(pa)→va转换取到地址里面的东西。

简单来说,把放着很多很多页的虚拟地址,按照虚拟地址的索引,把它对应的物理地址放到另一个虚拟地址pgidr里。

这样就“假装”建立起了二级页表,物理地址从未使用过,要访问时永远要加上ULIM。

Page结构体其实是物理地址的一个“象征”。因为它减去pages拿到ppn以后,shift12位再加上ULIM,就是虚拟地址,减去ULM又变回物理地址。建立起来虚拟页和物理页之间的桥梁。尽管自己不是真正的4KB页,但是是桥梁。

三.Page存储结构

在这里插入图片描述

四.自映射机制图示

在这里插入图片描述

五.页面与地址的转化

在本次实验中涉及到许多的页面与地址的转化,其中用到许多已经定义的函数,现整理如下:

page2pa:得到某个page结构体的物理地址

1
2
3
4
5
6
7
/* Get the physical address of Page 'pp'.
*/
static inline u_long
page2pa(struct Page *pp)
{
return page2ppn(pp) << PGSHIFT;
}

pa2page:得到某个物理地址所对应的Page结构体

1
2
3
4
5
6
7
8
9
10
11
/* Get the Page struct whose physical address is 'pa'.
*/
static inline struct Page *
pa2page(u_long pa)
{
if (PPN(pa) >= npage) {
panic("pa2page called with invalid pa: %x", pa);
}

return &pages[PPN(pa)];
}

page2kva:得到某个Page结构体的内核虚拟地址

1
2
3
4
5
6
7
/* Get the kernel virtual address of Page 'pp'.
*/
static inline u_long
page2kva(struct Page *pp)
{
return KADDR(page2pa(pp));
}

PPN:得到某个虚拟地址的页号

1
#define PPN(va)		(((u_long)(va))>>12)

PADDR:将某个内核虚拟地址转化为物理地址

1
2
3
4
5
6
7
8
// 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; \
})

KADDR:将某个物理地址转化为内核虚拟地址

1
2
3
4
5
6
7
8
// 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; \
})

本次实验的前半部分涉及了许多对这类函数的应用,熟练掌握这类函数实现各类地址查询是本次实验的一大难点。

六.部分实验代码详解

部分代码含义解释如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 1 #define BY2PG        4096             // 页面大小的Byte数,一个页面大小为4kb
2 #define PDMAP (4*1024*1024) // 一个页表管理1024个页面,大小总共4*1024*1024Byte
3 #define PGSHIFT 12 // 页面的偏移位数,4kb对应12位
4 #define PDSHIFT 22 // 页表的偏移位数,同上,即log2(PDMAP)
5 #define PDX(va) ((((u_long)(va))>>22) & 0x03FF) // 取虚拟地址高10位,为页目录号
6 #define PTX(va) ((((u_long)(va))>>12) & 0x03FF) // 取虚拟地址高11~20位,为页表号
7 #define PTE_ADDR(pte) ((u_long)(pte)&~0xFFF) // 页表项取低12位,为页内偏移
8
9 // page number field of address
10 #define PPN(va) (((u_long)(va))>>12) // 物理页号,为虚拟地址偏移12位
11 #define VPN(va) PPN(va) // 虚页号
12
13 #define VA2PFN(va) (((u_long)(va)) & 0xFFFFF000 )
14 #define PTE2PT 1024
15 //$#define VA2PDE(va) (((u_long)(va)) & 0xFFC00000 )
16
17 /* Page Table/Directory Entry flags
18 * these are defined by the hardware
19 */
20 #define PTE_G 0x0100 // 全局位
21 #define PTE_V 0x0200 // 有效位
22 #define PTE_R 0x0400 // 修改位,如果是0表示只对该页面进行过读操作,否则进行过写操作,要引发中断将内容写回内存
23 #define PTE_D 0x0002 // 文件缓存的修改位dirty
24 #define PTE_COW 0x0001 // 写时复制copy on write
25 #define PTE_UC 0x0800 // 未缓存uncached
26 #define PTE_LIBRARY 0x0004 // 共享内存

其余的一些定义(异常码的解释略去):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 1 #define KERNBASE 0x80010000 // 内核基地址
2
3 #define VPT (ULIM + PDMAP ) //
4 #define KSTACKTOP (VPT-0x100) // 内核栈顶
5 #define KSTKSIZE (8*BY2PG) // 内核栈大小
6 #define ULIM 0x80000000 // 用户态地址上限
7
8 #define UVPT (ULIM - PDMAP) //
9 #define UPAGES (UVPT - PDMAP) // 用户页表
10 #define UENVS (UPAGES - PDMAP) // 用户进程控制块
11
12 #define UTOP UENVS // 用户态高地址
13 #define UXSTACKTOP (UTOP) // 用户态异常栈
14 #define TIMESTACK 0x82000000 // 上下文保存栈
15
16 #define USTACKTOP (UTOP - 2*BY2PG) // 用户栈
17 #define UTEXT 0x00400000 // 用户代码段

最后是一些函数与函数宏:

  • void bcopy(const void *, void *, size_t):内存拷贝
  • void bzero(void *, size_t):内存清空
  • assert(x):支持断言机制。
  • TRUP(_p):相当于min(_p, ULIM),似乎是为了防止用户读写内核段内存。

在进行内存初始化时,mips_detect_memory()mips_vm_init()page_init()被依次调用。mips_detect_memory()用来初始化一些全局变量(此处将物理内存大小设置为64MB,在实际中,内存大小是由硬件得到的,这里只是模拟了检测物理内存大小这个过程)。其余的函数的功能为:

  • static void *alloc(u_int n, u_int align, int clear):申请一块内存,返回首地址。
  • static Pte *boot_pgdir_walk(Pde *pgdir, u_long va, int create):从页目录项中找出虚拟地址va对应的页表项,若create置位,则不存在时创建。
  • void boot_map_segment(Pde *pgdir, u_long va, u_long size, u_long pa, int perm):将虚拟地址va映射到物理地址pa
  • void mips_vm_init():创建一个二级页表。
  • void page_init(void):将内存分页并初始化空闲页表。
  • int page_alloc(struct Page **pp):分配一页内存并把值赋给pp。
  • void page_free(struct Page *pp):释放一页内存。
  • int pgdir_walk(Pde *pgdir, u_long va, int create, Pte **ppte):建立起二级页表结构后从页目录中找到va对应页表项的函数。
  • int page_insert(Pde *pgdir, struct Page *pp, u_long va, u_int perm):将物理页pp映射到va。
  • struct Page * page_lookup(Pde *pgdir, u_long va, Pte **ppte):找到虚拟地址va对应的物理页面。
  • void page_decref(struct Page *pp):降低物理页面的引用次数,降到0后释放页面。
  • void page_remove(Pde *pgdir, u_long va):释放虚拟地址va对应的页面。
  • void tlb_invalidate(Pde *pgdir, u_long va):更新TLB。

OS-Lab2
http://example.com/2022/07/17/OS-Lab2/
作者
Wei Xia
发布于
2022年7月17日
许可协议