LOADING

加载过慢请开启缓存 浏览器默认开启

NEMU PA3 阶段三、四(实现分页机制)

2024/11/2

分页

(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);
    }
}