一.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结构体的物理地址
/* Get the physical address of Page 'pp'.
*/
static inline u_long
page2pa(struct Page *pp)
{
return page2ppn(pp) << PGSHIFT;
}
pa2page:得到某个物理地址所对应的Page结构体
/* 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结构体的内核虚拟地址
/* Get the kernel virtual address of Page 'pp'.
*/
static inline u_long
page2kva(struct Page *pp)
{
return KADDR(page2pa(pp));
}
PPN:得到某个虚拟地址的页号
#define PPN(va) (((u_long)(va))>>12)
PADDR:将某个内核虚拟地址转化为物理地址
// 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:将某个物理地址转化为内核虚拟地址
// 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 #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 #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。