> For the complete documentation index, see [llms.txt](https://mit-public-courses-cn-translatio.gitbook.io/mit6-s081/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mit-public-courses-cn-translatio.gitbook.io/mit6-s081/lec17-virtual-memory-for-applications-frans/17.2-zhi-chi-ying-yong-cheng-xu-shi-yong-xu-ni-nei-cun-de-xi-tong-tiao-yong.md).

# 17.2 支持应用程序使用虚拟内存的系统调用

第一个或许也是最重要的一个，是一个叫做mmap的系统调用。它接收某个对象，并将其映射到调用者的地址空间中。举个例子，如果你想映射一个文件，那么你需要将文件描述符传递给mmap系统调用。mmap系统调用有许多令人眼花缭乱的参数（注，mmap的具体说明可以参考[man page](https://man7.org/linux/man-pages/man2/mmap.2.html)）：

* 第一个参数是一个你想映射到的特定地址，如果传入null表示不指定特定地址，这样的话内核会选择一个地址来完成映射，并从系统调用返回。
* 第二个参数是想要映射的地址段长度len。
* 第三个参数是Protection bit，例如读写R|W。
* 第四个参数我们会跳过不做讨论，它的值可以是MAP\_PRIVATE。它指定了如果你更新了传入的对象，会发生什么。（注，第四个参数是flags，MAP\_PRIVATE是其中一个值，在mmap文件的场景下，MAP\_PRIVATE表明更新文件不会写入磁盘，只会更新在内存中的拷贝，详见[man page](https://man7.org/linux/man-pages/man2/mmap.2.html)）。
* 第五个参数是传入的对象，在上面的例子中就是文件描述符。
* 第六个参数是offset。

![](/files/-MWvXdKAlbT--8Eq5_Um)

通过上面的系统调用，可以将文件描述符指向的文件内容，从起始位置加上offset的地方开始，映射到特定的内存地址（如果指定了的话），并且连续映射len长度。这使得你可以实现Memory Mapped File，你可以将文件的内容带到内存地址空间，进而只需要方便的通过普通的指针操作，而不用调用read/write系统调用，就可以从磁盘读写文件内容。这是一个方便的接口，可以用来操纵存储在文件中的数据结构。实际上，你们将会在下个lab实现基于文件的mmap，下个lab结合了XV6的文件系统和虚拟内存，进而实现mmap。

mmap还可以用作他途。除了可以映射文件之外，还可以用来映射匿名的内存（Anonymous Memory）。这是sbrk（注，详见8.2）的替代方案，你可以向内核申请物理内存，然后映射到特定的虚拟内存地址。

mmap是实现应用程序虚拟内存的核心系统调用之一，我们稍后会将它与之前提到的特性关联起来。

![](/files/-MX9rlYGrR1scCdLxcgH)

除此之外，还需要有一些系统调用来支持论文中讨论到的特性。

mprotect系统调用（注，详见[man page](https://man7.org/linux/man-pages/man2/mprotect.2.html)）。当你将某个对象映射到了虚拟内存地址空间，你可以修改对应虚拟内存的权限，这样你就可以以特定的权限保护对象的一部分，或者整个对象。

![](/files/-MWvdTCfnoKGjG8R1V-G)

举个例子，通过上图对于mprotect的调用将权限设置成只读（R），这时，对于addr到addr+len这段地址，load指令还能执行，但是store指令将会变成Page Fault。类似的，如果你想要将一段地址空间变成完成不可访问的，那么可以在mprotect中的权限参数传入None，那么任何对于addr到addr+len这段地址的访问，都会生成Page Fault。

对应mmap还有一个系统调用munmap，它使得你可以移除一个地址或者一段内存地址的映射关系。如果你好奇这里是怎么工作的，你应该查看这些系统调用的[man page](https://man7.org/linux/man-pages/man2/)。

![](/files/-MWy6rGEBJ-PegyL0K64)

最后一个系统调用是sigaction，它本质上是用来处理signal。它使得应用程序可以设置好一旦特定的signal发生了，就调用特定的函数。可以给它传入函数f作为特定signal的handler。在Page Fault的场景下，生成的signal是segfault。你或许之前在用户代码中看过了segfault，通常来说当发生segfault时，应用程序会停止运行并crash。但是如果应用程序为segfault signal设置了handler，发生segfault时，应用程序不会停止，相应的handler会被内核调用，然后应用程序可以在handler中响应segfault。

当内核发现Page Fault时，或许会通过修复Page Table来使得应用程序还能继续执行。与内核响应Page Fault的方式类似，在这里的handler中或许会调用mprotect来修改内存的权限来避免segfault，这样应用程序的指令就可以恢复运行。

![](/files/-MWyBqwKaeUD2H070lEG)

与sigaction类似的有sigalarm（译注：sigalarm 是[traps lab](https://pdos.csail.mit.edu/6.S081/2020/labs/traps.html) 中实现的一个系统调用，不属于标准Unix 接口），在sigalarm中可以设置每隔一段时间就调用handler。sigaction也可以实现这个功能，只是它更加的通用，因为它可以响应不同类型的signal。

> 学生提问：看起来mprotect暗示了你可以为单独的地址添加不同的权限，然而在XV6中，我们只能为整个Page设置相同的权限，这里是有区别吗？
>
> Frans教授：不，这里并没有区别，它们都在Page粒度工作。如果你好奇的话，有一个单独的系统调用可以查看Page的大小。

如果你回想前面提到过的虚拟内存特性，我们可以将它们对应到这一节描述的Unix接口中来。

* trap对应的是sigaction系统调用
* Prot1，ProtN和Unprot可以使用mprotect系统调用来实现。mprotect足够的灵活，你可以用它来修改一个Page的权限，也可以用它来修改多个Page的权限。当修改多个Page的权限时，可以获得只清除一次TLB的好处。
* 查看Page的Dirty位要稍微复杂点，并没有一个直接的系统调用实现这个特性，不过你可以使用一些技巧完成它，我稍后会介绍它。
* map2也没有一个系统调用能直接对应它，通过多次调用mmap，你可以实现map2特性。

或许并不完全受这篇论文所驱动，但是内核开发人员已经在操作系统中为现在的应用程序提供了这些特性。接下来，我将在框架层面简单介绍一下这些特性是如何实现的，之后再看看应用程序是如何使用这些特性。
