仓库地址

阶段一

第一次没过破防了决定直接一部到胃一起写必做一和选做一

1. 概述

  1. 根据指导书可知,我们需要实现一级高速缓存和二级告诉缓存,其中l2会多一个脏标签的处理
  2. 公式:C=B* E *S (E缓存块 B字节 S组 C总大小)
    1. l1: E=8 B=64 C=64*1024 S=128
    2. l2: E=8 B=64 C=4*1024 *1024 S=4096
  3. Address:
    1. tag: t bits
    2. set index: s bits
    3. block offset: b bits
    1. l1: b=6 s=7
    2. l2: b=6 s=12

2. 实现

  1. 定义高速缓存的结构(nemu/include/memory/cache.h)
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
27
28
29
30
31
32
33
34
#include <stdint.h>

#define CACHE_b 6 //b
#define CACHE_L1_e 3 //一级高速缓存的e
#define CACHE_L1_s 7 //一级高速缓存的s
#define CACHE_L2_e 4 //二级高速缓存的e
#define CACHE_L2_s 12 //二级高速缓存的s
#define CACHE_L1_CAP (64 * 1024) //一级高速缓存的C
#define CACHE_L2_CAP (4 * 1024 * 1024) //二级高速缓存的C
//通过对1进行右移小写字母位的操作对应的就是转化为大写字母的过程(*2)
#define CACHE_B (1 << CACHE_b)
#define CACHE_L1_E (1 << CACHE_L1_e)
#define CACHE_L1_S (1 << CACHE_L1_s)
#define CACHE_L2_E (1 << CACHE_L2_e)
#define CACHE_L2_S (1 << CACHE_L2_s)

//一级高速缓存L1的定义
typedef struct{
uint8_t data[CACHE_B]; //字节数组存储块内数据
uint32_t tag; //标签位
bool validVal; //有效位
} L1;
//缓存块数组表示整个一级高速缓存
L1 cache_L1[CACHE_L1_S * CACHE_L1_E];

//二级高速缓存L2的定义,与一级高速缓存相比多了一个脏标签
typedef struct{
uint8_t data[CACHE_B];
uint32_t tag;
bool validVal;
bool dirtyVal;
} L2;

L2 cache_L2[CACHE_L2_S * CACHE_L2_E];
  1. 实现相关函数(nemu/src/memory/cache.c)
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
//初始化高速缓存
void init_cache() {
//遍历所有高速缓存块,将有效位和脏标签清空即可
int i;
for (i = 0; i < CACHE_L1_S * CACHE_L1_E; i++) {
cache_L1[i].validVal = false;
}
for (i = 0; i < CACHE_L2_S * CACHE_L2_E; i++) {
cache_L2[i].dirtyVal = false;
cache_L2[i].validVal = false;
}
return;
}

//一级高速缓存
//声明需要调用的外部函数,实现对内存种数据的读取
void ddr3_read_me(hwaddr_t addr, void* data);

//查找地址对应的数据是否存在于一级高速缓存中,返回数据块位于缓存中的位置参数
int read_cache_L1(hwaddr_t addr) {
uint32_t set = ((addr >> CACHE_b) & (CACHE_L1_S - 1)); //获取组索引参数
uint32_t tag = (addr >> (CACHE_b + CACHE_L1_s)); //获取标签参数
//遍历组内每一个缓存块进行匹配
int block_i;
int set_begin = set * CACHE_L1_E;
int set_end = (set + 1) * CACHE_L1_E;
for (block_i = set_begin; block_i < set_end; block_i++)
if (cache_L1[block_i].validVal && cache_L1[block_i].tag == tag) // Hit!
return block_i;
//若一级高速缓存内未命中,则到二级高速缓存中查找
srand(time(0));
int block_i_L2 = read_cache_L2(addr);
//若二级缓存内未命中,则查找一级高速缓存中是否存在空的缓存块
for (block_i = set_begin; block_i < set_end; block_i++)
if (!cache_L1[block_i].validVal)
break;
//若无空缓存块,则采用随机替换策略,通过随机生成数在组内找一个缓存块进行替换
if (block_i == set_end)
block_i = set_begin + rand() % CACHE_L1_E;
//写入数据到替换好的缓存块中,从二级高速缓存中读取,已经读入了二级高速缓存中
memcpy(cache_L1[block_i].data, cache_L2[block_i_L2].data, CACHE_B);

cache_L1[block_i].validVal = true;
cache_L1[block_i].tag = tag;
return block_i;
}

