要开始实验,切换到 PGTBL 分支:

1
2
3
git fetch
git checkout pgtbl
make clean

Inspect a user-process page table

这个实验要求分析用户进程的页表输出,解释每个页表条目的逻辑内容和权限位。

这里直接按照教程来做就行

1
2
3
make clean
make qemu
pgtbltest

输出如下:

image-20260107140238299

输出格式说明:

va:虚拟地址 pte:页表条目完整值
pa:物理地址 perm:权限位(pte的低8位)

在 RISC-V 中,权限位定义如下(参考 kernel/riscv.h):

1
2
3
4
5
#define PTE_V (1L << 0) // valid
#define PTE_R (1L << 1)
#define PTE_W (1L << 2)
#define PTE_X (1L << 3)
#define PTE_U (1L << 4) // user can access

image-20260107140831992

Speed up system calls

这个实验的目标是通过在用户空间和内核之间共享一个只读页面来加速 getpid()系统调用

正常情况下获取 PID 的系统调用流程:用户程序调用、编译为汇编、进入内核、保存用户上下文、进入内核 C 代码、系统调用分发、实际获取 PID、返回用户空间

而共享页面加速后:用户程序直接读取 USYSCALL 页面

先在kernel/memlayout.h中定义共享页面的虚拟地址

1
2
3
4
5
6
#define USYSCALL (TRAPFRAME - PGSIZE)

// 共享数据结构
struct usyscall {
int pid; // 进程ID
};

这样就可以插入共享页面了

1
2
3
4
5
6
7
8
9
10
高地址
0xFFFFFFFF ──
0xFFFFF000 ── trampoline (内核-用户切换代码)
0xFFFFE000 ── trapframe (寄存器保存区)
0xFFFFD000 ── usyscall ← 新增的共享页面
0xFFFFC000 ── guard page (保护页面,防止栈溢出)
0xFFFFB000 ── 用户栈顶
...
0x00000000 ── 代码和数据
低地址

为什么在 memlayout.h中只有 usyscall定义,而其他页面(如 trapframetrampoline)没有定义?

因为用户程序需要知道 usyscall的结构,但不需要知道其他页面的结构。

image-20260108200646643

kernel/proc.hstruct proc中添加:

1
2
3
4
5
struct proc {
// ... 已有字段 ...
struct usyscall *usyscall; // 用户系统调用共享页面
// ... 其他字段 ...
};

kernel/proc.c中修改进程创建函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static struct proc*
allocproc(void)
{
// ... 已有代码 ...

// 分配 usyscall 页面
if((p->usyscall = (struct usyscall *)kalloc()) == 0){
freeproc(p);
release(&p->lock);
return 0;
}

// 初始化 pid(在 p->pid 设置后)
p->usyscall->pid = p->pid;

// ... 已有代码 ...
return p;
}

修改页表创建函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pagetable_t
proc_pagetable(struct proc *p)
{
....

if(mappages(pagetable, TRAPFRAME, PGSIZE,
(uint64)(p->trapframe), PTE_R | PTE_W) < 0){
uvmunmap(pagetable, TRAMPOLINE, 1, 0);
uvmfree(pagetable, 0);
return 0;
}

// 映射 usyscall 页面(只读,用户可访问)
if(mappages(pagetable, USYSCALL, PGSIZE,
(uint64)(p->usyscall), PTE_R | PTE_U) < 0){
uvmunmap(pagetable, TRAMPOLINE, 1, 0);
uvmunmap(pagetable, TRAPFRAME, 1, 0);
uvmfree(pagetable, 0);
return 0;
}

return pagetable;
}

修改进程销毁函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static void
freeproc(struct proc *p)
{
if(p->trapframe)
kfree((void*)p->trapframe);
p->trapframe = 0;

if(p->usyscall) // 添加这行
kfree((void*)p->usyscall); // 添加这行
p->usyscall = 0; // 添加这行

if(p->pagetable)
proc_freepagetable(p->pagetable, p->sz);
p->pagetable = 0;
// ... 其他清理代码 ...
}

修改页表释放函数

1
2
3
4
5
6
7
8
void
proc_freepagetable(pagetable_t pagetable, uint64 sz)
{
uvmunmap(pagetable, TRAMPOLINE, 1, 0);
uvmunmap(pagetable, TRAPFRAME, 1, 0);
uvmunmap(pagetable, USYSCALL, 1, 0); // 添加这行
uvmfree(pagetable, sz);
}

然后make qemu

image-20260108201238146

Which other xv6 system call(s) could be made faster using this shared page? Explain how.

1
2
3
4
5
6
7
8
9
10
11
12
struct usyscall_ext {
int pid; // 当前进程ID
int ppid; // 父进程ID
int uid; // 用户ID
int gid; // 组ID
uint64 uptime; // 系统运行时间(滴答数)
uint64 starttime; // 进程启动时间
uint64 cputime; // 进程CPU时间
uint64 mem_usage; // 内存使用量
char cwd[MAXPATH]; // 当前工作目录
// 可以添加更多字段
}

Use superpages