XV6学习(7)Lab lazy

代码在github上。

这一个实验是要利用缺页异常来实现懒分配(lazy allocation)。用户态程序通过sbrk系统调用来在堆上分配内存,而sbrk则会通过kalloc函数来申请内存页面,之后将页面映射到页表当中。

当申请小的空间时,上述过程是没有问题的。但是如果当进程一次申请很大的空间,如数GB的空间,再使用上述策略来一页页地申请映射的话就会非常的慢(1GB/4KB=262,144)。这时候就引入了lazy allocation技术,当调用sbrk时不进行页面的申请映射,而是仅仅增大堆的大小,当实际访问页面时,就会触发缺页异常,此时再申请一个页面并映射到页表中,这是再次执行触发缺页异常的代码就可以正常读写内存了。

通过lazy allocation技术,就可以将申请页面的开销平摊到读写内存当中去,在sbrk中进行大量内存页面申请的开销是不可以接受的,但是将代价平摊到读写操作当中去就可以接受了。

总体来说这一个实验的难度并不大,理解了上一个trap的实验以及缺页异常就能比较轻松地完成了。

1|0Eliminate allocation from sbrk() (easy)

这一个就是要修改sbrk函数,使其不调用growproc函数进行页面分配,关键就是p->sz += n将堆大小增大,然后注释掉growprocif(n < 0)是后面部分的内容。

uint64 sys_sbrk(void) { int addr; int n; if(argint(0, &n) < 0) return -1; struct proc *p = myproc(); addr = p->sz; p->sz += n; if(n < 0) { p->sz = uvmdealloc(p->pagetable, addr, addr + n); } // if(growproc(n) < 0) // return -1; return addr; }

2|0Lazy allocation (moderate)

接下来就是真正实现Lazy allocation:当系统发生缺页异常时,就会进入到usertrap函数中,此时scause寄存器保存的是异常原因(13为page load fault,15为page write fault),stval是引发缺页异常的地址。

usertrap判断scause为13或15后,就可以读取stval获取引发异常的地址,之后调用lazy_alloc对该地址的页面进行分配即可。在这里不需要进行p->trapframe->epc += 4操作,因为我们要返回发生异常的那条指令并重新执行。

void usertrap(void) { ... } else if((which_dev = devintr()) != 0){ // ok } else if (r_scause() == 13 || r_scause() == 15) { // 13: page load fault; 15: page write fault // printf("page fault\n"); uint64 addr = r_stval(); if (lazy_alloc(addr) < 0) { p->killed = 1; } } else { printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid); printf(" sepc=%p stval=%p\n", r_sepc(), r_stval()); p->killed = 1; } ... }

lazy_alloc函数中,首先判断地址是否合法,之后通过PGROUNDDOWN宏获取对应页面的起始地址,然后调用kalloc分配页面,memset将页面内容置0,最后调用mappages将页面映射到页表中去。

int lazy_alloc(uint64 addr) { struct proc *p = myproc(); // page-faults on a virtual memory address higher than any allocated with sbrk() // this should be >= not > !!! if (addr >= p->sz) { // printf("lazy_alloc: access invalid address"); return -1; } if (addr < p->trapframe->sp) { // printf("lazy_alloc: access address below stack"); return -2; } uint64 pa = PGROUNDDOWN(addr); char* mem = kalloc(); if (mem == 0) { // printf("lazy_alloc: kalloc failed"); return -3; } memset(mem, 0, PGSIZE); if(mappages(p->pagetable, pa, PGSIZE, (uint64)mem, PTE_W|PTE_X|PTE_R|PTE_U) != 0){ kfree(mem); return -4; } return 0; }

3|0Lazytests and Usertests (moderate)

这一部分就是要强化上面写的的lazy allocation,使其能在一些特殊情况下工作。

Handle negative sbrk() arguments.

这一个就是在上面的sys_sbrk函数中的if(n < 0)部分,当参数为负数时,调用uvmdealloc取消分配。

Kill a process if it page-faults on a virtual memory address higher than any allocated with sbrk().

这一个即lazy_alloc函数中的addr >= p->sz部分,当访问的地址大于堆的大小时就说明访问了非法地址,注意这里是>=而不是>

Handle the parent-to-child memory copy in fork() correctly.

fork函数中通过uvmcopy进行地址空间的拷贝,我们只要将其中panic的部分改为continue就行了,当页表项不存在时并不是说明出了问题,直接跳过就可以了。

Handle the case in which a process passes a valid address from sbrk() to a system call such as read or write, but the memory for that address has not yet been allocated.

当进程通过readwrite等系统调用访问未分配页面的地址时,并不会通过页表硬件来访问,也就是说不会发生缺页异常;在内核态时是通过walkaddr来访问用户页表的,因此在这里也要对缺页的情况进行处理。
当出现pte == 0 || (*pte & PTE_V) == 0时,就说明发生了缺页,这时只要调用lazy_alloc进行分配,之后再次使用walk就能正确得到页表项了。

uint64 walkaddr(pagetable_t pagetable, uint64 va) { pte_t *pte; uint64 pa; if(va >= MAXVA) return 0; pte = walk(pagetable, va, 0); if(pte == 0 || (*pte & PTE_V) == 0) { if (lazy_alloc(va) == 0) { pte = walk(pagetable, va, 0); } else { return 0; } } if((*pte & PTE_U) == 0) return 0; pa = PTE2PA(*pte); return pa; }

Handle out-of-memory correctly: if kalloc() fails in the page fault handler, kill the current process.

kalloc失败时,lazy_alloc就会返回负值,此时判断返回值然后p->killed = 1就行了。

Handle faults on the invalid page below the user stack.

这一个可以通过addr < p->trapframe->sp判断,当地址小于栈顶地址时就说明发生了非法访问。


__EOF__

本文作者星见遥
本文链接https://www.cnblogs.com/weijunji/p/14338483.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   星見遥  阅读(1829)  评论(1编辑  收藏  举报
编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示