//查找二级缓存
//声明对内存进行写操作的外部函数
void ddr3_write_me(hwaddr_t addr, void* data, uint8_t* mask);

// 查找二级高速缓存中与地址匹配的缓存块,返回缓存块的位置参数
int read_cache_L2(hwaddr_t addr) {
uint32_t set = ((addr >> CACHE_b) & (CACHE_L2_S - 1));
uint32_t tag = (addr >> (CACHE_b + CACHE_L2_s));
//涉及到块内写操作,要遵循空间局部性原理,清空块内偏移量,找到块的起始位置
uint32_t block_start = ((addr >> CACHE_b) << CACHE_b);
int block_i;
int set_begin = set * CACHE_L2_E;
int set_end = (set + 1) * CACHE_L2_E;
for (block_i = set_begin; block_i < set_end; block_i++)
if (cache_L2[block_i].validVal && cache_L2[block_i].tag == tag)
return block_i;
//若二级缓存中没有找到,则查找是否存在空的缓存块
srand(time(0));
for (block_i = set_begin; block_i < set_end; block_i++)
if (!cache_L2[block_i].validVal)
break;
//若无空缓存块,则采用随机替换策略,通过随机生成数在组内找一个缓存块进行替换
if (block_i == set_end)
block_i = set_begin + rand() % CACHE_L2_E;
//随机替换找的缓存块先确定其是否脏标签被置位,若是,需要先进行回写操作
int i;
if (cache_L2[block_i].validVal && cache_L2[block_i].dirtyVal) {
//临时缓冲区结合突发读写技术读取需要写入的数据
uint8_t tmp[BURST_LEN << 1];
memset(tmp, 1, sizeof(tmp));
uint32_t block_start_x = (cache_L2[block_i].tag << (CACHE_L2_s + CACHE_b)) | (set << CACHE_b);
for (i = 0; i < CACHE_B / BURST_LEN; i++) {
ddr3_write_me(block_start_x + BURST_LEN * i, cache_L2[block_i].data + BURST_LEN * i, tmp);
}
}
//处理好回写后替换块内数据,从内存中写入
for (i = 0; i < CACHE_B / BURST_LEN; i++) {
ddr3_read_me(block_start + BURST_LEN * i, cache_L2[block_i].data + BURST_LEN * i);
}
cache_L2[block_i].validVal = true;
cache_L2[block_i].dirtyVal = false;
cache_L2[block_i].tag = tag;
return block_i;
}
  1. 更改hwaddr_read()和hwaddr_write() (nemu/src/memory/memory.c)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
uint32_t hwaddr_read(hwaddr_t addr, size_t len) {
int cache_L1_way_1_index = read_cache_L1(addr); // 在L1缓存中查找地址对应的缓存行索引
uint32_t block_bias = addr & (CACHE_B - 1); //计算块内偏移
uint8_t ret[BURST_LEN << 1];
if (block_bias + len > CACHE_B) {
// 需要读取两个缓存块
int cache_L1_way_2_index = read_cache_L1(addr + CACHE_B - block_bias);
memcpy(ret, cache_L1[cache_L1_way_1_index].data + block_bias, CACHE_B - block_bias);
// 单个块内读取
memcpy(ret + CACHE_B - block_bias, cache_L1[cache_L1_way_2_index].data, len - (CACHE_B - block_bias));
} else {
memcpy(ret, cache_L1[cache_L1_way_1_index].data + block_bias, len);
}
int tmp = 0;
uint32_t result = unalign_rw(ret + tmp, 4) & (~0u >> ((4 - len) << 3)); // 处理非对齐访问
return result;
}

