实验讲解
操作系统可以使用页表硬件的许多巧妙技巧之一是用户空间堆内存的惰性分配。 Xv6应用程序使用sbrk()系统调用向内核请求堆内存。 在我们给您的内核中,sbrk()分配物理内存并将其映射到进程的虚拟地址空间。 内核为大型请求分配和映射内存可能需要很长时间。 例如,一个千兆字节由262,144个4096字节的页面组成; 即使每个分配都是便宜的,这也是一个巨大的分配。 另外,某些程序分配的内存超过了它们实际使用的内存(例如,实现稀疏数组),或者在使用之前分配了足够的内存。 为了使sbrk()在这些情况下更快完成,复杂的内核会延迟分配用户内存。 也就是说,sbrk()不会分配物理内存,而只是记住分配了哪些用户地址,并将这些地址在用户页表中标记为无效。 当进程首次尝试使用延迟分配内存的任何给定页面时,CPU会生成一个页面错误,内核将通过分配物理内存,将其清零并对其进行映射来处理该页面错误。 您将在本练习中将此延迟分配功能添加到xv6。
- sbrk函数不在分配虚实地址映射关系,而是只把proc->sz += n,不做growup()
- 当发生缺页中断时,去判断va < proc->sz,说明当前页是该进程的,只是未做映射而已,说明是懒加载,那现在再去做一个映射
关于brk与sbrk:https://www.163.com/dy/article/GD0EPO3U05373I2Y.html
Eliminate allocation from sbrk()
lazy allocation就是在用户程序通过sbrk系统调用申请内存空间时,并不当场给它分配内存,而仅仅是增加p->sz。当用户程序使用申请的内存时会产生page fault而trap到内核态,此时再分配内存。
这里去掉了分配内存的部分,增加了proc的sz
之后运行xv6,执行echo hi会产生page fault。这是因为执行echo hi时系统会使用sbrk分配一些内存,当访问到这一部分内存时,由于我们修改了的sbrk并没有真正分配物理内存,就会导致访问的页面不存在。
产生page fault后看到有个panic。这是因为产生页面中断后,内核的做法会杀死当前进程(在usertrap()中)。进程退出的时候需要free页表,free页表首先会把页表对应项unmap再free物理内存。unmap的时候是从虚拟地址0到p->sz逐页unmap的,在我们修改的sbrk函数中增加了p->sz,对应的内存并没有分配并map。因此在uvmunmap的逻辑中就会panic。
Lazy allocation
在trap.c中加上处理page fault的逻辑,使得echo hi能够正常运行。
根据提示,我们去做一个r_scause = 13 || r_scause == 15的判断,如果是缺页异常的话,就去看该虚拟地址是否合法(va >= p->sz),合法的话,我们再去引入该虚拟地址对应的物理内存。
else if (r_scause() == 13 || r_scause() == 15){uint64 va = r_stval();if (is_lazy_alloc_va(va)) { // 该虚拟地址是否合法if (lazy_alloc(va) < 0) {printf("lazy_alloc fail!\n");p->killed = 1;}}}
/*** 判断该虚拟地址是否合法,1合法,0不合法* @param va* @return*/intis_lazy_alloc_va(uint64 va) {struct proc *p = myproc();if (va >= p->sz) {return 0;}return 1;}int lazy_alloc(uint64 va) {va = PGROUNDDOWN(va);char *mem = kalloc();if (mem == 0) {return -1;}memset(mem, 0, PGSIZE);struct proc *p = myproc();if (mappages(p->pagetable, va, PGSIZE, (uint64)mem, PTE_W|PTE_X|PTE_R|PTE_U) != 0) {kfree(mem);return -1;}return 0;}
Lazytests and Usertests
完善lazy allocation,保证:
1.处理sbrk的负数参数
2.如果va大于sz,杀死进程
3.在fork中正确处理父进程到子进程的复制过程
4.处理这样一种情况:系统调用(比如write)传入的虚拟地址对应的内存并没有被分配。
5.物理内存不足时杀死进程
6.处理va在user stack以下的情况
sys_sbrk()中处理负数参数的情况
如果传入的n为负数,则把p->sz+n到p->sz的地址范围都dealloc
这里加上了两个判断,传入sbrk的参数不合法(即sz+n大于trapframe或小于user stack),sbrk失败,返回-1.
之前修改过unmap的部分,dealloc的时候如果某一页不存在,不会panic而是会跳过这一页。在fork时会调用uvmcopy复制一份父进程的内存,在lazy allocation中可能0->sz中有部分没有真正分配,在uvmcopy中就会导致panic。累次uvmunmap,修改uvmcopy使得在页面不存在时跳过这一页。
处理第4种情况,即系统调用(比如write)传入的虚拟地址对应的内存并没有被分配。
首先搞清楚函数执行流程,在调用write后系统trap到内核态,执行copyin来把用户程序va处的内容复制到内核空间,此时若va处并未分配内存,walkaddr会返回0导致系统调用失败。因此我们要做的就是在walkaddr中分配内存。
自述
- sbrk函数不在分配虚实地址映射关系,而是只把proc->sz += n,不做growup()
- 当发生缺页中断时,去判断va < proc->sz,说明当前页是该进程的,只是未做映射而已,说明是懒加载,那现在再去做一个映射
思路从中断异常开始,我们去判断中断类型是不是缺页异常,也就是scasue等于13或者15,如果是缺页异常的话,我们去判断这个是不是又懒加载引起的,如果这个虚拟地址超出了进程块的地址大小,那么就意味着这是个错误的地址,那就照样报错就行了,不然的话说明这是个懒加载的页,那么再去给该虚拟地址分配内存mapages,这个是使用路径来看。
