MIT 6.S081 2020Lab5 lazy page allocation 个人全流程
文章目录
- 零、写在前面
- 一、Eliminate allocation from sbrk()
- 1.1 说明
- 1.2 实现
- 二、Lazy allocation
- 2.1 说明
- 2.2 实现
- 三、Lazytests and Usertests
- 3.1 说明
- 3.2 实现
- 3.2.1 lazytests
- 3.2.2 usertests
零、写在前面
可以阅读下4.6页面错误异常
像应用程序申请内存,内核分配和映射这些内存其实是很耗费时间的。比如,一个 GB 的内存包含 262,144 个 4096 字节的页;即便每次分配的开销很小,这么多次操作累积起来仍然非常耗时。
此外,一些程序会分配比实际使用更多的内存(例如,为了实现稀疏数组),或者会提前分配内存但迟迟不使用。为加快 sbrk()
的执行速度,现代内核采用了一种称为懒惰分配的技术:sbrk()
不再立即分配物理内存,而是仅记录下哪些用户地址被分配了,并在用户页表中将这些地址标记为无效。
当进程首次尝试访问这些**“懒惰分配”**的页面时,CPU 会触发一次缺页异常(page fault)。内核在处理该异常时,会分配一页物理内存、将其清零,并将其映射到进程的地址空间中。
总的来说,懒惰分配是一种常见的降低均摊成本的操作。
在本实验中,你将为 xv6 添加这个懒惰分配的功能。
记得先切换分支到lazy
一、Eliminate allocation from sbrk()
1.1 说明
你的第一个任务是从 sbrk(n)
系统调用的实现中删除物理页面分配。该系统调用对应的函数是 sysproc.c
文件中的 sys_sbrk()
。
sbrk(n)
系统调用的作用是将当前进程的内存大小增加 n
字节,并返回新分配区域的起始地址(即原来的内存大小)。你需要修改 sbrk(n)
的实现,使其仅仅将进程的大小(myproc()->sz
)增加 n
字节并返回旧的大小。
注意:不应该在这里分配物理内存,因此需要删除对 growproc()
的调用。但你仍然需要更新进程的大小字段。
试着猜一猜:这个修改会导致什么问题?会有什么地方出错?
进行上述修改后,启动 xv6,并在 shell 中输入 echo hi
。你应该会看到类似下面的输出:
init: starting sh
$ echo hi
usertrap(): unexpected scause 0x000000000000000f pid=3sepc=0x0000000000001258 stval=0x0000000000004008
va=0x0000000000004000 pte=0x0000000000000000
panic: uvmunmap: not mapped
其中,usertrap(): ...
是来自 trap.c
中用户异常处理函数(user trap handler)的信息;它捕获到了一个它不知道如何处理的异常。你需要弄清楚为什么会发生这个缺页异常(page fault)。
信息中的 stval=0x0000000000004008
表示导致缺页异常的虚拟地址是 0x4008。
1.2 实现
我们先来看看初始的sys_sbrk:
uint64 sys_sbrk(void) {int addr;int n;if(argint(0, &n) < 0)return -1;addr = myproc()->sz;if(growproc(n) < 0)return -1;return addr;
}
- 从寄存器a0拿出n
- growproc(n) 来分配n byte 的物理内存
- 返回addr
修改后:
- 删除分配物理内存的逻辑
- 如果n 大于0,我们增加sz
- 否则,我们dealloc 掉n个字节
uint64 sys_sbrk(void) {int addr;int n;if(argint(0, &n) < 0)return -1;struct proc* p = myproc();addr = p->sz;// if(growproc(n) < 0)// return -1;// allocif (n > 0){(p->sz) += n;} else {// shrinkuint sz = p->sz;p->sz = uvmdealloc(p->pagetable, sz, sz + n);}return addr;
}
我们启动xv6来测试下:
和预期一致,我们接着做后面的作业。
二、Lazy allocation
2.1 说明
修改 trap.c
中的代码,使其能在用户空间发生缺页异常时,为发生异常的地址映射一页新分配的物理内存,然后返回用户空间,让进程继续执行。你应当在打印 "usertrap(): ..."
的那条 printf
语句之前加入你的处理代码。此外,你还需要根据需要修改 xv6 内核中的其他代码,使 echo hi
能够正常运行。
官网的一些提示:
- 你可以在
usertrap()
中通过检查r_scause()
是否为 13 或 15 来判断是否是页面异常(page fault):- 13 表示加载时的页面异常(load page fault)
- 15 表示存储时的页面异常(store page fault)
r_stval()
返回 RISC-V 的stval
寄存器的值,它表示触发异常的虚拟地址。- 你可以参考
vm.c
中的uvmalloc()
函数的代码,这是sbrk()
通过growproc()
最终调用的函数。你会用到以下两个函数:kalloc()
:用于分配一页物理内存。mappages()
:用于将虚拟地址映射到物理页。
- 使用
PGROUNDDOWN(va)
宏将发生异常的虚拟地址向下对齐到页边界。 uvmunmap()
默认会触发 panic;你需要修改它的行为,使其在取消映射时不会因为某些页未映射就 panic。- 如果内核崩溃了,你可以查找
kernel/kernel.asm
中的sepc
来定位异常发生的位置。 - 使用你在页表实验(pgtbl lab)中写的
vmprint
函数来打印页表内容,辅助调试。 - 如果你遇到
incomplete type proc
的错误,记得先#include "spinlock.h"
,再#include "proc.h"
。
如果一切顺利,你的懒惰分配(lazy allocation)代码应该能使 echo hi
正常工作。在执行过程中,系统应该至少会发生一次页面异常(触发懒惰分配),也可能会触发两次。
2.2 实现
- 按照官网提示,在trap.c 中的usertrap 的 printf else分支前添加处理代码
- 如果 是 13(load page fault)或 15(store page fault)我们就调用 uvmalloc() 函数分配物理内存,并映射用户页表。
// ...
} else if((which_dev = devintr()) != 0){// ok
} else if(r_scause() == 13 || r_scause() == 15) {uint64 va = r_stval();if (uvmalloc(p->pagetable, PGROUNDDOWN(va), PGROUNDDOWN(va) + PGSIZE) == 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;
}
值得注意的是,因为我们是利用缺页异常实现延迟分配,所以 uvmunmap 中的无法根据虚拟地址找到实际物理页面的情况需要取消panic,改为continue,否则就会触发panic:
然后我们运行一下:
三、Lazytests and Usertests
3.1 说明
我们为你提供了一个名为 lazytests
的 xv6 用户程序,它会测试一些特定情况,这些情况可能会对你的懒惰内存分配器造成压力。请修改你的内核代码,使 lazytests
和 usertests
中的所有测试都能通过。
你需要处理以下几种情况:
- 处理
sbrk()
的负数参数:即当进程释放内存时,要正确缩小进程的地址空间。 - 如果进程在访问一个高于
sbrk()
分配范围的虚拟地址时发生缺页异常,应终止该进程。 - 正确处理
fork()
中父进程到子进程的内存拷贝,包括懒惰分配页的复制。 - 当进程将一个来自
sbrk()
的合法地址传递给系统调用(如read
或write
),但该地址尚未实际分配物理内存时,也应正确处理并触发分配。 - 正确处理内存耗尽的情况:如果在页面异常处理函数中
kalloc()
失败,表示系统已无可用内存,应该终止当前进程。 - 处理位于用户栈下方的非法页面上的访问异常。
你的实现是合格的,如果你的内核能够通过 lazytests
和 usertests
的全部测试,如下所示:
$ lazytests
lazytests starting
running test lazy alloc
test lazy alloc: OK
running test lazy unmap...
usertrap(): ...
test lazy unmap: OK
running test out of memory
usertrap(): ...
test out of memory: OK
ALL TESTS PASSED$ usertests
...
ALL TESTS PASSED$
3.2 实现
先跑一下看看哪里报错,结合官网提示去调:
3.2.1 lazytests
~~然后它就死了。~~但是给了很多信息。
我们先看uvmcopy,原代码:
- 这个函数会把给定的父进程页表拷贝内存到子进程页表,页表和物理内存都进行拷贝
- 官网提示我们正确完成拷贝包括懒惰分配页的复制
// Given a parent process's page table, copy
// its memory into a child's page table.
// Copies both the page table and the
// physical memory.
// returns 0 on success, -1 on failure.
// frees any allocated pages on failure.
int uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{pte_t *pte;uint64 pa, i;uint flags;char *mem;for(i = 0; i < sz; i += PGSIZE){if((pte = walk(old, i, 0)) == 0)panic("uvmcopy: pte should exist");if((*pte & PTE_V) == 0)panic("uvmcopy: page not present");pa = PTE2PA(*pte);flags = PTE_FLAGS(*pte);if((mem = kalloc()) == 0)goto err;memmove(mem, (char*)pa, PGSIZE);if(mappages(new, i, PGSIZE, (uint64)mem, flags) != 0){kfree(mem);goto err;}}return 0;err:uvmunmap(new, 0, i / PGSIZE, 1);return -1;
}
然后注意到 panic(“uvmcopy: page not present”);被触发显然是因为遇到了我们假分配的页面
那就很简单了,注释掉panic,改为continue即可。
再跑一遍:
它又死了,这次报错在 freewalk
我们查看一下源码:
- 它会递归释放页表页
- 所以叶子映射都必须已经释放
// Recursively free page-table pages.
// All leaf mappings must already have been removed.
void freewalk(pagetable_t pagetable)
{// there are 2^9 = 512 PTEs in a page table.for(int i = 0; i < 512; i++){pte_t pte = pagetable[i];if((pte & PTE_V) && (pte & (PTE_R|PTE_W|PTE_X)) == 0){// this PTE points to a lower-level page table.uint64 child = PTE2PA(pte);freewalk((pagetable_t)child);pagetable[i] = 0;} else if(pte & PTE_V){panic("freewalk: leaf");}}kfree((void*)pagetable);
}
钠根据该函数的描述,我们知道说明有叶子节点没有被释放。
这其实是比较奇怪的,然后看到官网这一条提示:
- 如果进程在访问一个高于
sbrk()
分配范围的虚拟地址时发生缺页异常,应终止该进程。
于是合理怀疑是因为测试点里面有对于非法地址的访问,触发缺页异常,然后让我们误以为是懒惰分配,从而分配了物理内存。
于是在最早的usertrap中的逻辑中加一个地址界限的判断:
这次就没问题了:
3.2.2 usertests
同样的,我们根据错误找问题:
通过vscode找到这个sbrkarg函数是在 usertest.c 下
- 用来测试对分配内存的读写
- 我们报错是在write的地方报错了
// test reads/writes from/to allocated memory
void sbrkarg(char *s)
{char *a;int fd, n;a = sbrk(PGSIZE);fd = open("sbrk", O_CREATE|O_WRONLY);unlink("sbrk");if(fd < 0) {printf("%s: open sbrk failed\n", s);exit(1);}if ((n = write(fd, a, PGSIZE)) < 0) {printf("%s: write sbrk failed\n", s);exit(1);}close(fd);// test writes to allocated memorya = sbrk(PGSIZE);if(pipe((int *) a) != 0){printf("%s: pipe() failed\n", s);exit(1);}
}
官网有着这样一条提示:
- 当进程将一个来自
sbrk()
的合法地址传递给系统调用(如read
或write
),但该地址尚未实际分配物理内存时,也应正确处理并触发分配。
那其实很好理解了,我们没有对 write 访问假分配时进行分配物理内存。
这个就需要我们去查看write 系统调用的实现,以及写逻辑。
先找到 sys_write
uint64 sys_write(void)
{struct file *f;int n;uint64 p;if(argfd(0, 0, &f) < 0 || argint(2, &n) < 0 || argaddr(1, &p) < 0)return -1;return filewrite(f, p, n);
}
发现调用了 filewrite
然后查看 filewrite 发现它又调用了 writei 来进行写逻辑
writei 又调用了either_copyin,而either_copyin又调用了copyin
最终在 copyin 中发现了对于pagetable 的访问
总而言之是这么个逻辑:
sys_write() -> filewrite() -> writei() -> either_copyin() -> copyin() -> walkaddr()
查看 walkaddr:
- 果然有问题,如果访问到空页或者无效页,它直接返回0了(0代表未映射)
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)return 0;if((*pte & PTE_V) == 0)return 0;if((*pte & PTE_U) == 0)return 0;pa = PTE2PA(*pte);return pa;
}
我们特判尝试物理分配即可:
值得注意的是两个头文件的引用顺序:
测试一下:
相关文章:

MIT 6.S081 2020Lab5 lazy page allocation 个人全流程
文章目录 零、写在前面一、Eliminate allocation from sbrk()1.1 说明1.2 实现 二、Lazy allocation2.1 说明2.2 实现 三、Lazytests and Usertests3.1 说明3.2 实现3.2.1 lazytests3.2.2 usertests 零、写在前面 可以阅读下4.6页面错误异常 像应用程序申请内存,内…...

C++初阶-list的使用2
目录 1.std::list::splice的使用 2.std::list::remove和std::list::remove_if的使用 2.1remove_if函数的简单介绍 基本用法 函数原型 使用函数对象作为谓词 使用普通函数作为谓词 注意事项 复杂对象示例 2.2remove与remove_if的简单使用 3.std::list::unique的使用 …...
PHP序列化数据格式详解
PHP序列化数据格式详解 概述 PHP序列化是将PHP变量(包括对象)转换为可存储或传输的字符串表示形式的过程。了解这些序列化格式对于数据处理、调试和安全性分析非常重要。本文将详细介绍PHP中各种数据类型的序列化表示方式。 基本数据类型序列化格式 …...

如何优化 MySQL 存储过程的性能?
文章目录 1. 优化 SQL 语句避免全表扫描减少子查询,改用 JOIN避免 SELECT 2. 合理使用索引3. 优化存储过程结构减少循环和临时变量避免重复计算 4. 使用临时表和缓存5. 优化事务处理6. 分析和监控性能7. 优化数据库配置8. 避免用户自定义函数(UDF&#…...
深度学习:损失函数与激活函数全解析
目录 深度学习中常见的损失函数和激活函数详解引言一、损失函数详解1.1 损失函数的作用与分类1.2 回归任务损失函数1.2.1 均方误差(MSE)1.2.2 平均绝对误差(MAE) 1.3 分类任务损失函数1.3.1 交叉熵损失(Cross-Entropy&…...
【大前端】Node Js下载文件
NodeJs 获取远程文件有很多方式,常见的方式有以下两种: - fetch(原生) - axios(插件) 通过 Fetch 下载文件,代码如下: import fs from node:fsfunction main(){fetch(http://xxx.x…...
自训练NL-SQL模型
使用T5小模型在笔记本上训练 nature language to SQL/自然语言 转SQL 实测通过。 本文介绍了如何在笔记本上使用T5小模型训练自然语言转SQL的任务。主要内容包括:1) 创建Python 3.9环境并安装必要的依赖包;2) 通过Hugging Face镜像下载wikisql数据集和T5-small模型;3) 实现…...

创新点!贝叶斯优化、CNN与LSTM结合,实现更准预测、更快效率、更高性能!
能源与环境领域的时空数据预测面临特征解析与参数调优双重挑战。CNN-LSTM成为突破口:CNN提取空间特征,LSTM捕捉时序依赖,实现时空数据的深度建模。但混合模型超参数(如卷积核数、LSTM层数)调优复杂,传统方法…...

【Flutter】创建BMI计算器应用并添加依赖和打包
😏★,:.☆( ̄▽ ̄)/$:.★ 😏 这篇文章主要介绍创建BMI计算器应用并添加依赖和打包。 学其所用,用其所学。——梁启超 欢迎来到我的博客,一起学习,共同进步。 喜欢的朋友可以关注一下,下…...

【Linux 学习计划】-- 倒计时、进度条小程序
目录 \r 、\n、fflush 倒计时 进度条 进度条进阶版 结语 \r 、\n、fflush 首先我们先来认识这三个东西,这将会是我们接下来两个小程序的重点之一 首先是我们的老演员\n,也就是回车加换行 这里面其实包含了两个操作,一个叫做回车&…...

微服务的应用案例
从“菜市场”到“智慧超市”:一场微服务的变革之旅 曾经,我们的系统像一个熙熙攘攘的传统菜市场。所有功能模块(摊贩)都挤在一个巨大的单体应用中。用户请求(买菜的顾客)一多,整个市场就拥堵不堪…...
后端开发概念
1. 后端开发概念解析 1.1. 什么是服务器,后端服务 1.1.1. 服务器 服务器是一种提供服务的计算机系统,它可以接收、处理和响应来自其他计算机系统(客户端)的请求。服务器主要用于存储、处理和传输数据,以便客户端可以…...

2025网络安全趋势报告 内容摘要
2025 年网络安全在技术、法规、行业等多个维度呈现新趋势。技术上,人工智能、隐私保护技术、区块链、量子安全技术等取得进展;法规方面,数据安全法规进一步细化;行业应用中,物联网、工业控制系统安全升级,供…...

云原生安全基石:深度解析HTTPS协议(从原理到实战)
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念:HTTPS是什么? HTTPS(HyperText Transfer Protocol Secure)是HTTP协议的安全版本,…...

Autodl训练Faster-RCNN网络--自己的数据集(一)
参考文章: Autodl服务器中Faster-rcnn(jwyang)复现(一)_autodl faster rcnn-CSDN博客 Autodl服务器中Faster-rcnn(jwyang)训练自己数据集(二)_faster rcnn autodl-CSDN博客 食用指南:先跟着参考文章一进行操作,遇到问题再来看我这里有没有解…...

python打卡day36
复习日 仔细回顾一下神经网络到目前的内容,没跟上进度的补一下进度 作业:对之前的信贷项目,利用神经网络训练下,尝试用到目前的知识点让代码更加规范和美观。探索性作业(随意完成):尝试进入nn.M…...
8.Java 8 日期时间处理:从 Date 的崩溃到 LocalDate 的优雅自救
一、被 Date 逼疯的程序员:那些年踩过的坑 还记得刚学 Java 时被Date支配的恐惧吗? 想获取 "2023 年 10 月 1 日"?new Date(2023, 9, 1)—— 等等,为什么月份是 9?哦对,Java 的月份从 0 开…...
基于Python的全卷积网络(FCN)实现路径损耗预测
以下是一份详细的基于Python的全卷积网络(FCN)实现路径损耗预测的技术文档。本方案包含理论基础、数据生成、模型构建、训练优化及可视化分析,代码实现约6000字。 基于全卷积网络的无线信道路径损耗预测系统 目录 问题背景与需求分析系统架构设计合成数据生成方法全卷积网络…...
【ubuntu】安装NVIDIA Container Toolkit
目录 安装NVIDIA Container Toolkit 安装依赖 添加密钥和仓库 配置中国科技大学(USTC) 镜像 APT 源 更新 APT 包列表 安装 NVIDIA Container Toolkit 验证安装 重启docker 起容器示例命令 【问题】如何在docker中正确使用GPU? 安装…...

Paimon和Hive相集成
Flink版本1.17 Hive版本3.1.3 1、Paimon集成Hive 将paimon-hive-connector.jar复制到auxlib中,下载链接Index of /groups/snapshots/org/apache/https://repository.apache.org/snapshots/org/apache/paimon/ 通过flink进入查看paimon /opt/softwares/flink-1.…...
精益数据分析(74/126):从愿景到落地的精益开发路径——Rally的全流程管理实践
精益数据分析(74/126):从愿景到落地的精益开发路径——Rally的全流程管理实践 在创业的黏性阶段,如何将抽象的愿景转化为可落地的产品功能?如何在快速迭代中保持战略聚焦?今天,我们通过Rally软…...

HarmonyOS 鸿蒙应用开发进阶:深入理解鸿蒙跨设备互通机制
鸿蒙跨设备互通(HarmonyOS Cross-Device Collaboration)是鸿蒙系统分布式能力的重要体现,通过创新的分布式软总线技术,实现了设备间的高效互联与能力共享。本文将系统性地解析鸿蒙跨设备互通的技术架构、实现原理及开发实践。 跨设…...

Vue.js教学第十五章:深入解析Webpack与Vue项目实战
Webpack 与 Vue 项目详解 在现代前端开发中,Webpack 作为最流行的模块打包工具之一,对于 Vue 项目的构建和优化起着至关重要的作用。本文将深入剖析 Webpack 的基本概念、在 Vue 项目中的应用场景,并详细讲解常用的 Webpack loaders 和 plugins 的配置与作用,同时通过实例…...
深入浅出 Python Testcontainers:用容器优雅地编写集成测试
在现代软件开发中,自动化测试已成为敏捷开发与持续集成中的关键环节。单元测试可以快速验证函数或类的行为是否符合预期,而集成测试则确保多个模块协同工作时依然正确。问题是:如何让集成测试可靠、可重复且易于维护? 这时&#…...

Cmake编译gflags过程记录和在QT中测试
由于在QT中使用PaddleOCR2.8存在这样那样的问题,查找貌似是gflags相关问题导致的,因此从头开始按相关参考文章编译一遍gflags源码,测试结果表明Qt5.14.2中使用MSVC2017X64编译器运行的QTgflags项目是正常。 详细编译步骤如下: 1、…...

项目中Warmup耗时高该如何操作处理
1)项目中Warmup耗时高该如何操作处理 2)如何在卸载资源后Untracked和Other的内存都回收 3)总Triangles的值是否包含了通过GPU Instancing画的三角形 4)有没有用Lua来修复虚幻引擎中对C代码进行插桩Hook的方案 这是第432篇UWA技术知…...