void hwaddr_write(hwaddr_t addr, size_t len, uint32_t data) {
write_cache_L1(addr, len, data);
}
  1. 由于被多次调用的读写操作函数位于dram.c文件中的,其中有两个函数是用static封装,则需在dram.c文件中进行全局变量的声明(作为接口调用这两个函数)
1
2
3
4
5
6
void ddr3_read_me(hwaddr_t addr, void* data) {
ddr3_read(addr, data) ;
}
void ddr3_write_me(hwaddr_t addr, void* data, uint8_t* mask) {
ddr3_write(addr, data, mask) ;
}

3. 调试

1
2
make clean
make run

通过即可

阶段二

模拟IA-32的分段机制

  1. 修改kernel/include/common.h:去掉**#define IA32_SEG*的注释
  2. 根据报错信息和指导手册,现在需要补全lgdt指令(修改nemu/include/cpu/reg.h)
  3. 段寄存器索引枚举
1
enum { R_CS, R_DS, R_SS, R_ES };
  1. 定义段寄存器结构
1
2
3
4
5
6
7
8
// CR0 CR3定义在libcommon/x86-inc/cpu.h中,添加头文件
#include "../../lib-common/x86-inc/cpu.h"

typedef struct {
uint16_t selector; // 段选择符
uint32_t base, limit, type; // 段描述符高速缓存 段描述符的信息
} S_reg;

  1. GDT描述符读取结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef struct {
union {
struct {
uint16_t lim1, b1;
};
uint32_t p1;
};
union {
struct {
uint32_t b2 : 8, a : 1, type : 3, s : 1, dpl : 2, p : 1, lim2 : 4;
uint32_t avl : 1, : 1, x : 1, g : 1, b3 : 8;
};
uint32_t p2;
};
}Sreg_info; // 用于读取DGT的内容
Sreg_info sreg_info;
  1. 定义分段机制相关寄存器
1
2
3
4
5
6
7
8
9
10
11
12
13
union { // 段寄存器
struct {
S_reg sreg[4]; // 为了方便swaddr_read和seg_translate
};
struct {
S_reg CS, DS, SS, ES; // 代码段,数据段,堆栈,扩展
};
};
struct {
uint32_t base, limit; // GDT的 首地址 和 限界
} GDTR;
CR0 cr0;
CR3 cr3;
  1. 在restart()函数中初始化分段机制相关的寄存器(修改nemu/src/monitor/monitor.c)
1
2
3
4
5
6
7
8
9
/* 初始化CR0寄存器 - 实模式 */
static void init_cr0() {
cpu.cr0.protect_enable = 0;
cpu.cr0.paging = 0;
}

//restart()中
/* Initialize CR0~~~~. */
init_cr0();
  1. 创建 nemu/src/cpu/exec/data-mov/lgdt.h
1
2
3
4
5
6
#ifndef __LGDT_H__
#define __LGDT_H__

make_helper(lgdt_rm_v);

#endif
  1. 创建 nemu/src/cpu/exec/data-mov/lgdt.c
1
2
3
4
5
6
7
8
9
10
11
#include "cpu/exec/helper.h"

#define DATA_BYTE 2
#include "lgdt-template.h"
#undef DATA_BYTE

#define DATA_BYTE 4
#include "lgdt-template.h"
#undef DATA_BYTE

make_helper_v(lgdt_rm)
  1. 创建 nemu/src/cpu/exec/data-mov/lgdt-template.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "cpu/exec/template-start.h"

#define instr lgdt

static void do_execute() {
/* 目标地址共有 6Bytes 的内容,存放limit和base */
// 16位操作数:16bits limit + 24bits base | 2+3 Bytes
// 32位操作数:16bits limit + 32bits base | 2+4 Bytes
cpu.GDTR.limit = swaddr_read(op_src->addr, 2, R_DS);
if (op_src->size == 2)
cpu.GDTR.base = swaddr_read(op_src->addr + 2, 3, R_DS);
else {
cpu.GDTR.base = swaddr_read(op_src->addr + 2, 4, R_DS);
}
print_asm_template1();
}

