该文档是关于CS537项目4的说明,包括截止日期、迟交政策、测试、协作、资源使用等信息。项目主要在xv6中实现wmap
、wunmap
、wremap
等系统调用,以支持内存映射功能,还需实现getwmapinfo
和getpgdirinfo
系统调用用于调试和测试。文档详细说明了每个系统调用的功能、参数、返回值以及实现过程中的注意事项,包括地址分配、最佳实践等,并对fork()
和exit()
系统调用进行修改。
- 截止日期:2024年3月12日晚上11:59。
- 项目可以迟交最多3天,但每迟交一天将被扣10个百分点。
- 延迟天数:
- 如果您在项目上需要额外的时间,每个人对于个人项目将有2天的延迟天数,对于小组项目将有3天的延迟天数(本学期总共5天的延迟天数)。在截止日期后,我们将为按时评分复制提交目录的副本。
- 要使用延迟天数或迟交作业,您将提交文件以及一个额外的文件,该文件仅包含一个数字,表示您的作业迟交的天数(例如1、2、3)。每连续一天,我们将复制任何包含这些slipdays.txt文件的目录。当您提交最终提交时,此文件必须存在,否则我们将不知道对您的代码进行评分。
- 我们将跟踪您从一个项目到另一个项目的延迟天数和迟交提交,并在您用完延迟天数后开始扣除百分比。
- 在用完延迟天数后,如果迟交1天,您最多可以获得90%的分数;迟交2天,最多可以获得80%的分数;迟交3天,最多可以获得70%的分数,但对于任何单个作业,如果超过第三天我们将不接受提交,除非有例外。这意味着如果您在单个作业上使用了两个个人延迟天数,您只能再迟交一天,总共迟交3天,扣除10%。
- 任何例外都需要向教师请求。
- 示例slipdays.txt
1
- 测试将在
~cs537 - 1/tests/P4
中提供。该目录中有一个README.md
文件,其中包含运行测试的说明。测试部分完成,您的项目可能会根据其他测试进行评分。 - 问题:我们将使用Piazza回答所有问题。
- 协作:作业可以自己完成或与一个伙伴一起完成。从其他任何人复制代码都被视为作弊。阅读此内容以获取更多关于什么是可以的和什么是不可以的信息。请帮助我们所有人度过一个愉快的学期,不要这样做。
- 这个项目将在Linux实验室机器上完成,因此您可以更多地了解在典型的基于UNIX的平台(Linux)上用C编程。您的解决方案将在这些机器上进行测试。
- 如果适用,一个名为resources.txt的描述在线资源使用的文档应包含在您的提交中。欢迎您使用在线资源来帮助您完成作业。我们不建议您使用像ChatGPT这样的大型语言模型。对于本课程,我们特别看到这些工具给出的示例或解释接近但不完全正确,如果学生本身不知道正确答案,这会让他们更加困惑。请注意,当您向教学人员寻求帮助时,我们不会协助处理这些LLM,并且我们希望您能够向教学人员介绍您的代码和逻辑。在线资源(例如stack overflow)和生成工具正在改变包括计算机科学和教育在内的许多行业。但是,如果您使用在线资源,则需要提交一份文档,描述您对这些资源的使用。在该文档中指出您的解决方案中有多少是完全由您自己完成的,有多少是利用这些工具完成的。请具体说明,指出使用的资源以及您如何与这些资源互动。不给予外部资源信用是一种剽窃形式。在您的代码中使用这些资源的地方对来源进行注释可能是一个好的做法。您不会因为使用LLM或阅读帖子而受到惩罚,但您不应该在在线论坛上发布关于本课程项目的帖子。您的大部分代码也应该由您自己努力编写,并且您应该能够解释您提交的所有代码。
- 提交方式:
- 将所有xv6文件(不仅仅是您更改的文件)复制到
~cs537 - 1/handin/<cslogin>/P4/
– 在运行make clean
之后。目录结构应如下所示:
handin/P4/<您的cs登录名>/ |---- README.md |---- resources.txt |---- xv6 - public | ---- xv6的所有内容以及您的修改
- 小组项目提交:每个小组只需要提交一次。要提交,一个人应该将代码放在他们的提交目录中(另一个人应该为空)。
- 将所有xv6文件(不仅仅是您更改的文件)复制到
xv6内存映射
在这个项目中,您将在xv6中实现wmap
系统调用。内存映射,顾名思义,是将进程的“虚拟”地址空间中的内存映射到某些物理内存的过程。它通过一对系统调用wmap
和wunmap
来支持。首先,您调用wmap
以获取指向您可以访问和使用的内存区域(您指定的大小)的指针。当您不再需要该内存时,您调用wunmap
从进程的地址空间中删除该映射。您还可以通过另一个系统调用wremap
来增长或缩小映射的内存,您也将实现这个系统调用。 您还将实现两个系统调用getwmapinfo
和getpgdirinfo
,用于调试和测试目的。请注意,大多数测试都依赖于这两个系统调用。因此,即使您的一切都正常工作,但如果没有正确实现这些系统调用,您将无法通过大多数测试。
目标
- 实现
wmap
/wunmap
/wremap
系统调用(简化版本的mmap
/munmap
/mremap
) - 理解xv6内存布局
- 理解虚拟地址和物理地址之间的关系
- 理解页错误处理程序在内存管理中的使用
项目细节
wmap
系统调用
wmap
有两种操作模式。第一种是“匿名”内存分配,它与malloc
有类似的方面。wmap
的真正强大之处在于支持“文件支持”的映射。等等 – 文件支持的映射是什么意思?这意味着您创建一个文件的内存表示。从该内存区域读取数据与从文件中读取数据相同。如果我们写入由文件支持的内存会发生什么?它会反映在文件中吗?好吧,这取决于您用于wmap
的标志。当使用MAP_SHARED
标志时,您需要在wunmap
时将内存(可能修改的)内容写回文件,以确保文件保持最新。 这是wmap
系统调用的签名:
(uint addr, int length, int flags, int fd); uint wmap
addr
:根据标志(MAP_FIXED
,稍后更多介绍),它可以是wmap
应该用于映射的“虚拟”地址的提示,或者是wmap
必须用于映射的“虚拟”地址。length
:映射的长度(以字节为单位)。它必须大于0。fd
:如果它是文件支持的映射,这是要映射的文件的文件描述符。您可以假设fd属于FD_INODE
类型的文件。此外,您可以假设fd是以O_RDRW
模式打开的。在MAP_ANONYMOUS
的情况下(见标志),您应该忽略此参数。flags
:您请求的内存映射的类型。标志可以或在一起(例如,MAP_PRIVATE | MAP_ANONYMOUS
)。您应该在wmap.h
头文件中将这些标志定义为常量。使用提示部分提供的代码片段。如果您查看手册页,会有许多标志用于各种目的。在您的实现中,您只需要实现这些标志: a)MAP_ANONYMOUS:它不是文件支持的映射。如果提供此标志,您可以忽略最后一个参数(fd)。 b)MAP_SHARED:此标志告诉wmap
映射是共享的。您可能想知道,这里的“共享”是什么意思?如果您也知道MAP_PRIVATE
标志,可能会更清楚。内存映射在fork
系统调用中从父进程复制到子进程。如果映射是MAP_SHARED
,则子进程所做的更改将对父进程可见,反之亦然。但是,如果映射是MAP_PRIVATE
,则每个进程将拥有自己的映射副本。 c)MAP_PRIVATE:映射不是共享的。您仍然需要将映射从父进程复制到子进程,但这些映射应该使用不同的“物理”页面。换句话说,子进程中映射到相同的虚拟地址,但映射到不同的物理页面集。此标志将导致对内存的修改对其他进程不可见。此外,如果它是文件支持的映射,对内存的修改不会反映到基础文件中。请参阅最佳实践以获取实现指南。在标志MAP_SHARED
和MAP_PRIVATE
之间,必须在标志中指定其中一个。这两个标志不能一起使用。 d)MAP_FIXED:这与wmap
的第一个参数 –addr
参数有关。如果没有设置MAP_FIXED
,则该地址将被解释为映射应放置的位置的提示。如果设置了MAP_FIXED
,则映射必须精确地放置在“addr”处。在这个项目中,您只实现后者。换句话说,除非设置了MAP_FIXED
,否则您不关心addr
参数。此外,有效的addr
将是页面大小的倍数,并且在0x60000000和0x80000000之间(请参阅关于地址分配的说明)。 您对wmap
的实现应该进行“延迟分配”。延迟分配意味着在调用wmap
时,您实际上并不分配任何物理页面。您只是使用某种结构跟踪分配的区域。稍后,当进程尝试访问该内存时,会生成一个页错误,由内核处理。在内核中,您现在可以分配一个物理页面并让用户恢复执行。延迟分配使大型内存映射快速。 要设置页错误处理程序,您将在trap.c
中的trap
函数中添加类似这样的内容:
case T_PGFLT: // T_PGFLT = 14
if page fault addr is part of a mapping: // lazy allocation
// handle it
else:
("Segmentation Fault\n");
cprintf// kill the process
您可以做出一些简化的假设来实现wmap
: * 所有映射的内存都是可读/可写的。如果您查看mmap
的手册页,您会看到一个prot
(保护)参数。我们没有这个参数,并假设prot
始终为PROT_READ | PROT_WRITE
。 * 内存映射的最大数量为16。 * 对于文件支持的映射,在我们的测试中,您可以始终期望映射大小等于文件大小。
wunmap
系统调用
wunmap()
系统调用的签名如下:
int wunmap(uint addr);
addr
:要删除的映射的起始地址。它必须是页面对齐的,并且是某个现有wmap
的起始地址。wunmap
从进程虚拟地址空间中删除从addr
开始的映射。如果它是具有MAP_SHARED
的文件支持的映射,它会将内存数据写回文件以确保文件保持最新。因此,wunmap
不会部分取消任何mmap
。
wremap
系统调用
wremap
的签名如下:
(uint oldaddr, int oldsize, int newsize, int flags) uint wremap
oldaddr
:现有内存映射的起始地址。它必须是页面对齐的,并且是某个现有wmap
的起始地址。这意味着如果现有wmap
从0x60023000开始,其长度为0x1000,则wremap
必须使用这些值精确地调用oldaddr
和oldsize
,否则wremap
将失败。因此,不支持部分重新映射mmap
。oldsize
:从oldaddr
开始的映射的大小。newsize
:wremap
返回的映射的大小。它必须大于0。flags
:要么为0
,要么为MREMAP_MAYMOVE
。wremap
用于增长或缩小现有映射。现有映射可以在原地修改,也可以根据标志移动到新地址:如果flags
为0
,则wremap
尝试在原地增长/缩小映射,如果没有足够的空间,则失败。如果设置了MREMAP_MAYMOVE
标志,则wremap
还应该尝试通过移动映射来分配请求的newsize
。请注意,只有在无法在原地增长时,才允许移动映射。 如果wremap
失败,则应保留现有映射不变。换句话说,只有在新映射成功后,才应删除旧映射。
getpgdirinfo
系统调用
int getpgdirinfo(struct pgdirinfo *pdinfo);
pdinfo
:指向将由系统调用填充的struct pgdirinfo
的指针。 添加一个新的系统调用getpgdirinfo
,通过填充struct pgdirinfo
来检索有关进程地址空间的信息。您应该只收集关于设置了PTE_U
的页面(即用户页面)的信息(无论是计算n_pages
还是返回va
/pa
对)。做到这一点的唯一方法是直接查阅进程的页表。 这个系统调用应该计算当前进程地址空间中当前分配的物理页面数量,并将其存储在n_upages
中。它还应该用前MAX_UPAGE_INFO
(请参阅提示)个页面的虚拟地址和物理地址填充va[MAX_UPAGE_INFO]
和pa[MAX_UPAGE_INFO]
,按虚拟地址排序。
getwmapinfo
系统调用
int getwmapinfo(struct wmapinfo *wminfo);
wminfo
:指向将由系统调用填充的struct wmapinfo
的指针。 添加一个新的系统调用getwmapinfo
,通过填充struct wmapinfo
来检索有关进程地址空间的信息。 这个系统调用应该计算当前进程地址空间中当前的内存映射(mmap
)数量,并将结果存储在total_mmaps
中。它还应该用每个wmap
的地址和长度填充addr[MAX_WMAP_INFO]
和length[MAX_WMAP_INFO]
。您可以预期当前进程中的mmap
数量不会超过MAX_UPAGE_INFO
。n_loaded_pages[MAX_WMAP_INFO]
应该存储每个wmap
实际分配的物理页面数量(对应于addr
和length
数组的索引)。此字段应反映延迟分配。
返回值
SUCCESS
和FAILED
在wmap.h
中定义为0和-1,分别(请参阅提示)。 wmap
,wremap
:如果成功,则返回内存的起始虚拟地址,如果失败,则返回FAILED
。该虚拟地址必须是页面大小的倍数。 wunmap
,getwmapinfo
,getpgdirinfo
:返回SUCCESS
表示成功,返回FAILED
表示失败。
修改fork()
系统调用
内存映射应该由子进程继承。为此,您需要修改fork()
系统调用,以在fork()
过程中从父进程复制映射到子进程。 您可以在这里做出一些简化的假设: – 由于延迟分配,在调用fork
时,父进程的页表中可能有一些页面尚未反映(即这些页面尚未被触及)。 *我们将确保在调用fork
之前触及父进程中所有分配的页面,因此您无需担心这种情况。 – 此外,您可以假设子进程不会取消父进程创建的映射。 – 而且,父进程将始终在子进程之后退出。
修改exit()
系统调用
您应该修改exit()
,以便它从当前进程地址空间中删除所有映射。这是为了防止用户程序在退出前忘记调用wunmap
时发生内存泄漏。
关于地址分配的说明
如果未设置MAP_FIXED
标志,则您需要在虚拟地址空间中找到一个可用区域。请注意,该区域的起始地址必须是页面对齐的。为此,您应该首先了解xv6的内存布局。在xv6中,更高的内存地址总是用于映射内核。具体来说,有一个定义为KERNBASE的常量,其值为0x80000000。这意味着仅0到KERNBASE之间的“虚拟”地址