制作一款打飞机游戏53:子弹样式
现在,我们有一个小程序可以发射子弹,但这些子弹并不完美,我们稍后会修复它们。 子弹模式与目标 在开始之前,我想修正一下,因为我观察到在其他射击游戏中有一个我想复制的简单行为。我们有静态射击、瞄准射击和快速射击…...
Windows磁盘无法格式化及磁盘管理
简述:D盘使用了虚拟分区,结果导致无法格式化。 一、无法格式化磁盘 因为以前划分C盘的时候,空间划小了,所以在下载一些程序的依赖包之后爆红。当我想要把D盘的空间分给C盘时,发现D盘无法格式化。在网上没有找到合适的…...
每日算法 -【Swift 算法】Z 字形变换(Zigzag Conversion)详解与实现
Swift | Z 字形变换(Zigzag Conversion)详解与实现 🧩 题目描述 给定一个字符串 s 和一个行数 numRows,请按照从上往下、再从下往上的“Z”字形排列这个字符串,并按行输出最终结果。例如: 输入ÿ…...

Docker运维-5.3 配置私有仓库(Harbor)
1. harbor的介绍 Harbor(港湾),是一个用于存储和分发 Docker 镜像的企业级 Registry 服务器。以前的镜像私有仓库采用官方的 Docker Registry,不便于管理镜像。 Harbor 是由 VMWare 在 Docker Registry 的基础之上进行了二次封装,加进去了很…...