make_instr_helper(rm);

#include "cpu/exec/template-end.h"
  1. nemu/src/cpu/exec/exec.c中修改group7的定义
1
2
3
make_group(group7,
inv, inv, lgdt_rm_v, inv,
inv, inv, inv, inv)
  1. nemu/src/cpu/exec/all-instr.h中添加声明
1
#include "data-mov/lgdt.h"
  1. 实现操作CR0和CR3的mov指令,修改nemu/src/cpu/exec/data-mov/mov-template.h:
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
27
28
29
30
31
32
#if DATA_BYTE == 4

make_helper(mov_cr2r) {
uint8_t tmp = instr_fetch(eip + 1, 1);
uint8_t cr = (tmp >> 3) & 7; // 倒数4~6位
uint8_t reg = tmp & 7; // 后3位
if (cr == 0) {
reg_l(reg) = cpu.cr0.val;
print_asm("mov cr0 %%%s", REG_NAME(reg));
}
else if (cr == 3) {
reg_l(reg) = cpu.cr3.val;
print_asm("mov cr3 %%%s", REG_NAME(reg));
}
return 2;
}
make_helper(mov_r2cr) {
uint8_t tmp = instr_fetch(eip + 1, 1);
uint8_t cr = (tmp >> 3) & 7; // 倒数4~6位
uint8_t reg = tmp & 7; // 后3位
if (cr == 0) {
cpu.cr0.val = reg_l(reg);
print_asm("mov %%%s cr0", REG_NAME(reg));
}
else if (cr == 3) {
cpu.cr3.val = reg_l(reg);
print_asm("mov %%%s cr3", REG_NAME(reg));
}
return 2;
}

#endif
  1. nemu/src/cpu/exec/data-mov/mov.h中添加声明:
1
2
make_helper(mov_cr2r);
make_helper(mov_r2cr);
  1. nemu/src/cpu/exec/exec.c的*_2byte_opcode_table*中注册指令:
1
/* 0x20 */	mov_cr2r, inv, mov_r2cr, inv, 
  1. 修改nemu/include/memory/memory.h
1
2
uint32_t swaddr_read(swaddr_t, size_t, uint8_t);
void swaddr_write(swaddr_t, size_t, uint32_t, uint8_t);
  1. 修改nemu/include/cpu/exec/template-start.h中的MEM宏
1
2
#define MEM_R(addr, sreg) swaddr_read(addr, DATA_BYTE, sreg)    // sreg
#define MEM_W(addr, data, sreg) swaddr_write(addr, DATA_BYTE, data, sreg)
  1. nemu/src/memory/memory.c中实现seg_translate函数并修改修改swaddr_readswaddr_write函数(虚拟地址->线性地址)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 添加
#include "cpu/reg.h"

// 添加函数
/* 虚拟地址->线性地址 */
lnaddr_t seg_translate(swaddr_t addr, size_t len, uint8_t sreg) {
if (cpu.cr0.protect_enable == 0) return addr;
return cpu.sreg[sreg].base + addr;
}

// 更改
uint32_t swaddr_read(swaddr_t addr, size_t len, uint8_t sreg) {
assert(len == 1 || len == 2 || len == 4);
lnaddr_t lnaddr = seg_translate(addr, len, sreg);
return lnaddr_read(lnaddr, len);
}

void swaddr_write(swaddr_t addr, size_t len, uint32_t data, uint8_t sreg) {
assert(len == 1 || len == 2 || len == 4);
lnaddr_t lnaddr = seg_translate(addr, len, sreg);
lnaddr_write(lnaddr, len, data);
}
  1. 修改nemu/include/cpu/decode/operand.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef struct {
uint32_t type;
size_t size;
union {
uint32_t reg;
// swaddr_t addr;
struct {
swaddr_t addr;
uint8_t sreg;
};
uint32_t imm;
int32_t simm;
};
uint32_t val;
char str[OP_STR_SIZE];
} Operand;
  1. 修改nemu/src/cpu/decode/modrm.c(在read_ModR_M函数中设置rm->sreg)
