mirror of
https://github.com/LCTT/TranslateProject.git
synced 2024-12-26 21:30:55 +08:00
PRF: 20181016 Lab 5- File system, Spawn and Shell.md
@qhwdw
This commit is contained in:
parent
fc311b4dfa
commit
2392354a6b
@ -1,4 +1,4 @@
|
||||
实验 5:文件系统、Spawn 和 Shell
|
||||
Caffeinated 6.828:实验 5:文件系统、Spawn 和 Shell
|
||||
======
|
||||
|
||||
### 简介
|
||||
@ -10,31 +10,31 @@
|
||||
使用 Git 去获取最新版的课程仓库,然后创建一个命名为 `lab5` 的本地分支,去跟踪远程的 `origin/lab5` 分支:
|
||||
|
||||
```
|
||||
athena% cd ~/6.828/lab
|
||||
athena% add git
|
||||
athena% git pull
|
||||
Already up-to-date.
|
||||
athena% git checkout -b lab5 origin/lab5
|
||||
Branch lab5 set up to track remote branch refs/remotes/origin/lab5.
|
||||
Switched to a new branch "lab5"
|
||||
athena% git merge lab4
|
||||
Merge made by recursive.
|
||||
.....
|
||||
athena%
|
||||
athena% cd ~/6.828/lab
|
||||
athena% add git
|
||||
athena% git pull
|
||||
Already up-to-date.
|
||||
athena% git checkout -b lab5 origin/lab5
|
||||
Branch lab5 set up to track remote branch refs/remotes/origin/lab5.
|
||||
Switched to a new branch "lab5"
|
||||
athena% git merge lab4
|
||||
Merge made by recursive.
|
||||
.....
|
||||
athena%
|
||||
```
|
||||
|
||||
在实验中这一部分的主要新组件是文件系统环境,它位于新的 `fs` 目录下。通过检查这个目录中的所有文件,我们来看一下新的文件都有什么。另外,在 `user` 和 `lib` 目录下还有一些文件系统相关的源文件。
|
||||
|
||||
fs/fs.c 维护文件系统在磁盘上结构的代码
|
||||
fs/bc.c 构建在我们的用户级页故障处理功能之上的一个简单的块缓存
|
||||
fs/ide.c 极简的基于 PIO(非中断驱动的)IDE 驱动程序代码
|
||||
fs/serv.c 使用文件系统 IPC 与客户端环境交互的文件系统服务器
|
||||
lib/fd.c 实现一个常见的类 UNIX 的文件描述符接口的代码
|
||||
lib/file.c 磁盘上文件类型的驱动,实现为一个文件系统 IPC 客户端
|
||||
lib/console.c 控制台输入/输出文件类型的驱动
|
||||
lib/spawn.c spawn 库调用的框架代码
|
||||
- `fs/fs.c` 维护文件系统在磁盘上结构的代码
|
||||
- `fs/bc.c` 构建在我们的用户级页故障处理功能之上的一个简单的块缓存
|
||||
- `fs/ide.c` 极简的基于 PIO(非中断驱动的)IDE 驱动程序代码
|
||||
- `fs/serv.c` 使用文件系统 IPC 与客户端环境交互的文件系统服务器
|
||||
- `lib/fd.c` 实现一个常见的类 UNIX 的文件描述符接口的代码
|
||||
- `lib/file.c` 磁盘上文件类型的驱动,实现为一个文件系统 IPC 客户端
|
||||
- `lib/console.c` 控制台输入/输出文件类型的驱动
|
||||
- `lib/spawn.c` spawn 库调用的框架代码
|
||||
|
||||
你应该再次去运行 `pingpong`、`primes`、和 `forktree`,测试实验 4 完成后合并到新的实验 5 中的代码能否正确运行。你还需要在 `kern/init.c` 中注释掉 `ENV_CREATE(fs_fs)` 行,因为 `fs/fs.c` 将尝试去做一些 I/O,而 JOS 到目前为止还不具备该功能。同样,在 `lib/exit.c` 中临时注释掉对 `close_all()` 的调用;这个函数将调用你在本实验后面部分去实现的子程序,如果现在去调用,它将导致 JOS 内核崩溃。如果你的实验 4 的代码没有任何 bug,将很完美地通过这个测试。在它们都能正常工作之前是不能继续后续实验的。在你开始做练习 1 时,不要忘记去取消这些行上的注释。
|
||||
你应该再次去运行 `pingpong`、`primes` 和 `forktree`,测试实验 4 完成后合并到新的实验 5 中的代码能否正确运行。你还需要在 `kern/init.c` 中注释掉 `ENV_CREATE(fs_fs)` 行,因为 `fs/fs.c` 将尝试去做一些 I/O,而 JOS 到目前为止还不具备该功能。同样,在 `lib/exit.c` 中临时注释掉对 `close_all()` 的调用;这个函数将调用你在本实验后面部分去实现的子程序,如果现在去调用,它将导致 JOS 内核崩溃。如果你的实验 4 的代码没有任何 bug,将很完美地通过这个测试。在它们都能正常工作之前是不能继续后续实验的。在你开始做练习 1 时,不要忘记去取消这些行上的注释。
|
||||
|
||||
如果它们不能正常工作,使用 `git diff lab4` 去重新评估所有的变更,确保你在实验 4(及以前)所写的代码在本实验中没有丢失。确保实验 4 仍然能正常工作。
|
||||
|
||||
@ -44,11 +44,11 @@ lib/spawn.c spawn 库调用的框架代码
|
||||
|
||||
### 文件系统的雏形
|
||||
|
||||
你将要使用的文件系统比起大多数“真正的”文件系统(包括 xv6 UNIX 的文件系统)要简单的多,但它也是很强大的,足够去提供基本的特性:创建、读取、写入、和删除组织在层次目录结构中的文件。
|
||||
你将要使用的文件系统比起大多数“真正的”文件系统(包括 xv6 UNIX 的文件系统)要简单的多,但它也是很强大的,足够去提供基本的特性:创建、读取、写入和删除组织在层次目录结构中的文件。
|
||||
|
||||
到目前为止,我们开发的是一个单用户操作系统,它提供足够的保护并能去捕获 bug,但它还不能在多个不可信用户之间提供保护。因此,我们的文件系统还不支持 UNIX 的所有者或权限的概念。我们的文件系统目前也不支持硬链接、时间戳、或像大多数 UNIX 文件系统实现的那些特殊的设备文件。
|
||||
|
||||
### 磁盘上文件系统结构
|
||||
### 磁盘上的文件系统结构
|
||||
|
||||
主流的 UNIX 文件系统将可用磁盘空间分为两种主要的区域类型:节点区域和数据区域。UNIX 文件系统在文件系统中为每个文件分配一个节点;一个文件的节点保存了这个文件重要的元数据,比如它的 `stat` 属性和指向数据块的指针。数据区域被分为更大的(一般是 8 KB 或更大)数据块,它在文件系统中存储文件数据和目录元数据。目录条目包含文件名字和指向到节点的指针;如果文件系统中的多个目录条目指向到那个文件的节点上,则称该文件是硬链接的。由于我们的文件系统不支持硬链接,所以我们不需要这种间接的级别,并且因此可以更方便简化:我们的文件系统将压根就不使用节点,而是简单地将文件的(或子目录的)所有元数据保存在描述那个文件的(唯一的)目录条目中。
|
||||
|
||||
@ -71,6 +71,7 @@ UNIX xv6 文件系统使用 512 字节大小的块,与它底层磁盘的扇区
|
||||
#### 文件元数据
|
||||
|
||||
![File structure][2]
|
||||
|
||||
元数据的布局是描述在我们的文件系统中的一个文件中,这个文件就是 `inc/fs.h` 中的 `struct File`。元数据包含文件的名字、大小、类型(普通文件还是目录)、指向构成这个文件的块的指针。正如前面所提到的,我们的文件系统中并没有节点,因此元数据是保存在磁盘上的一个目录条目中,而不是像大多数“真正的”文件系统那样保存在节点中。为简单起见,我们将使用 `File` 这一个结构去表示文件元数据,因为它要同时出现在磁盘上和内存中。
|
||||
|
||||
在 `struct File` 中的数组 `f_direct` 包含一个保存文件的前 10 个块(`NDIRECT`)的块编号的空间,我们称之为文件的直接块。对于最大 `10*4096 = 40KB` 的小文件,这意味着这个文件的所有块的块编号将全部直接保存在结构 `File` 中,但是,对于超过 40 KB 大小的文件,我们需要一个地方去保存文件剩余的块编号。所以我们分配一个额外的磁盘块,我们称之为文件的间接块,由它去保存最多 4096/4 = 1024 个额外的块编号。因此,我们的文件系统最多允许有 1034 个块,或者说不能超过 4MB 大小。为支持大文件,“真正的”文件系统一般都支持两个或三个间接块。
|
||||
@ -91,33 +92,28 @@ UNIX xv6 文件系统使用 512 字节大小的块,与它底层磁盘的扇区
|
||||
|
||||
只要我们依赖轮询、基于 “编程的 I/O”(PIO)的磁盘访问,并且不使用磁盘中断,那么在用户空间中实现磁盘访问还是很容易的。也可以去实现由中断驱动的设备驱动程序(比如像 L3 和 L4 内核就是这么做的),但这样做的话,内核必须接收设备中断并将它派发到相应的用户模式环境上,这样实现的难度会更大。
|
||||
|
||||
x86 处理器在 EFLAGS 寄存器中使用 IOPL 位去确定保护模式中的代码是否允许执行特定的设备 I/O 指令,比如 `IN` 和 `OUT` 指令。由于我们需要的所有 IDE 磁盘寄存器都位于 x86 的 I/O 空间中而不是映射在内存中,所以,为了允许文件系统去访问这些寄存器,我们需要做的唯一的事情便是授予文件系统环境“I/O 权限”。实际上,在 EFLAGS 寄存器的 IOPL 位上规定,内核使用一个简单的“要么全都能访问、要么全都不能访问”的方法来控制用户模式中的代码能否访问 I/O 空间。在我们的案例中,我们希望文件系统环境能够去访问 I/O 空间,但我们又希望任何其它的环境完全不能访问 I/O 空间。
|
||||
x86 处理器在 EFLAGS 寄存器中使用 IOPL 位去确定保护模式中的代码是否允许执行特定的设备 I/O 指令,比如 `IN` 和 `OUT` 指令。由于我们需要的所有 IDE 磁盘寄存器都位于 x86 的 I/O 空间中而不是映射在内存中,所以,为了允许文件系统去访问这些寄存器,我们需要做的唯一的事情便是授予文件系统环境“I/O 权限”。实际上,在 EFLAGS 寄存器的 IOPL 位上规定,内核使用一个简单的“要么全都能访问、要么全都不能访问”的方法来控制用户模式中的代码能否访问 I/O 空间。在我们的案例中,我们希望文件系统环境能够去访问 I/O 空间,但我们又希望任何其它的环境完全不能访问 I/O 空间。
|
||||
|
||||
```markdown
|
||||
练习 1、`i386_init` 通过将类型 `ENV_TYPE_FS` 传递给你的环境创建函数 `env_create` 来识别文件系统。修改 `env.c` 中的 `env_create` ,以便于它只授予文件系统环境 I/O 的权限,而不授予任何其它环境 I/O 的权限。
|
||||
> **练习 1**、`i386_init` 通过将类型 `ENV_TYPE_FS` 传递给你的环境创建函数 `env_create` 来识别文件系统。修改 `env.c` 中的 `env_create` ,以便于它只授予文件系统环境 I/O 的权限,而不授予任何其它环境 I/O 的权限。
|
||||
|
||||
确保你能启动这个文件系统环境,而不会产生一般保护故障。你应该要通过在 `make grade` 中的 "fs i/o" 测试。
|
||||
```
|
||||
> 确保你能启动这个文件系统环境,而不会产生一般保护故障。你应该要通过在 `make grade` 中的 "fs i/o" 测试。
|
||||
|
||||
```markdown
|
||||
问题
|
||||
|
||||
1、当你从一个环境切换到另一个环境时,你是否需要做一些操作来确保 I/O 权限设置能被保存和正确地恢复?为什么?
|
||||
```
|
||||
.
|
||||
|
||||
> **问题 1**、当你从一个环境切换到另一个环境时,你是否需要做一些操作来确保 I/O 权限设置能被保存和正确地恢复?为什么?
|
||||
|
||||
注意本实验中的 `GNUmakefile` 文件,它用于设置 QEMU 去使用文件 `obj/kern/kernel.img` 作为磁盘 0 的镜像(一般情况下表示 DOS 或 Windows 中的 “C 盘”),以及使用(新)文件 `obj/fs/fs.img` 作为磁盘 1 的镜像(”D 盘“)。在本实验中,我们的文件系统应该仅与磁盘 1 有交互;而磁盘 0 仅用于去引导内核。如果你想去恢复其中一个有某些错误的磁盘镜像,你可以通过输入如下的命令,去重置它们到最初的、”崭新的“版本:
|
||||
|
||||
```
|
||||
$ rm obj/kern/kernel.img obj/fs/fs.img
|
||||
$ make
|
||||
$ rm obj/kern/kernel.img obj/fs/fs.img
|
||||
$ make
|
||||
```
|
||||
|
||||
或者:
|
||||
|
||||
```
|
||||
$ make clean
|
||||
$ make
|
||||
$ make clean
|
||||
$ make
|
||||
```
|
||||
|
||||
小挑战!实现中断驱动的 IDE 磁盘访问,既可以使用也可以不使用 DMA 模式。由你来决定是否将设备驱动移植进内核中、还是与文件系统一样保留在用户空间中、甚至是将它移植到一个它自己的的单独的环境中(如果你真的想了解微内核的本质的话)。
|
||||
@ -132,45 +128,39 @@ x86 处理器在 EFLAGS 寄存器中使用 IOPL 位去确定保护模式中的
|
||||
|
||||
当然,将整个磁盘读入到内存中需要很长时间,因此,我们将它实现成”按需“分页的形式,那样我们只在磁盘映射区域中分配页,并且当在这个区域中产生页故障时,从磁盘读取相关的块去响应这个页故障。通过这种方式,我们能够假装将整个磁盘装进了内存中。
|
||||
|
||||
```markdown
|
||||
练习 2、在 `fs/bc.c` 中实现 `bc_pgfault` 和 `flush_block` 函数。`bc_pgfault` 函数是一个页故障服务程序,就像你在前一个实验中编写的写时复制 fork 一样,只不过它的任务是从磁盘中加载页去响应一个页故障。在你编写它时,记住: (1) `addr` 可能并不会做边界对齐,并且 (2) 在扇区中的 `ide_read` 操作并不是以块为单位的。
|
||||
> **练习 2**、在 `fs/bc.c` 中实现 `bc_pgfault` 和 `flush_block` 函数。`bc_pgfault` 函数是一个页故障服务程序,就像你在前一个实验中编写的写时复制 fork 一样,只不过它的任务是从磁盘中加载页去响应一个页故障。在你编写它时,记住: (1) `addr` 可能并不会做边界对齐,并且 (2) 在扇区中的 `ide_read` 操作并不是以块为单位的。
|
||||
|
||||
(如果需要的话)函数 `flush_block` 应该会将一个块写入到磁盘上。如果在块缓存中没有块(也就是说,页没有映射)或者它不是一个脏块,那么 `flush_block` 将什么都不做。我们将使用虚拟内存硬件去跟踪,磁盘块自最后一次从磁盘读取或写入到磁盘之后是否被修改过。查看一个块是否需要写入时,我们只需要去查看 `uvpt` 条目中的 `PTE_D` 的 ”dirty“ 位即可。(`PTE_D` 位由处理器设置,用于表示那个页被写入;具体细节可以查看 x386 参考手册的 [第 5 章][3] 的 5.2.4.3 节)块被写入到磁盘上之后,`flush_block` 函数将使用 `sys_page_map` 去清除 `PTE_D` 位。
|
||||
>(如果需要的话)函数 `flush_block` 应该会将一个块写入到磁盘上。如果在块缓存中没有块(也就是说,页没有映射)或者它不是一个脏块,那么 `flush_block` 将什么都不做。我们将使用虚拟内存硬件去跟踪,磁盘块自最后一次从磁盘读取或写入到磁盘之后是否被修改过。查看一个块是否需要写入时,我们只需要去查看 `uvpt` 条目中的 `PTE_D` 的 ”dirty“ 位即可。(`PTE_D` 位由处理器设置,用于表示那个页被写入;具体细节可以查看 x386 参考手册的 [第 5 章][3] 的 5.2.4.3 节)块被写入到磁盘上之后,`flush_block` 函数将使用 `sys_page_map` 去清除 `PTE_D` 位。
|
||||
|
||||
使用 `make grade` 去测试你的代码。你的代码应该能够通过 "check_bc"、"check_super"、和 "check_bitmap" 的测试。
|
||||
```
|
||||
> 使用 `make grade` 去测试你的代码。你的代码应该能够通过 "check_bc"、"check_super"、和 "check_bitmap" 的测试。
|
||||
|
||||
在 `fs/fs.c` 中的函数 `fs_init` 是块缓存使用的一个很好的示例。在初始化块缓存之后,它简单地在全局变量 `super` 中保存指针到磁盘映射区。在这之后,如果块在内存中,或我们的页故障服务程序按需将它们从磁盘上读入后,我们就能够简单地从 `super` 结构中读取块了。
|
||||
|
||||
```markdown
|
||||
小挑战!到现在为止,块缓存还没有清除策略。一旦某个块因为页故障被读入到缓存中之后,它将一直不会被清除,并且永远保留在内存中。给块缓存增加一个清除策略。在页表中使用 `PTE_A` 的 "accessed" 位来实现,任何环境访问一个页时,硬件就会设置这个位,你可以通过它来跟踪磁盘块的大致使用情况,而不需要修改访问磁盘映射区域的任何代码。使用脏块要小心。
|
||||
```
|
||||
.
|
||||
|
||||
> **小挑战!**到现在为止,块缓存还没有清除策略。一旦某个块因为页故障被读入到缓存中之后,它将一直不会被清除,并且永远保留在内存中。给块缓存增加一个清除策略。在页表中使用 `PTE_A` 的 "accessed" 位来实现,任何环境访问一个页时,硬件就会设置这个位,你可以通过它来跟踪磁盘块的大致使用情况,而不需要修改访问磁盘映射区域的任何代码。使用脏块要小心。
|
||||
|
||||
### 块位图
|
||||
|
||||
在 `fs_init` 设置了 `bitmap` 指针之后,我们可以认为 `bitmap` 是一个装满比特位的数组,磁盘上的每个块就是数组中的其中一个比特位。比如 `block_is_free`,它只是简单地在位图中检查给定的块是否被标记为空闲。
|
||||
|
||||
```markdown
|
||||
练习 3、使用 `free_block` 作为实现 `fs/fs.c` 中的 `alloc_block` 的一个模型,它将在位图中去查找一个空闲的磁盘块,并将它标记为已使用,然后返回块编号。当你分配一个块时,你应该立即使用 `flush_block` 将已改变的位图块刷新到磁盘上,以确保文件系统的一致性。
|
||||
> **练习 3**、使用 `free_block` 作为实现 `fs/fs.c` 中的 `alloc_block` 的一个模型,它将在位图中去查找一个空闲的磁盘块,并将它标记为已使用,然后返回块编号。当你分配一个块时,你应该立即使用 `flush_block` 将已改变的位图块刷新到磁盘上,以确保文件系统的一致性。
|
||||
|
||||
使用 `make grade` 去测试你的代码。现在,你的代码应该要通过 "alloc_block" 的测试。
|
||||
```
|
||||
> 使用 `make grade` 去测试你的代码。现在,你的代码应该要通过 "alloc_block" 的测试。
|
||||
|
||||
### 文件操作
|
||||
|
||||
在 `fs/fs.c` 中,我们提供一系列的函数去实现基本的功能,比如,你将需要去理解和管理结构 `File`、扫描和管理目录”文件“的条目、 以及从根目录开始遍历文件系统以解析一个绝对路径名。阅读 `fs/fs.c` 中的所有代码,并在你开始实验之前,确保你理解了每个函数的功能。
|
||||
|
||||
```markdown
|
||||
练习 4、实现 `file_block_walk` 和 `file_get_block`。`file_block_walk` 从一个文件中的块偏移量映射到 `struct File` 中那个块的指针上或间接块上,它非常类似于 `pgdir_walk` 在页表上所做的事。`file_get_block` 将更进一步,将去映射一个真实的磁盘块,如果需要的话,去分配一个新的磁盘块。
|
||||
> **练习 4**、实现 `file_block_walk` 和 `file_get_block`。`file_block_walk` 从一个文件中的块偏移量映射到 `struct File` 中那个块的指针上或间接块上,它非常类似于 `pgdir_walk` 在页表上所做的事。`file_get_block` 将更进一步,将去映射一个真实的磁盘块,如果需要的话,去分配一个新的磁盘块。
|
||||
|
||||
使用 `make grade` 去测试你的代码。你的代码应该要通过 "file_open"、"file_get_block"、以及 "file_flush/file_truncated/file rewrite"、和 "testfile" 的测试。
|
||||
```
|
||||
> 使用 `make grade` 去测试你的代码。你的代码应该要通过 "file_open"、"file_get_block"、以及 "file_flush/file_truncated/file rewrite"、和 "testfile" 的测试。
|
||||
|
||||
`file_block_walk` 和 `file_get_block` 是文件系统中的”劳动模范“。比如,`file_read` 和 `file_write` 或多或少都在 `file_get_block` 上做必需的登记工作,然后在分散的块和连续的缓存之间复制字节。
|
||||
|
||||
```
|
||||
小挑战!如果操作在中途实然被打断(比如,突然崩溃或重启),文件系统很可能会产生错误。实现软件更新或日志处理的方式让文件系统的”崩溃可靠性“更好,并且演示一下旧的文件系统可能会崩溃,而你的更新后的文件系统不会崩溃的情况。
|
||||
```
|
||||
.
|
||||
|
||||
> **小挑战!**如果操作在中途实然被打断(比如,突然崩溃或重启),文件系统很可能会产生错误。实现软件更新或日志处理的方式让文件系统的”崩溃可靠性“更好,并且演示一下旧的文件系统可能会崩溃,而你的更新后的文件系统不会崩溃的情况。
|
||||
|
||||
### 文件系统接口
|
||||
|
||||
@ -207,19 +197,17 @@ x86 处理器在 EFLAGS 寄存器中使用 IOPL 位去确定保护模式中的
|
||||
|
||||
服务器也通过 IPC 来发送响应。我们为函数的返回代码使用 32 位的数字。对于大多数 RPC,这已经涵盖了它们全部的返回代码。`FSREQ_READ` 和 `FSREQ_STAT` 也返回数据,它们只是被简单地写入到客户端发送它的请求时的页上。在 IPC 的响应中并不需要去发送这个页,因为这个页是文件系统服务器和客户端从一开始就共享的页。另外,在它的响应中,`FSREQ_OPEN` 与客户端共享一个新的 "Fd page”。我们将快捷地返回到文件描述符页上。
|
||||
|
||||
```markdown
|
||||
练习 5、实现 `fs/serv.c` 中的 `serve_read`。
|
||||
> **练习 5**、实现 `fs/serv.c` 中的 `serve_read`。
|
||||
|
||||
`serve_read` 的重任将由已经在 `fs/fs.c` 中实现的 `file_read` 来承担(它实际上不过是对 `file_get_block` 的一连串调用)。对于文件读取,`serve_read` 只能提供 RPC 接口。查看 `serve_set_size` 中的注释和代码,去大体上了解服务器函数的结构。
|
||||
> `serve_read` 的重任将由已经在 `fs/fs.c` 中实现的 `file_read` 来承担(它实际上不过是对 `file_get_block` 的一连串调用)。对于文件读取,`serve_read` 只能提供 RPC 接口。查看 `serve_set_size` 中的注释和代码,去大体上了解服务器函数的结构。
|
||||
|
||||
使用 `make grade` 去测试你的代码。你的代码通过 "serve_open/file_stat/file_close" 和 "file_read" 的测试后,你得分应该是 70(总分为 150)。
|
||||
```
|
||||
> 使用 `make grade` 去测试你的代码。你的代码通过 "serve_open/file_stat/file_close" 和 "file_read" 的测试后,你得分应该是 70(总分为 150)。
|
||||
|
||||
```markdown
|
||||
练习 6、实现 `fs/serv.c` 中的 `serve_write` 和 `lib/file.c` 中的 `devfile_write`。
|
||||
.
|
||||
|
||||
使用 `make grade` 去测试你的代码。你的代码通过 "file_write"、"file_read after file_write"、"open"、和 "large file" 的测试后,得分应该是 90(总分为150)。
|
||||
```
|
||||
> **练习 6**、实现 `fs/serv.c` 中的 `serve_write` 和 `lib/file.c` 中的 `devfile_write`。
|
||||
|
||||
> 使用 `make grade` 去测试你的代码。你的代码通过 "file_write"、"file_read after file_write"、"open"、和 "large file" 的测试后,得分应该是 90(总分为150)。
|
||||
|
||||
### 进程增殖
|
||||
|
||||
@ -227,21 +215,19 @@ x86 处理器在 EFLAGS 寄存器中使用 IOPL 位去确定保护模式中的
|
||||
|
||||
我们实现的是 `spawn`,而不是一个类 UNIX 的 `exec`,因为 `spawn` 是很容易从用户空间中、以”外内核式“ 实现的,它无需来自内核的特别帮助。为了在用户空间中实现 `exec`,想一想你应该做什么?确保你理解了它为什么很难。
|
||||
|
||||
```markdown
|
||||
练习 7、`spawn` 依赖新的系统调用 `sys_env_set_trapframe` 去初始化新创建的环境的状态。实现 `kern/syscall.c` 中的 `sys_env_set_trapframe`。(不要忘记在 `syscall()` 中派发新系统调用)
|
||||
> **练习 7**、`spawn` 依赖新的系统调用 `sys_env_set_trapframe` 去初始化新创建的环境的状态。实现 `kern/syscall.c` 中的 `sys_env_set_trapframe`。(不要忘记在 `syscall()` 中派发新系统调用)
|
||||
|
||||
运行来自 `kern/init.c` 中的 `user/spawnhello` 程序来测试你的代码`kern/init.c` ,它将尝试从文件系统中增殖 `/hello`。
|
||||
> 运行来自 `kern/init.c` 中的 `user/spawnhello` 程序来测试你的代码`kern/init.c` ,它将尝试从文件系统中增殖 `/hello`。
|
||||
|
||||
使用 `make grade` 去测试你的代码。
|
||||
```
|
||||
> 使用 `make grade` 去测试你的代码。
|
||||
|
||||
```markdown
|
||||
小挑战!实现 Unix 式的 `exec`。
|
||||
```
|
||||
.
|
||||
|
||||
```markdown
|
||||
小挑战!实现 `mmap` 式的文件内存映射,并如果可能的话,修改 `spawn` 从 ELF 中直接映射页。
|
||||
```
|
||||
> **小挑战!**实现 Unix 式的 `exec`。
|
||||
|
||||
.
|
||||
|
||||
> **小挑战!**实现 `mmap` 式的文件内存映射,并如果可能的话,修改 `spawn` 从 ELF 中直接映射页。
|
||||
|
||||
### 跨 fork 和 spawn 共享库状态
|
||||
|
||||
@ -255,11 +241,9 @@ UNIX 文件描述符是一个通称的概念,它还包括管道、控制台 I/
|
||||
|
||||
我们在 `inc/lib.h` 中定义了一个新的 `PTE_SHARE` 位,在 Intel 和 AMD 的手册中,这个位是被标记为”软件可使用的“的三个 PTE 位之一。我们将创建一个约定,如果一个页表条目中这个位被设置,那么在 `fork` 和 `spawn` 中应该直接从父环境中复制 PTE 到子环境中。注意它与标记为写时复制的差别:正如在第一段中所描述的,我们希望确保能共享页更新。
|
||||
|
||||
```markdown
|
||||
练习 8、修改 `lib/fork.c` 中的 `duppage`,以遵循最新约定。如果页表条目设置了 `PTE_SHARE` 位,仅直接复制映射。(你应该去使用 `PTE_SYSCALL`,而不是 `0xfff`,去从页表条目中掩掉有关的位。`0xfff` 仅选出可访问的位和脏位。)
|
||||
> **练习 8**、修改 `lib/fork.c` 中的 `duppage`,以遵循最新约定。如果页表条目设置了 `PTE_SHARE` 位,仅直接复制映射。(你应该去使用 `PTE_SYSCALL`,而不是 `0xfff`,去从页表条目中掩掉有关的位。`0xfff` 仅选出可访问的位和脏位。)
|
||||
|
||||
同样的,在 `lib/spawn.c` 中实现 `copy_shared_pages`。它应该循环遍历当前进程中所有的页表条目(就像 `fork` 那样),复制任何设置了 `PTE_SHARE` 位的页映射到子进程中。
|
||||
```
|
||||
> 同样的,在 `lib/spawn.c` 中实现 `copy_shared_pages`。它应该循环遍历当前进程中所有的页表条目(就像 `fork` 那样),复制任何设置了 `PTE_SHARE` 位的页映射到子进程中。
|
||||
|
||||
使用 `make run-testpteshare` 去检查你的代码行为是否正确。正确的情况下,你应该会看到像 "`fork handles PTE_SHARE right`" 和 "`spawn handles PTE_SHARE right`” 这样的输出行。
|
||||
|
||||
@ -269,9 +253,7 @@ UNIX 文件描述符是一个通称的概念,它还包括管道、控制台 I/
|
||||
|
||||
为了能让 shell 工作,我们需要一些方式去输入。QEMU 可以显示输出,我们将它的输出写入到 CGA 显示器上和串行端口上,但到目前为止,我们仅能够在内核监视器中接收输入。在 QEMU 中,我们从图形化窗口中的输入作为从键盘到 JOS 的输入,虽然键入到控制台的输入作为出现在串行端口上的字符的方式显现。在 `kern/console.c` 中已经包含了由我们自实验 1 以来的内核监视器所使用的键盘和串行端口的驱动程序,但现在你需要去把这些增加到系统中。
|
||||
|
||||
```markdown
|
||||
练习 9、在你的 `kern/trap.c` 中,调用 `kbd_intr` 去处理捕获 `IRQ_OFFSET+IRQ_KBD` 和 `serial_intr`,用它们去处理捕获 `IRQ_OFFSET+IRQ_SERIAL`。
|
||||
```
|
||||
> **练习 9**、在你的 `kern/trap.c` 中,调用 `kbd_intr` 去处理捕获 `IRQ_OFFSET+IRQ_KBD` 和 `serial_intr`,用它们去处理捕获 `IRQ_OFFSET+IRQ_SERIAL`。
|
||||
|
||||
在 `lib/console.c` 中,我们为你实现了文件的控制台输入/输出。`kbd_intr` 和 `serial_intr` 将使用从最新读取到的输入来填充缓冲区,而控制台文件类型去排空缓冲区(默认情况下,控制台文件类型为 stdin/stdout,除非用户重定向它们)。
|
||||
|
||||
@ -282,40 +264,38 @@ UNIX 文件描述符是一个通称的概念,它还包括管道、控制台 I/
|
||||
运行 `make run-icode` 或 `make run-icode-nox` 将运行你的内核并启动 `user/icode`。`icode` 又运行 `init`,它将设置控制台作为文件描述符 0 和 1(即:标准输入 stdin 和标准输出 stdout),然后增殖出环境 `sh`,就是 shell。之后你应该能够运行如下的命令了:
|
||||
|
||||
```
|
||||
echo hello world | cat
|
||||
cat lorem |cat
|
||||
cat lorem |num
|
||||
cat lorem |num |num |num |num |num
|
||||
lsfd
|
||||
echo hello world | cat
|
||||
cat lorem |cat
|
||||
cat lorem |num
|
||||
cat lorem |num |num |num |num |num
|
||||
lsfd
|
||||
```
|
||||
|
||||
注意用户库常规程序 `cprintf` 将直接输出到控制台,而不会使用文件描述符代码。这对调试非常有用,但是对管道连接其它程序却很不利。为将输出打印到特定的文件描述符(比如 1,它是标准输出 stdout),需要使用 `fprintf(1, "...", ...)`。`printf("...", ...)` 是将输出打印到文件描述符 1(标准输出 stdout) 的快捷方式。查看 `user/lsfd.c` 了解更多示例。
|
||||
|
||||
```markdown
|
||||
练习 10、
|
||||
这个 shell 不支持 I/O 重定向。如果能够运行 `run sh <script` 就更完美了,就不用将所有的命令手动去放入一个脚本中,就像上面那样。为 `<` 在 `user/sh.c` 中添加重定向的功能。
|
||||
> **练习 10**、这个 shell 不支持 I/O 重定向。如果能够运行 `run sh <script` 就更完美了,就不用将所有的命令手动去放入一个脚本中,就像上面那样。为 `<` 在 `user/sh.c` 中添加重定向的功能。
|
||||
|
||||
通过在你的 shell 中输入 `sh <script` 来测试你实现的重定向功能。
|
||||
> 通过在你的 shell 中输入 `sh <script` 来测试你实现的重定向功能。
|
||||
|
||||
运行 `make run-testshell` 去测试你的 shell。`testshell` 只是简单地给 shell ”喂“上面的命令(也可以在 `fs/testshell.sh` 中找到),然后检查它的输出是否与 `fs/testshell.key` 一样。
|
||||
```
|
||||
> 运行 `make run-testshell` 去测试你的 shell。`testshell` 只是简单地给 shell ”喂“上面的命令(也可以在 `fs/testshell.sh` 中找到),然后检查它的输出是否与 `fs/testshell.key` 一样。
|
||||
|
||||
```markdown
|
||||
小挑战!给你的 shell 添加更多的特性。最好包括以下的特性(其中一些可能会要求修改文件系统):
|
||||
.
|
||||
|
||||
* 后台命令 (`ls &`)
|
||||
* 一行中运行多个命令 (`ls; echo hi`)
|
||||
* 命令组 (`(ls; echo hi) | cat > out`)
|
||||
* 扩展环境变量 (`echo $hello`)
|
||||
* 引号 (`echo "a | b"`)
|
||||
* 命令行历史和/或编辑功能
|
||||
* tab 命令补全
|
||||
* 为命令行查找目录、cd 和路径
|
||||
* 文件创建
|
||||
* 用快捷键 `ctl-c` 去杀死一个运行中的环境
|
||||
|
||||
可做的事情还有很多,并不仅限于以上列表。
|
||||
```
|
||||
> **小挑战!**给你的 shell 添加更多的特性。最好包括以下的特性(其中一些可能会要求修改文件系统):
|
||||
|
||||
> * 后台命令 (`ls &`)
|
||||
> * 一行中运行多个命令 (`ls; echo hi`)
|
||||
> * 命令组 (`(ls; echo hi) | cat > out`)
|
||||
> * 扩展环境变量 (`echo $hello`)
|
||||
> * 引号 (`echo "a | b"`)
|
||||
> * 命令行历史和/或编辑功能
|
||||
> * tab 命令补全
|
||||
> * 为命令行查找目录、cd 和路径
|
||||
> * 文件创建
|
||||
> * 用快捷键 `ctl-c` 去杀死一个运行中的环境
|
||||
|
||||
> 可做的事情还有很多,并不仅限于以上列表。
|
||||
|
||||
到目前为止,你的代码应该要通过所有的测试。和以前一样,你可以使用 `make grade` 去评级你的提交,并且使用 `make handin` 上交你的实验。
|
||||
|
||||
@ -328,7 +308,7 @@ via: https://pdos.csail.mit.edu/6.828/2018/labs/lab5/
|
||||
作者:[csail.mit][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[qhwdw](https://github.com/qhwdw)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user