文章目录
实验十 文件内存映射
一、代码理解
1.物理内存布局
-
启动 ROM:
0x00001000
:启动 ROM,由 QEMU 提供。
-
CLINT(Core Local Interruptor):
0x02000000
:CLINT 寄存器基地址。CLINT_MTIMECMP(hartid)
:每个 hart(硬件线程)的时间比较寄存器地址。CLINT_MTIME
:自启动以来的周期数寄存器地址。
-
PLIC(Platform-Level Interrupt Controller):
0x0C000000
:PLIC 寄存器基地址。PLIC_PRIORITY
、PLIC_PENDING
、PLIC_MENABLE(hart)
、PLIC_SENABLE(hart)
、PLIC_MPRIORITY(hart)
、PLIC_SPRIORITY(hart)
、PLIC_MCLAIM(hart)
、PLIC_SCLAIM(hart)
:PLIC 相关寄存器的偏移地址。
-
UART0:
0x10000000
:UART0 寄存器基地址。UART0_IRQ
:UART0 的中断号。
-
VIRTIO0:
0x10001000
:VirtIO 磁盘设备的寄存器基地址。VIRTIO0_IRQ
:VirtIO 磁盘设备的中断号。
-
内核使用的物理内存:
0x80000000
:内核代码和数据的起始地址。PHYSTOP
:内核使用的物理内存的结束地址(0x80000000 + 128MB
)。
2.内核内存布局
-
内核基地址:
KERNBASE
:内核代码和数据的起始物理地址(0x80000000
)。
-
物理内存结束地址:
PHYSTOP
:内核使用的物理内存的结束地址(0x80000000 + 128MB
)。
-
跳板页(Trampoline Page):
TRAMPOLINE
:跳板页的地址,位于用户和内核空间的最高地址(MAXVA - PGSIZE
)。
-
内核栈:
KSTACK(p)
:每个 hart 的内核栈地址,位于跳板页下方,每个栈被无效的守护页包围。
-
陷阱帧(Trapframe):
TRAPFRAME
:陷阱帧的地址,位于跳板页下方(TRAMPOLINE - PGSIZE
)。
3.用户内存布局
- 用户内存起始地址:
- 从地址
0
开始,依次是:- 代码段(text)
- 原始数据和 bss 段
- 固定大小的栈
- 可扩展的堆
- 陷阱帧(
TRAPFRAME
) - 跳板页(
TRAMPOLINE
)
- 从地址
二、mmap
1.题目描述
mmap和munmap系统调用允许 UNIX 程序对其地址空间施加详细控制。它们可用于在进程之间共享内存、将文件映射到进程地址空间,以及作为用户级页面错误方案(如讲座中讨论的垃圾收集算法)的一部分。在本实验中,您将向 xv6 添加mmap和munmap , 重点关注内存映射文件。
2.解答
要实现 mmap
和 munmap
系统调用,并在 xv6 内核中支持内存映射文件.
a. 添加 mmap
和 munmap
系统调用
首先,在 kernel/syscall.h
中定义系统调用号:
#define SYS_mmap 22
#define SYS_munmap 23
在 kernel/syscall.c
中添加系统调用入口:
extern uint64 sys_mmap(void);
extern uint64 sys_munmap(void);
static uint64 (*syscalls[])(void) = {
[SYS_mmap] sys_mmap,
[SYS_munmap] sys_munmap,
};
在 kernel/sysfile.c
中实现 sys_mmap
和 sys_munmap
:
uint64 sys_mmap(void) {
// For now, just return errors.
return -1;
}
uint64 sys_munmap(void) {
// For now, just return errors.
return -1;
}
b. 定义 VMA 结构
在 kernel/proc.h
中定义 VMA 结构:
#define NVMA 16
struct vma {
uint64 addr; // Start address
uint64 length; // Length of the mapping
int prot; // Protection flags (PROT_READ, PROT_WRITE, etc.)
int flags; // Mapping flags (MAP_SHARED, MAP_PRIVATE, etc.)
struct file *file; // File being mapped
uint64 offset; // Offset in the file
};
struct proc {
struct vma vmas[NVMA];
int nvma;
};
c. 实现 mmap
在 kernel/sysfile.c
中实现 sys_mmap
:
uint64 sys_mmap(void) {
uint64 addr;
uint64 length;
int prot;
int flags;
int fd;
uint64 offset;
if (argaddr(0, &addr) < 0 || argint(1, &length) < 0 || argint(2, &prot) < 0 ||
argint(3, &flags) < 0 || argfd(4, &fd, 0) < 0 || argaddr(5, &offset) < 0) {
return -1;
}
struct file *f = filedup(myproc()->ofile[fd]);
if (f == 0) {
return -1;
}
struct proc *p = myproc();
if (p->nvma >= NVMA) {
fileclose(f);
return -1;
}
// Find an unused region in the process's address space.
uint64 map_addr = p->sz;
p->sz += length;
// Add a VMA to the process's table of mapped regions.
p->vmas[p->nvma].addr = map_addr;
p->vmas[p->nvma].length = length;
p->vmas[p->nvma].prot = prot;
p->vmas[p->nvma].flags = flags;
p->vmas[p->nvma].file = f;
p->vmas[p->nvma].offset = offset;
p->nvma++;
return map_addr;
}
d. 实现页面错误处理
在 kernel/trap.c
中处理页面错误:
void usertrap(void) {
// ...
if(r_scause() == 13 || r_scause() == 15) {
uint64 va = r_stval();
struct proc *p = myproc();
for (int i = 0; i < p->nvma; i++) {
if (va >= p->vmas[i].addr && va < p->vmas[i].addr + p->vmas[i].length) {
uint64 pa = kalloc();
if (pa == 0) {
p->killed = 1;
break;
}
ilock(p->vmas[i].file->ip);
readi(p->vmas[i].file->ip, 0, pa, p->vmas[i].offset + (va - p->vmas[i].addr), PGSIZE);
iunlock(p->vmas[i].file->ip);
if (mappages(p->pagetable, va, PGSIZE, pa, p->vmas[i].prot) != 0) {
kfree(pa);
p->killed = 1;
}
break;
}
}
}
// ...
}
e. 实现 munmap
在 kernel/sysfile.c
中实现 sys_munmap
:
uint64 sys_munmap(void) {
uint64 addr;
uint64 length;
if (argaddr(0, &addr) < 0 || argint(1, &length) < 0) {
return -1;
}
struct proc *p = myproc();
for (int i = 0; i < p->nvma; i++) {
if (addr == p->vmas[i].addr && length == p->vmas[i].length) {
uvmunmap(p->pagetable, addr, length / PGSIZE, 1);
fileclose(p->vmas[i].file);
p->nvma--;
memmove(&p->vmas[i], &p->vmas[i + 1], (p->nvma - i) * sizeof(struct vma));
return 0;
}
}
return -1;
}
f. 修改 exit
和 fork
在 kernel/proc.c
中修改 exit
和 fork
:
void exit(int status) {
struct proc *p = myproc();
// Unmap all mapped regions.
for (int i = 0; i < p->nvma; i++) {
uvmunmap(p->pagetable, p->vmas[i].addr, p->vmas[i].length / PGSIZE, 1);
fileclose(p->vmas[i].file);
}
p->nvma = 0;
// ...
}
int fork(void) {
int i, pid;
struct proc *np;
struct proc *p = myproc();
// ...
// Copy VMA information.
np->nvma = p->nvma;
memmove(np->vmas, p->vmas, sizeof(struct vma) * p->nvma);
for (i = 0; i < p->nvma; i++) {
filedup(p->vmas[i].file);
}
// ...
}
三、心得体会
a.研〇趣事
老师:我发现你性格有点急
我:啥?(他发现我最近在实习?!!)
老师:我发现你性格有点急,每次你说完就撂。
同门:我每次等老师说完拜拜,他退出会议我再退
我: 还好没发现我实习,呜呜,之后再也不一说完再见就结束会议了