1
2
3
4
5
6
7
8
9
10
11
// read_ModR_M()修改部分
else {
int instr_len = load_addr(eip, &m, rm);
// rm->val = swaddr_read(rm->addr, rm->size);
if (rm->reg == R_EBP || rm->reg == R_ESP) { // 栈相关,用SS
rm->sreg = R_SS;
}
else rm->sreg = R_DS;
rm->val = swaddr_read(rm->addr, rm->size, rm->sreg);
return instr_len;
}

在load_addr()中:

1
rm->reg = m->R_M;
  1. 之后需要修改所有涉及到swaddr函数的参数和MEM宏定义的参数,排查方式:
1
2
3
4
5
6
7
8
grep -r "swaddr_read" nemu/src/ --include="*.c" -n
grep -r "swaddr_write" nemu/src/ --include="*.c" -n
grep -r "MEM_R" nemu/src/ --include="*.c" -n
grep -r "MEM_W" nemu/src/ --include="*.c" -n
grep -r "MEM_R" nemu/include/ --include="*.h" -n
grep -r "MEM_W" nemu/include/ --include="*.h" -n
find nemu/src/cpu/exec/ -name "*.h" -exec grep -l "MEM_R\|MEM_W" {} \;
find nemu/src/cpu/exec/ -name "*.c" -exec grep -l "MEM_R\|MEM_W" {} \;

经过排查之后,修改如下:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// nemu/src/cpu/exec/data-mov/leave.c
cpu.ebp = swaddr_read(cpu.esp, 4, R_SS); // 堆栈 SS

// nemu/src/cpu/exec/control/ret.c
cpu.eip = swaddr_read(cpu.esp, 4, R_SS) - 1;
cpu.eip = swaddr_read(cpu.esp, 4, R_SS) - 1-2;

// nemu/src/monitor/debug/expr.c 429行附近
return swaddr_read(val, 4, R_DS); // 解引用 DS

// nemu/src/monitor/debug/ui.c
// cmd_x() 138行附近
uint32_t value = swaddr_read(address + i*4, 4, R_DS);
// cmd_bt() 275-278行 282-283行附近
PartOfStackFrame.args[0] = swaddr_read(ebp + 8, 4, R_SS);
PartOfStackFrame.args[1] = swaddr_read(ebp + 12, 4, R_SS);
PartOfStackFrame.args[2] = swaddr_read(ebp + 16, 4, R_SS);
PartOfStackFrame.args[3] = swaddr_read(ebp + 20, 4, R_SS);

eip = swaddr_read(ebp + 4, 4, R_SS);
ebp = swaddr_read(ebp, 4, R_SS);

// nemu/src/cpu/exec/control/call-template.h
MEM_W(reg_l(R_ESP), cpu.eip+len+1, R_SS);
MEM_W(reg_l(R_ESP), cpu.eip + len + 1, R_SS);

// nemu/src/cpu/exec/string/movs-template.h
// MEM_W(cpu.edi, MEM_R(cpu.esi));
uint32_t data = swaddr_read(cpu.esi, DATA_BYTE, R_DS);
swaddr_write(cpu.edi, DATA_BYTE, data, R_ES);

// nemu/src/cpu/exec/string/lods-template.h
REG(R_EAX) = MEM_R(cpu.esi, R_DS);

// nemu/src/cpu/exec/string/scas-template.h
DATA_TYPE src = MEM_R(cpu.edi, R_ES);;

// nemu/src/cpu/exec/string/stos-template.h
MEM_W(cpu.edi, REG(R_EAX), R_ES);

// nemu/include/cpu/helper.h 中 instr_fetch()
return swaddr_read(addr, len, R_CS);

// nemu/src/cpu/exec/data-mov/push-template.h
swaddr_write(cpu.esp, 4, op_src->val, R_SS);

// nemu/src/cpu/exec/data-mov/mov-template.h
// MEM_W(addr, REG(R_EAX));
swaddr_write(addr, DATA_BYTE, REG(R_EAX), R_DS);

