持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第19天,点击查看活动详情
在更新实验的时候还要去学,axios 救命!还是前端的东西比较适合我一些
上次的文章中记录了 Unix 提供fork()系统调用作为其主要进程创建基元。fork()系统调用复制调用进程(父进程)的地址空间以创建新进程(子进程)。
这次搞搞写时复制
写时复制
xv6 Unix 通过将父级页面中的所有数据复制到分配给子进程的新页面中来实现fork()。这基本上与dumbfork()采用的方法相同。将父级的地址空间复制到子进程中是fork()操作中最昂贵的部分。
但是,在子进程中,fork()调用通常几乎立即调用exec(),这会用新程序替换子进程的记忆。例如,这就是外壳通常执行的操作。在这种情况下,复制父级的地址空间所花费的时间在很大程度上被浪费了,因为子进程在调用 exec()之前将使用很少的内存。
出于这个原因,更高版本的Unix利用虚拟内存硬件,允许父级和子进程共享映射到其各自地址空间的内存,直到其中一个进程实际修改它。此技术称为写入时复制。为此,内核上fork()会将地址空间映射从父级复制到子进程,而不是映射页的内容,同时将现在共享的页标记为只读。当两个进程中的一个尝试写入其中一个共享页时,该进程会出现页错误。在这一点上,Unix内核意识到该页面实际上是一个“虚拟”或“写入时复制”副本,因此它为错误过程制作了一个新的,私有的,可写的页面副本。通过这种方式,单个页面的内容在实际写入之前不会实际复制。这种优化使得 fork()后跟一个exec()在子进程中开销更小:exec()子进程可能只需要在调用 之前复制一页(其堆栈的当前页面)。
在本实验的下一部分中,将实现一个“适当的”类Unix的fork(),其中包含写入时复制,作为用户空间库例程。在用户空间中实现fork()和写入时复制支持的好处是内核仍然更简单,因此更有可能是正确的。它还允许各个用户模式程序为 fork()定义自己的语义。想要稍微不同的实现的程序(例如,昂贵的始终复制版本,如dumbfork()或者父级和子进程实际上共享内存的版本)可以很容易地提供自己的实现。
用户级页面错误处理
用户级写入时复制fork()需要了解写保护页上的页面错误,因此这是您将首先实现的内容。写入时复制只是用户级页面错误处理的众多可能用途之一。
设置地址空间以便页面错误指示何时需要执行某些操作是很常见的。例如,大多数Unix内核最初只映射新进程的堆栈区域中的单个页面,然后随着进程的堆栈消耗增加并导致尚未映射的堆栈地址上的页面错误,稍后“按需”分配和映射其他堆栈页面。典型的Unix内核必须跟踪当进程空间的每个区域发生页面错误时要执行的操作。例如,堆栈区域中的故障通常会分配和映射物理内存的新页面。程序的BSS区域中的错误通常会分配一个新页面,用零填充它,然后映射它。在具有按需分页可执行文件的系统中,文本区域中的故障将从磁盘上读取二进制文件的相应页面,然后将其映射。
这是内核要跟踪的大量信息。而不是采用传统的Unix方法,您将决定如何处理用户空间中的每个页面错误,其中错误具有较小的破坏性。这种设计还有一个额外的好处,即允许程序在定义其内存区域时具有极大的灵活性。稍后将使用用户级页面错误处理来映射和访问基于磁盘的文件系统上的文件。
置页面错误处理程序
为了处理自己的页面错误,用户环境需要向 JOS 内核注册页面错误处理程序入口点。用户环境通过新的sys_env_set_pgfault_upcall系统调用注册其页面错误入口点。我们已向Env结构中添加了一个新成员 env_pgfault_upcall,以记录此信息。
Exercise 8. 实现sys_env_set_pgfault_upcall系统调用。请务必在查找目标环境的环境 ID 时启用权限检查,因为这是一个“危险”的系统调用。
结余
这章写的大部分(全部)都是文字,阅读起来很麻烦,但真的很难理解透彻,记完笔记觉得自己环视毫无进展