分页
(1)
添加CR3寄存器。
已经添加完了
(2)
为CR0寄存器添加PG位的功能。装载CR0后, 如果发现CR0的PE位和 PG 位均为 1, 则开启 IA-32 分页机制, 从此所有线性地址的访问(包括 lnaddr_read(), lnaddr_write())都需要经过页级地址转换。在 restart()函数 中对CR0寄存器进行初始化时, PG位要被置0。
已经实现
(3)
为了实现页级地址转换, 你需要对 lnaddr_read()和 lnaddr_write()函数作 少量修改。
//线性地址
hwaddr_t page_translate(lnaddr_t addr) {
return addr;
}
uint32_t lnaddr_read(lnaddr_t addr, size_t len) {
assert(len == 1 || len == 2 || len == 4);
hwaddr_t a = page_translate(addr);
hwaddr_t b = page_translate(addr + len);
if (a != b) { // 两页,直接终止
assert(0);
}
else {
hwaddr_t hwaddr = a;
return hwaddr_read(hwaddr, len);
}
}
void lnaddr_write(lnaddr_t addr, size_t len, uint32_t data) {
assert(len == 1 || len == 2 || len == 4);
if (0) { // 两页
assert(0);
}
else {
hwaddr_t hwaddr = page_translate(addr);
return hwaddr_write(hwaddr, len, data);
}
}
(4)
需要在 kernel 中加入分页管理相关的代码, 你只需要在 kernel/include/common.h 中 定 义 宏 IA32_PAGE, 然后修改 kernel/Makefile.part 中的链接选项
很简单,不多说
(5)
你需要理解页级地址转过的过程, 然后实现page_translate()
hwaddr_t page_translate(lnaddr_t addr) {
if (!cpu.cr0.protect_enable || !cpu.cr0.paging) return addr;
/* addr = 10 dictionary + 10 page + 12 offset */
uint32_t dictionary = addr >> 22;
uint32_t page = (addr >> 12) & 0x3ff;
uint32_t offset = addr & 0xfff;
/* 读取页表信息 */
uint32_t tmp = (cpu.cr3.page_directory_base << 12) + dictionary * 4; // 页目录基地址 + 页目录号 * 页表项大小
PDE dictionary_;
PTE page_;
dictionary_.val = hwaddr_read(tmp, 4);
tmp = (dictionary_.addr << 12) + page * 4; // 二级页表基地址 + 页号 + 页表项大小
page_.val = hwaddr_read(tmp, 4);
Assert(dictionary_.present == 1, "dirctionary present");
Assert(page_.present == 1, "second present");
return (page_.page_frame << 12) + offset;
}
直接用给的PDE,PTE即可。
接下来解决跨页的问题
if ((int64_t)(offset + len) > 0x1000) { // 跨页
size_t l = 0xfff - offset + 1; // 低位最多几个字节同页
uint32_t down_val = lnaddr_read(addr, l); // 低位
uint32_t up_val = lnaddr_read(addr + l, len - l); //高位
return (up_val << (l * 8)) | down_val;
}
另一个写内存的同理
发现存在没有实现的指令,为cld与std
实现一下即可,这两个很容易。
(6)
为用户进程分配虚拟空间
在kernel/src/elf/elf.c/loader()里面添加即可ph -> p_vaddr = mm_malloc(ph -> p_vaddr,ph -> p_memsz);
选做二
ADDR的指令的函数,在memory.c中
hwaddr_t cmd_page(lnaddr_t addr) { // 简易调试器
if(!cpu.cr0.protect_enable || !cpu.cr0.paging) return addr;
/* addr = 10 dictionary + 10 page + 12 offset */
uint32_t dictionary = addr >> 22, page = (addr >> 12) & 0x3ff, offset = addr & 0xfff;
/* 读取页表信息 */
uint32_t tmp = (cpu.cr3.page_directory_base << 12) + dictionary * 4; // 页目录基地址 + 页目录号 * 页表项大小
Page_info dictionary_, page_;
dictionary_.val = hwaddr_read(tmp, 4);
tmp = (dictionary_.addr << 12) + page * 4; // 二级页表基地址 + 页号 + 页表项大小
page_.val = hwaddr_read(tmp, 4);
if(dictionary_.p != 1) {
printf("dirctionary present != 1\n");
return 0;
}
if(page_.p != 1) {
printf("second page table present != 1\n");
return 0;
}
return (page_.addr << 12) + offset;
}
在ui.c中:
hwaddr_t cmd_page(lnaddr_t addr);
static int cmd_translate(char* args) {
if (args == NULL) { printf("parameter invalid!\n"); return 0; }
uint32_t addr;
sscanf(args, "%x", &addr);
hwaddr_t ans = cmd_page(addr);
if (ans) printf("Addr is 0x%08x\n", ans);
return 0;
}
必做4
实现tlb
tlb.h
#ifndef __TLB_H__
#define __TLB_H__
#include "common.h"
/******************************
* TLB 总共有 64 项
* fully associative
* 标志位只需要 valid bit
* 替换算法采用随机方式
* tag有20位, 即虚拟页号
******************************/
#define TLB_SIZE 64
typedef struct{
bool valid; // 有效位
uint32_t tag, data; // tag是虚拟页号 data是物理页号
} TLB;
TLB tlb[TLB_SIZE];
void init_tlb();
int read_tlb(lnaddr_t addr);
void write_tlb(lnaddr_t addr, hwaddr_t haaddr);
#endif
tlb.c
#include "memory/tlb.h"
#include "burst.h"
#include <time.h>
#include <stdlib.h>
void init_tlb(){
int i;
for (i = 0; i < TLB_SIZE; i++) tlb[i].valid = 0;
srand(clock());
}
// 如果有,返回index,没有返回-1
int read_tlb(lnaddr_t addr) {
int tag = addr >> 12;
int i;
for (i = 0; i < TLB_SIZE; ++ i) {
if (tlb[i].tag == tag && tlb[i].valid) return i;
}
return -1; // tlb里没有
}
// 参数:虚拟地址,物理地址
void write_tlb(lnaddr_t addr, hwaddr_t addr_){
int tag = addr >> 12, i;
addr_ >>= 12;
for (i = 0; i < TLB_SIZE; i++) {
if(!tlb[i].valid){
tlb[i].tag = tag, tlb[i].data = addr_, tlb[i].valid = 1;
return ;
}
}
// 没有空闲的了, 替换
i = rand() % TLB_SIZE;
tlb[i].tag = tag, tlb[i].data = addr_, tlb[i].valid = 1;
}
然后在page_translate里面修改即可:
if (index != -1)
return (tlb[index].data << 12) + offset;
如果命中,直接返回
没有命中,翻译之后写入tlb
hwaddr_t addr_ = (page_.page_frame << 12) + offset;
write_tlb(addr, addr_);
return addr_;
选做3
void create_video_mapping() {
/* TODO: create an identical mapping from virtual memory area
* [0xa0000, 0xa0000 + SCR_SIZE) to physical memory area
* [0xa0000, 0xa0000 + SCR_SIZE) for user program. You may define
* some page tables to create this mapping.
*/
PDE* pdir = (PDE*)va_to_pa(get_updir());
pdir[0].val = make_pde(va_to_pa(ptable));
uint32_t i, start, end;
start = VMEM_ADDR / PAGE_SIZE;
end = start + SCR_SIZE / PAGE_SIZE;
if (SCR_SIZE % PAGE_SIZE)
end++;
for (i = start; i <= end; i++) {
ptable[i].val = make_pte(i << 12);
}
}