// REG(R_EAX) = MEM_R(addr);
REG(R_EAX) = swaddr_read(addr, DATA_BYTE, R_DS);

// nemu/src/cpu/exec/data-mov/pop-template.h
// OPERAND_W(op_src,swaddr_read(cpu.esp, 4));
// reg_l(R_ESP) += 4;
uint32_t val = swaddr_read(cpu.esp, DATA_BYTE, R_SS);
OPERAND_W(op_src, val);
cpu.esp += DATA_BYTE;


// nemu/src/cpu/exec/data-mov/lgdt-template.h
cpu.GDTR.limit = swaddr_read(op_src->addr, 2, R_DS);
cpu.GDTR.base = swaddr_read(op_src->addr + 2, 3, R_DS);
cpu.GDTR.base = swaddr_read(op_src->addr + 2, 4, R_DS);

// nemu/src/cpu/decode/decode-template.h
// else if(op->type == OP_TYPE_MEM) { swaddr_write(op->addr, op->size, src); }
else if(op->type == OP_TYPE_MEM) { swaddr_write(op->addr, op->size, src, op->sreg); }
  1. 实现段寄存器mov:nemu/src/cpu/exec/data-mov/mov-template.h中添加:
1
2
3
4
5
6
7
8
9
10
11
12
13
#if DATA_BYTE == 2

make_helper(mov_sreg2rm) {
uint8_t modrm = instr_fetch(eip + 1, 1);
uint8_t sreg = (modrm >> 3) & 7;
uint8_t reg = (modrm & 7);
cpu.sreg[sreg].selector = reg_w(reg);
sreg_set(sreg); // 更新段描述符高速缓存
print_asm("mov %s sreg%d", REG_NAME(reg), sreg);
return 2;
}

#endif

在mov.h添加声明

1
make_helper(mov_sreg2rm);
  1. nemu/src/cpu/reg.c中的更新描述符cache
1
2
3
4
5
6
7
8
9
10
void sreg_set(uint8_t id) {	// 根据段描述符 更新 段描述符高速缓存
lnaddr_t chart_addr = cpu.GDTR.base + ((cpu.sreg[id].selector >> 3) << 3); //段描述符地址
sreg_info.p1 = lnaddr_read(chart_addr, 4);
sreg_info.p2 = lnaddr_read(chart_addr + 4, 4);
cpu.sreg[id].base = sreg_info.b1 + (sreg_info.b2 << 16) + (sreg_info.b3 << 24);
cpu.sreg[id].limit = sreg_info.lim1 + (sreg_info.lim2 << 16) + (0xfff << 24);
if (sreg_info.g == 1) { //粒度位(G):0表示段界限单位是B;1表示4KB
cpu.sreg[id].limit <<= 12;
}
}
  1. nemu/include/cpu/reg.h中添加声明
1
void sreg_set(uint8_t id);
  1. nemu/src/cpu/exec/exec.c中注册
1
/* 0x8c */	inv, lea, mov_sreg2rm, inv,
  1. nemu/src/monitor/monitor.c中添加调用
1
2
3
4
5
6
7
static void init_cs() {
cpu.CS.base = 0, cpu.CS.limit = 0xffffffff;
}

// restart()中
/* Initialize CS~~~~. */
init_cs();
  1. nemu/src/cpu/exec/control/jmp-template.h中添加ljmp指令
1
2
3
4
5
6
7
make_helper(ljmp) {
cpu.eip = instr_fetch(cpu.eip + 1, 4) - 7; // 后面eip会+7,所以先-7
cpu.CS.selector = instr_fetch(cpu.eip + 1 + 4, 2); // 设置CS寄存器
sreg_set(R_CS); // 更新CS描述符高速缓存
print_asm("ljmp 0x%x 0x%x", instr_fetch(cpu.eip + 1 + 4, 2), instr_fetch(cpu.eip + 1, 4));
return 7;
}

在jmp.h中声明

1
make_helper(ljmp);
  1. nemu/src/cpu/exec/exec.c的opcode_table中注册
1
/* 0xe8 */	call_i_v, jmp_si_l, ljmp, jmp_si_b,
  1. 进行检测
