OS Lab2 Code Review
wzk
queue.h
mmu.h
pmap.h
pmap.c
tlb_asm.S
queue.h
主要定义了链表的操作,最后两个宏函数是尾队列相关
LIST_HEAD:定义一个指针head指向链表表头
LIST_HEAD_INITIALIZER:通过将head赋值为此来将链表变为空
LIST_ENTRY:定义链表项,包含type *le_next和**le_prev。注意与双向链表不同的是,这里的le_prev指向前一个元素的le_next指针。
LIST_EMPTY:判定链表是否为空
LIST_FIRST:返回head指向的链表的表头指针
LIST_FOREACH:封装了一个for(;;)语句,用于遍历链表
LIST_INIT:直接将链表head变为为空
LIST_INSERT_AFTER:(作业)在listelm元素后插入elm,注意le_prev指向的是指针
LIST_INSERT_BEFORE:在listelm前插入elm
LIST_INSERT_HEAD:在表头插入elm
LIST_INSERT_TAIL:在表尾插入elm
LIST_NEXT:返回元素的next指针
LIST_REMOVE:删除链表中的elm
TAILQ_HEAD:定义尾队列的head指针(*tqh_first、**tqh_las)
TAILQ_ENTRY:定义尾队列中的元素(类似LIST_ENTRY)
mmu.h
第一部分为MIPS定义。
BY2PG:每页字节数(4KB)
PDMAP:每个页目录项映射的字节大小(4MB)
PGSHIFT: 虚拟地址中二级页表号的右移位数(12位)
PDSHIFT:虚拟地址中页目录号的右移位数(22位)
PDX:对虚拟地址取页目录号的宏(31~22位)
PTX:对虚拟地址取页表号的宏(21~12位)
PTE_ADDR:取页表项的对应地址(清空低12位的标识位)
PPN:物理页号(右移12位)
VPN:虚拟页号(与PPN相同)
VA2PFN:虚拟地址转换为page frame number(清空低12位)
PTE_G~PTE_LIBRARY 页表的标识位(有效位、dirty bit、uncached等)
MIPS内存映射布局图解(kuseg,kseg0~kseg3)
KERNBASE:内核内存起始地址
VPT:
KSTACKTOP:内核栈顶地址
KSTKSIZE:内核栈大小
ULIM:kuseg的地址上界
UVPT:用户VPT地址
UPAGES:用户页地址
UENVS:用户进程地址
UTOP:用户栈顶(比USTACKTOP多了异常栈)
UXSTACKTOP:同上
TIMESTACK:
USTACKTOP:用户正常栈栈顶
UTEXT:用户代码段
E_UNSPECIFIED~E_NOT_EXEC:用于函数返回值的各种异常编码(未知、进程不存在、无效参数、内存不足、无进程空间,文件系统的磁盘无空间、打开文件过多、文件未找到、错误路径、文件已存在、文件不可执行、最大异常编码)
bcopy:将src对应大小的内容复制到dst(定义于init.c)
bzero:清空地址对应大小空间的内容(定义于init.c)
bootstacktop,bootstack:
npage:页数量(在pmap.c)
Pde:页目录项类型(u_long)
Pte:页表项类型(u_long)
vpt:虚拟页表指针数组(Pte* [])
vpd:虚拟页目录指针数组(Pde* [])
PADDR:将核虚拟地址(kva)转换为物理地址,若在kuseg则报错,否则清除最高位
KADDR:PADDR的逆运算将物理地址转化为核虚拟地址,若对应页号(右移12位)大于页表数量则报错,否则最高位赋1
assert:断言,参数为0时panic
TRUP:那就是返回参数和ULIM的最大值(ULIM转换为参数的类型)
tlb_out:定义在汇编tlb_asm.S里
pmap.h
作为pmap.c的头文件出现
Page_list:元素类型为Page的链表Page_list
Page_LIST_entry_t:元素为Page的链表的项(包含*le_next和**le_prev,指向前后元素)类型
Page:由pp_link(一个Page链表项)和pp_ref(页面引用次数)构成的结构体
Page_list实质为Page的链表,Page包含pp_ref和链表项(前后指针)
pages:若干个页的数组(指针,相当于页起始地址)
page2ppn:将页指针转化为物理页号(减去pages)
page2pa:将页指针转化为物理地址(转化为物理页号再左移PGSHIFT位)
pa2page:将物理地址转化为页指针,若页号超过页表总数量则报错
page2kva:将页指针转化为内核虚拟地址(先page2pa转化为物理地址再KADDR)
va2pa:将虚拟地址转化为物理地址(通过二级页表),若对应页目录项/页表项无效则返回非0,返回的是页的起始地址
声明了pmap.c里的各种函数和mips_init,方便使用
tlb_asm.S
定义了tlb_out函数,功能是清除虚拟地址(参数)对应的TLB项。具体流程如下:
首先将a0(参数,虚拟地址)写入ENTRYHI,然后使用tlbp设置Index为a0对应的TLB项,若Index小于0(未找到)则跳转到NOFOUND,否则将TLB[Index]设为0->0的映射(通过将ENTRYHI和ENTRYLO0设为0实现)
tlbp是寻找与a0匹配的TLB入口,加载至Index
tlbwi是将ENTRYHI和ENTRYLO0对应的映射关系(页表项)写入TLB[index]
4行nop是为了保证CPU在没有转发机制时,tlbp和mfc0接连用到Index寄存器不会触发数据冒险
pmap.c
包含物理内存与虚拟内存管理的相关函数,完成了页表初始化、插入、查找、删除等一系列操作
开始时全局变量:最大物理地址maxpa(相对于ULIM)、内存最大页数npage、基础内存basemem、扩展内存extmem、空闲内存地址freemem、页指针(数组)pages、空闲链表page_free_list、页目录基地址boot_pdgir。除后三个其余均为u_long类型
void mips_detec_memory()
初始化部分全局变量(手动赋值)并打印
maxpa=basemem=0x4000 0000,extmem=0x0,npage=basemem>>PGSHIFT
void *alloc(u_int n, u_int align, int clear)
分配n字节物理内存并对齐,clear为1时清空对应内存区域。只在初始化虚拟内存时使用。内存不足时panic,否则内存返回地址
首先从linker script(定义data bss text段的文件)中获得end,表示当前空闲内存的起始地址(已分配内存的结束地址),将freemem初始化为end
将freemem对align对齐,增加n字节,在clear=1时调用bzero清空内存,若freemem对应物理地址超过最大物理地址则报错,否则返回分配的内存的低地址
freemem由end初始化,故为虚拟地址
Pte *boot_pgdir_walk(Pde *pgdir, u_long va, int create)
返回虚拟地址va对应的二级页表项(基于pgdir对应的页目录),若不存在页表项且create=1则创建。
首先使用pgdir+页目录号PDX(va)得到页目录项,然后PTE_ADDR清空标识位、KADDR转化为内核虚拟地址,得到对应页表
若页目录项标识位为0(页表不存在),则create=1时给pgtable分配一页内存,页目录项赋值为PADDR(pgtable),增加有效位PTE_V和dirty bit PTE_R否则返回0
最后二级页表基地址pgtable+二级页表号PTX(va)得到页表项并返回
页目录项指针pgdir_entryp为物理地址,页表项指针pgtable为虚拟地址
void boot_map_segment(Pde *pgdir, u_long va, u_long size, u_long pa, int perm)
将虚拟地址[va,va+size]映射到物理地址[pa,pa+size],pgdir为页目录基地址,perm作为有效位
检查size是否为BY2PG整数倍,不满足则返回(ROUND也可以)
循环遍历va到va+size,调用boot_pgdir_walk返回va+i对应的页表项指针,将页表项赋值为pa+i,低12位清空并加上有效位perm|PTE_V以及dirty bit PTE_R
void mips_vm_init()
初始化虚拟内存,建立二级页表
首先在freemem给页目录分配一页空间,设定mCONTEXT(asm中定义)为页目录基地址
然后初始化物理内存,分配npage个一页大小的内存给pages,调用boot_map_segment将用户页起始地址UPAGES开始的npage页数(向BY2PG对齐)映射到pages物理页对应的物理地址
最后初始化进程控制块envs,方式与物理页相同,从用户进程空间UENVS开始映射
上述函数调用关系:mips_vm_init调用alloc和boot_map_segment,boot_map_segment调用boot_pgdir_walk,
以下page_alloc和page_free为物理内存的管理
void page_init()
初始化空闲链表和虚拟页。每个页的pp_ref表示引用次数,空闲页存在空闲链表中
首先调用LIST_INIT初始化空闲链表,将freemem对齐到BY2PG。
然后将物理页数组pages的PADDR(freemem)/BY2PG以下部分的pp_ref赋为1,以上部分的pp_ref赋为0并插入空闲链表头部
int page_alloc(struct Page **pp)
从空闲链表中分配一个物理页到*pp,返回值表示是否成功
若空闲链表为空则返回错误,否则取出空闲链表第一项并从链表中移除,调用bzero将对应虚拟地址的空间清零,赋给*pp,返回0
void page_free(struct Page *pp)
释放一页,标记为空闲
若pp_ref>0则返回,=0则插入空闲链表头部,<0则报错
int pgdir_walk(Pde *pgdir, u_long va, int create, Pte **ppte)
类似boot_pgdir_walk,设置*ppte为虚拟地址va对应的二级页表项(基于pgdir对应的页目录),若不存在页表项且create=1则创建。
首先使用pgdir+页目录号PDX(va)得到页目录项,然后PTE_ADDR清空标识位、KADDR转化为内核虚拟地址,得到对应页表
若页目录项标识位为0(页表不存在),则create=1时给pgtable分配一页内存(使用page_alloc,与boot_pgdir_walk不同),没有空闲页则返回错误,否则对应页的pp_ref+=1,页目录项赋值为PADDR(pgtable),增加有效位PTE_V和dirty bit PTE_R否则返回0
最后二级页表基地址pgtable+二级页表号PTX(va)得到页表项并赋值给*ppte,返回0
页目录项指针pgdir_entryp为物理地址,页表项指针pgtable为虚拟地址
int page_insert(Pde *pgdir, struct Page *pp, u_long va, u_int perm)
将物理页pp映射到虚拟地址va
首先调用pgdir_walk(create为0)查找va对应的二级页表项
若页表项不为0(va已有对应页)且有效,检查其对应地址的对应页是否为pp,是则更新TLB并更新有效位返回0,否则去除原有页
然后更新TLB,重新调用pgdir_walk(create为1)创建va对应页,无空闲页则报错,否则插入页(将对应页表项赋值为物理页pp对应的物理地址),标记许可位,pp_ref+=1,返回0
struct Page *page_lookup(Pde *pgdir, u_long va, Pte **ppte)
查找va对应的物理页,返回对应页,对应该页的页表项存入*ppte
调用pgdir_walk查找页表项存入pte,若页表项不存在(pte对应0)或无效(pte对应标识位为0)则返回0
否则将pte存入*ppte,pte对应物理地址转化为物理页并返回
void page_decref(struct Page *pp)
将pp对应页的pp_ref减1,减至0时释放此页(调用page_free插入空闲链表)
void page_remove(Pde *pgdir, u_long va)
将va对应的物理页移除
调用page_lookup查找va对应页,返回0(错误)则返回,否则pp_ref-1,减至0时移至空闲链表
将页表项对应值清0,更新TLB
void tlb_invalidate(Pde *pgdir, u_long va)
更新TLB,删除va对应的TLB项
将va低12位清零,若有进程则与进程ID做或运算,然后调用汇编tlb_asm.S中的tlb_out
void physical_memory_manage_check()
物理内存管理检验
void page_check()
虚拟内存管理检验
void pageout(int va, int context)
进程切换时更换页,context作为页目录基地址
若context在用户区则报TLB回填错误,若va在进程区或小于0x10000则报错,若page_alloc失败则报错
page_alloc的页的ppref加1,在context基地址插入页p,虚拟地址为va对应的page frame number