Lab3 页表和进程的内存管理

0. 实验前:切换至Lab3分支

在xv6-labs-2021文件夹下运行终端,并且运行以下命令:

1
2
3
$ git fetch
$ git checkout syscall
$ make clean

image-20221031101541984

发现git check out命令发生错误,提示本地保存需要提交或存储后才能切换分支。

运行命令 git stash

image-20221031102314269

切换成功。

1. 页表和进程的内存管理

1.1 Speed up system calls

1
Some operating systems (e.g., Linux) speed up certain system calls by sharing data in a read-only region between userspace and the kernel. This eliminates the need for kernel crossings when performing these system calls. To help you learn how to insert mappings into a page table, your first task is to implement this optimization for the getpid() system call in xv6.
1.1.1 解题过程
  • 在kernel/proc.h中的struct proc在增加一个域,存储共享内存块的物理地址。

image-20221031103641834

  • 在kernel/proc.c的allocproc函数中增加申请共享内存页。

image-20221031103846850

  • 在kernel/proc.c的proc_pagetable函数中增加在内核中共享内存页的初始化,以及对共享内存块的页表初始化。

image-20221031103912918

  • 在kernel/proc.c的freeproc函数中增加释放共享内存块。

image-20221031104000138

  • 在kernel/proc.c的proc_freepagetable函数中增加一行释放页表中共享内存页项。

image-20221031104051589

1.1.2 代码
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
// kernel/proc.h

struct proc *parent;
...
struct inode *cwd;
struct usyscall *usyscall;

// kernel/proc.c
allocproc(void)
{
struct proc *p;
...
if((p->usyscall = (struct usyscall *)kalloc()) == 0){
freeproc(p);
release(&p->lock);
return 0;
}
...
}

extern pagetable_t kernel_pagetable;

...
pagetable_t
proc_pagetable(struct proc *p)


static void
freeproc(struct proc *p)
{
...
if(p->usyscall)
kfree((void*)p->usyscall);
p->usyscall = 0;
...
}

void
proc_freepagetable(pagetable_t pagetable, uint64 sz)
{
...
uvmunmap(pagetable, USYSCALL, 1, 0);
}

1.2 Print a page table

1
To help you visualize RISC-V page tables, and perhaps to aid future debugging, your second task is to write a function that prints the contents of a page table.
1.2.1 解题过程
  • 模仿freewalk()编写vmprint()函数,并添加在kernel/vm.c中。

image-20221031104941589

  • 在kernel/defs.h中定义vmprint的原型,以便可以从exec.c调用它。

image-20221031105014675

  • 在kernel/exec.c中的返回argc之前插入if (p->pid==1) vmprint(p->pagetable),以输出第一个进程的页表。

image-20221031105046623

1.2.2 代码
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
// kernel/vm.c

void
printwalk(pagetable_t pagetable, uint level) {
char* prefix;
if (level == 2) prefix = "..";
else if (level == 1) prefix = ".. ..";
else prefix = ".. .. ..";
for(int i = 0; i < 512; i++){
pte_t pte = pagetable[i];
if(pte & PTE_V){
uint64 pa = PTE2PA(pte);
printf("%s%d: pte %p pa %p\n", prefix, i, pte, pa);
if((pte & (PTE_R|PTE_W|PTE_X)) == 0){
printwalk((pagetable_t)pa, level - 1);
}
}
}
}

void
vmprint(pagetable_t pagetable) {
printf("page table %p\n", pagetable);
printwalk(pagetable, 2);
}

//kernel/defs.h

// vm.c
...
void vmprint(pagetable_t);

//kernel/exec.c

...

if (p->pid == 1)
vmprint(p->pagetable);

return argc; // this ends up in a0, the first argument to main(argc, argv)
1.2.3 运行结果测试

image-20221031105438304

1.3 Detecting which pages have been accessed

1
Some garbage collectors (a form of automatic memory management) can benefit from information about which pages have been accessed (read or write). In this part of the lab, you will add a new feature to xv6 that detects and reports this information to userspace by inspecting the access bits in the RISC-V page table. The RISC-V hardware page walker marks these bits in the PTE whenever it resolves a TLB miss.
1.3.1 解题过程
  • 在kernel/riscv.h中定义常量PTE_A。

image-20221031105812407

  • 在kernel/sysproc.c中编写sys_pgaccess函数。

image-20221031105853026

1.3.2 代码
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
// kernel/riscv.h

...
#define PTE_A (1L << 6)

// kernel/sysproc.c
#ifdef LAB_PGTBL
int
sys_pgaccess(void)
{
uint64 base;
uint64 mask;
int len;

pagetable_t pagetable = 0;
unsigned int procmask = 0 ;
pte_t *pte;

struct proc *p = myproc();

if(argaddr(0, &base) < 0 || argint(1, &len) < 0 || argaddr(2, &mask) < 0)
return -1;
if (len > sizeof(int)*8)
len = sizeof(int)*8;

for(int i=0; i<len; i++) {
pagetable = p->pagetable;

if(base >= MAXVA)
panic("pgaccess");

for(int level = 2; level > 0; level--) {
pte = &pagetable[PX(level, base)];
if(*pte & PTE_V) {
pagetable = (pagetable_t)PTE2PA(*pte);
} else {
return -1;
}
}
pte = &pagetable[PX(0, base)];
if(pte == 0)
return -1;
if((*pte & PTE_V) == 0)
return -1;
if((*pte & PTE_U) == 0)
return -1;
if(*pte & PTE_A) {
procmask = procmask | (1L << i);
*pte = *pte & (~PTE_A);
}
base += PGSIZE;
}

pagetable = p->pagetable;
return copyout(pagetable, mask, (char *) &procmask, sizeof(unsigned int));
}
#endif


2.实验心得

​ 一些操作系统(如Linux)通过在用户空间和内核之间的只读区域共享数据来加速某些系统调用。其中为了实现这一过程需要先添加一个存储共享内存块的物理地址->增加申请共享内存页->增加在内核中共享内存页的初始化,以及对共享内存块的页表初始化->增加释放共享内存块->释放页表中共享内存页项。

​ 而当需要模拟系统遍历页表的时候,可以先通过观察系统本身是如何遍历的,然后按着freewalk()函数编写输出遍历打印页表的函数vmprint()。