1
2
3
4
5
6
sudo docker start nemu-image
sudo docker exec -it nemu-image /bin/bash

make clean
make
make test

结果如下:
运行结果

阶段三

在阶段二的基础上实现分页机制,这次还是必做选做一起搞了

  1. 添加CR3寄存器(阶段二已经完成了)
  2. 为CR0寄存器添加PG位的功能。装载CR0后, 如果发现CR0的PE位和PG位均为1, 则开启 IA-32 分页机制, 从此所有线性地址的访问(包括 lnaddr_read(), lnaddr_write())都需要经过页级地址转换。在 restart()函数中对CR0寄存器进行初始化时, PG位要被置0(阶段二已经完成了)
  3. 修改kernel/include/common.h:去掉**#define IA32_PAGE的注释,修改kernel/Makefile.part*中的链接选项,使用虚拟地址,通过页表映射到物理地址0x00100000
1
kernel_LDFLAGS := -m elf_i386 -e start -Ttext=0xc0100000 
  1. 修改nemu/src/memory/memory.c中的lnaddr_read(),lnaddr_write()函数实现页级地址转换
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// 添加头文件
#include "memory/tlb.h"
#include "../../../lib-common/x86-inc/mmu.h"

// 添加转换函数
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;
int index = read_tlb(addr);
if (index != -1)
return (tlb[index].data << 12) + offset;
/* 读取页表信息 */
uint32_t tmp = (cpu.cr3.page_directory_base << 12) + dictionary * 4; // 页目录基地址 + 页目录号 * 页表项大小
PDE dictionary_;
PTE page_;
dictionary_.val = hwaddr_read(tmp, 4);
tmp = (dictionary_.page_frame << 12) + page * 4; // 二级页表基地址 + 页号 + 页表项大小
page_.val = hwaddr_read(tmp, 4);
Assert(dictionary_.present == 1, "dirctionary present");
Assert(page_.present == 1, "second present");
hwaddr_t addr_ = (page_.page_frame << 12) + offset;
write_tlb(addr, addr_);
return addr_;
}

// 更改
uint32_t lnaddr_read(lnaddr_t addr, size_t len) {
assert(len == 1 || len == 2 || len == 4);
uint32_t offset = addr & 0xfff;
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;
}
else {
hwaddr_t hwaddr = page_translate(addr);
return hwaddr_read(hwaddr, len);
}
}

void lnaddr_write(lnaddr_t addr, size_t len, uint32_t data) {
assert(len == 1 || len == 2 || len == 4);
uint32_t offset = addr & 0xfff;
if ((int64_t)(offset + len) > 0x1000) { // 跨页
size_t l = 0xfff - offset + 1; // 低位最多几个字节同页
lnaddr_write(addr, l, data & ((1 << (l * 8)) - 1)); // 低
lnaddr_write(addr + l, len - l, data >> (l * 8)); //高
}
else {
hwaddr_t hwaddr = page_translate(addr);
return hwaddr_write(hwaddr, len, data);
}
}
  1. 实现cld指令
1
2
3
4
5
6
7
8
9
// 创建nemu/src/cpu/exec/string/cld.h
#ifndef __CLD_H__
#define __CLD_H__

make_helper(cld);

#endif

// 创建nemu/src/cpu/exec/string/cld.c
  1. 实现std指令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 创建nemu/src/cpu/exec/string/std.h
#ifndef __STD_H__
#define __STD_H__

make_helper(std);

#endif

// 创建nemu/src/cpu/exec/string/std.c
#include "cpu/exec/helper.h"

make_helper(std) {
cpu.eflags.DF = 1;
print_asm("std\n");
return 1;
}

注册(nemu/src/cpu/exec/exec.c)

1
/* 0xfc */	cld, std, group4, group5,

声明(nemu/src/cpu/exec/all-instr.h)

1
2
#include "string/cld.h"
#include "string/std.h"
  1. 为用户进程分配虚拟空间,修改kernel/src/elf/elf.c
1
2
3
4
5
6
7
8
9
10
// 添加头文件
#include <stdio.h>

// 修改loader()
// uint32_t addr = ph->p_vaddr;
ph->p_vaddr = mm_malloc(ph->p_vaddr, ph->p_memsz);
ramdisk_read((void*)ph->p_vaddr, ph->p_offset, ph->p_filesz);
// ramdisk_read((void *)addr, ELF_OFFSET_IN_DISK + ph->p_offset,ph->p_filesz);
// memset((void *)addr + ph->p_filesz,0,ph->p_memsz - ph->p_filesz);
memset((void*)(ph->p_vaddr + ph->p_filesz), 0, ph->p_memsz - ph->p_filesz);
  1. nemu/src/memory/memory.c中实现用于调试的页表转换函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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; // 页目录基地址 + 页目录号 * 页表项大小
PDE dictionary_, page_;
dictionary_.val = hwaddr_read(tmp, 4);
tmp = (dictionary_.page_frame << 12) + page * 4; // 二级页表基地址 + 页号 + 页表项大小
page_.val = hwaddr_read(tmp, 4);
if (dictionary_.present != 1) {
printf("dirctionary present != 1\n");
return 0;
}
if (page_.page_frame != 1) {
printf("second page table present != 1\n");
return 0;
}
return (page_.page_frame << 12) + offset;
}

nemu/include/memory/memory.h中添加声明

1
hwaddr_t cmd_page(lnaddr_t addr);

修改nemu/src/monitor/debug/ui.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "memory/memory.h"

static int cmd_translate(char *args);

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

// 在table中添加
{ "page", "Translate virtual address to physical address", cmd_translate }
  1. 实现tlb:创建nemu/include/memory/tlb.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#ifndef __TLB_H__
#define __TLB_H__

#include "common.h"

#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

创建nemu/src/memory/tlb.c

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
27
28
29
30
31
32
33
34
35
#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;
}

修改nemu/src/monitor/monitor.c

1
2
3
4
5
6
7
// 添加
#include "memory/cache.h"

void init_tlb();

/* Initialize tlb~~~~. */
init_tlb();
  1. 实现create_video_mapping()函数来为用户进程创建显存的恒等映射(kernel/src/memory/vmem.c)
1
2
3
4
5
6
7
8
9
10
11
// 在void create_video_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);
}
  1. 更改nemu/src/cpu/exec/exec.c的opcode
1
/* 0x2c */	inv, sub_i2a_v, inv, inv,
  1. 增添sub_i2a()函数
    nemu/src/cpu/exec/arith/sub-template.h
1
make_instr_helper(i2a)

nemu/src/cpu/exec/arith/sub.c

1
2
3
4
5
#define DATA_BYTE 1
#include "sub-template.h"
#undef DATA_BYTE

make_helper_v(sub_i2a)

nemu/src/cpu/exec/arith/sub.h

1
make_helper(sub_i2a_v);
  1. 增添push_i_v()
    nemu/src/cpu/exec/arith/push-template.h
1
2
3
4
5
6
7
// 更改do_execute()
op_src->val = op_src->val;
reg_l(R_SP) -= 4;
swaddr_write(reg_l(R_SP), 4, (DATA_TYPE_S)op_src->val, R_SS);

// 添加
make_instr_helper(i)

nemu/src/cpu/exec/arith/push.c

1
make_helper_v(push_i)

nemu/src/cpu/exec/arith/push.h

1
2
3
4
make_helper(push_i_v);
更改*nemu/src/cpu/exec/exec.c*的opcode
```c
/* 0x68 */ push_i_v, imul_i_rm2r_v, push_si_b, imul_si_rm2r_v,
  1. 注册jg_l(),更改nemu/src/cpu/exec/exec.c的opcode
1
/* 0x8c */	inv, jge_l, jle_l, jg_l, 
  1. 进行检测
1
2
make clean
make test

结果如图:
运行结果
16. 提交(别翻指导书了这里直接告诉你哈哈啊哈哈)

1
make submit

NEMU_PA3经6次秽土重生后结束!✿✿ヽ(°▽°)ノ✿完结撒花!