本文为 CS 162 Project File System 官方指南的中文译本。
原文:https://cs162.org/static/proj/proj-filesys/
归档参考:https://web.archive.org/web/20251215163710/https://cs162.org/static/
- 代码:与其他 project 共用
../src/,本目录只有指南。 - 文档:File systems 专节本仓库译本未收录,可对照官网 File systems;通用文档见
../pintos-docs/。 - 构建 / 测试(在仓库根目录下):
cd src/filesys
make
make check
课上常要求从 Userprog 完成点重建 src/ 再做本 project;自学若一直用同一份 src/ 演进,可跳过下方官方 Setup 中的 checkout 步骤,直接在现有代码上改 filesys/。
来源:https://cs162.org/static/proj/proj-filesys/
欢迎来到 Project File Systems!在本 project 中,你将为 Pintos 的 file system 增加若干功能。
在 Project User Programs 中,你已实现了 file system 相关 syscalls 的大量功能。不过,当时大量内部细节被抽象掉了——你调用的是已有的 file system 函数。在本 project 中,你将深入 file system,一直深入到磁盘上存储的字节。此外,你还将添加 buffer cache,以加速对磁盘的访问。
本作业的细节见下方 Tasks。不过,你可能发现先阅读一部分 pintos-docs / 官网 File systems 章节会有帮助;这往往有助于理解所需任务。
即便你在先前的 projects 中已经读过其中一些内容,我们仍建议(重新)阅读:
- File systems(官网专节;本仓库译本未收录)
本 project 建立在 Userprog(以及你已完成的 Threads 改动,若有)之上。在本仓库中继续使用根目录下的 src/ 即可。
若你希望在动手前打快照,可自行:
git tag proj-threads-completed # 或 proj-userprog-completed
官方课程里曾要求用 tag 把 src/ 恢复到 Userprog 完成态;自学若代码一直在同一棵树上演进,一般不必执行 rm -rf src/ / git checkout ... -- src/。
来源:https://cs162.org/static/proj/proj-filesys/docs/tasks/
来源:https://cs162.org/static/proj/proj-filesys/docs/tasks/buffer-cache/
当前,每次调用 inode_read_at 与 inode_write_at 时,都会直接访问 file system 底层的 block device。你的任务是为 file system 添加 buffer cache,以提升读、写性能。你的 buffer cache 将缓存单个 disk blocks,从而(1)可以用缓存数据响应读请求,以及(2)可以将多次写合并为一次磁盘操作。Buffer cache 的最大容量应为 64 个 disk blocks。你可以自行选择 block replacement policy,但它应是基于 locality 假设对 MIN 的近似。例如,使用 LRU、NRU(clock)、n-th chance clock 或 second-chance lists 是可接受的,但使用 FIFO、RANDOM 或 MRU 则不可接受。若采用你自己设计的 replacement policy,需要给出充分的理由。Buffer cache 必须是 write back cache,而不是 write-through cache。你必须确保所有磁盘操作都使用你的 buffer cache,而不仅仅是前面提到的两个 inode 函数。
修改 file system,使其维护一份 file blocks 的 cache。当有读或写某个 block 的请求时,检查该 block 是否已在 cache 中;若在,则直接使用缓存数据,无需访问磁盘。否则,将该 block 从磁盘取入 cache,必要时淘汰一个较旧的 entry。你的 cache 大小不得超过 64 个 sectors。
你必须实现一种至少不差于 “clock” algorithm 的 cache replacement algorithm。我们鼓励你考虑 metadata 通常比 data 更有价值这一点。你可以实验 accessed、dirty 等信息的不同组合,以磁盘访问次数衡量,看何种组合性能最好。从 filesys/build 目录运行 Pintos 时,会在 kernel 关闭前向 console 打印磁盘读、写操作的合计次数。
当某个 thread 正在向/从某个 buffer cache block 主动写入或读取数据时,你必须确保其他 threads 无法淘汰该 block。同理,在从 cache 淘汰某个 block 期间,其他 threads 应被阻止访问该 block。若某个 block 正在被载入 cache,其他 threads 也需要被阻止将其载入另一个不同的 cache entry。此外,在 block 完全载入之前,其他 threads 不得访问它。如果你愿意,可以将 free map 的一份缓存副本永久放在内存中的特殊位置。它不计入 64 sector 的限制。
所提供的 inode 代码使用通过 malloc() 分配的 “bounce buffer”,将磁盘按 sector 的接口转换为 system call 接口按 byte 的接口。你应当去掉这些 bounce buffers。取而代之,直接在 buffer cache 中的 sectors 之间拷入、拷出数据。
当数据被写入 cache 时,不必立即写回磁盘。你应把 dirty blocks 保留在 cache 中,在它们被淘汰时以及系统关闭时再写回磁盘(修改 filesys_done() 函数以完成此事)。
若只在淘汰或关闭时 flush dirty blocks,则在发生 crash 时你的 file system 会更加脆弱。作为可选功能,你也可以让 buffer cache 周期性地将 dirty cache blocks flush 到磁盘。若你已有 Project 2 中可用的、非 busy-waiting 的 timer_sleep(),这将是一个很好的用途。否则,你可以实现一个不那么通用的机制,但务必确保它不会出现 busy-waiting。
作为可选功能,你还可以实现 read-ahead,即在读取某个文件的一个 block 时,自动将该文件的下一个 block 取入 cache。Read-ahead 只有在异步完成时才真正有用。也就是说,若某个 process 请求文件的 disk block 1,它应阻塞直到 disk block 1 被读入;但一旦该读完成,控制应立即返回给该 process。对 disk block 2 的 read-ahead 请求应在后台异步处理。
NOTE 1:这是你 report 成绩中相当重要的一部分。如果你实现了 buffer cache 却在实现中没有真正使用它,将会失分。这包括在 stack 上或用 malloc 创建 buffers,再把数据从你的 cache 拷贝到该 buffer,或任何其他绕过 cache 的实现。
NOTE 2:特别地,若不从 project 代码中移除 global lock,也会导致失分。
来源:https://cs162.org/static/proj/proj-filesys/docs/tasks/extensible-files/
Pintos 目前无法扩展文件大小,因为 Pintos file system 将每个文件分配为一组连续的 blocks。你的任务是修改 Pintos file system 以支持扩展文件。你的设计应为文件提供快速的 random accesses,因此应避免基于 File Allocation Tables(FAT)的设计。一种可能是使用带有 direct、indirect 与 doubly-indirect pointers 的 indexed inode structure,类似于 Unix FFS。你需要支持的最大文件大小为 8 MiB(2^23 bytes)。你还必须为新的 system call inumber(int fd) 增加支持,该调用返回与特定 file descriptor 相关联的文件的唯一 inode number。务必妥善处理操作系统内存或磁盘空间耗尽的情况:使 file system 保持一致状态(尤其是在 inode extension 方面),且不泄漏磁盘空间或内存。
基础 file system 将文件分配为单个 extent,从而容易受到 external fragmentation 的影响:即使有 n 个 blocks 空闲,也可能无法分配一个 n-block 的文件。通过修改 on-disk inode structure 来消除这一问题。 实践中,这很可能意味着使用带有 direct、indirect 与 doubly indirect blocks 的 index structure。你也可以选择其他方案,只要在 design documentation 中说明理由,且只要它不像我们提供的基于 extent 的 file system 那样遭受 external fragmentation。
你可以假定 file system partition 不会大于 8 MiB。你必须支持大到与 partition(减去 metadata)相当的文件。每个 inode 存储在一个 disk sector 中,这限制了它所能包含的 block pointers 数量。要支持 8 MiB 的文件,你需要实现 doubly-indirect blocks。
基于 extent 的文件只有在其后有空闲空间时才能增长,而 indexed inodes 使得只要有 free space 可用就可以增长文件。实现 file growth。 在基础 file system 中,文件大小在创建文件时指定。在大多数现代 file systems 中,文件最初以 size 0 创建,之后每次写到文件末尾之外时再扩展。你的 file system 必须允许这一点。
文件大小不应有预先设定的上限,唯一的限制是文件不能超过 file system 的大小(减去 metadata)。这也适用于 root directory 文件,它现在应被允许扩展到超过最初 16 个文件的限制。
User programs 被允许 seek 到当前 end-of-file(EOF)之外。Seek 本身并不会扩展文件。在超过 EOF 的位置写入会将文件扩展到被写入的位置,并且先前 EOF 与 write() 起始位置之间的任何间隙都必须用 zeros 填充。从超过 EOF 的位置开始的 read() 不返回任何 bytes。
远超 EOF 的写入可能导致许多 blocks 完全为零。有些 file systems 会为这些隐式为零的 blocks 分配并写入真正的 data blocks。另一些 file systems 则根本不为这些 blocks 分配,直到它们被显式写入。后者被称为支持 “sparse files”。你可以在你的 file system 中采用任一分配策略。
你已经熟悉如何在 C 中处理内存耗尽:检查 malloc 的 NULL 返回值。在本 project 中,你还需要处理磁盘空间耗尽。当你的 file system 无法分配新的 disk blocks 时,你必须有策略中止当前操作并 rollback 到先前的良好状态。
来源:https://cs162.org/static/proj/proj-filesys/docs/tasks/subdirectories/
- Implementation details
- Syscall signatures
- chdir
- mkdir
- readdir
- isdir
- inumber
当前 Pintos file system 支持 directories,但 user programs 无法使用它们(即目前文件只能放在 root directory 中)。你必须添加以下 system calls,以允许 user programs 操作 directories:chdir、mkdir、readdir、isdir。你还必须更新以下 system calls,使其能与 directories 一起工作:open、close、exec、remove、inumber。对于任何带有 file path 参数的 syscall,你还必须增加对 relative paths 的支持。例如,若某个 process 调用 chdir("my_files/"),然后调用 open("notes.txt"),你应相对于当前 directory 搜索 notes.txt,并打开文件 my_files/notes.txt。你还需要支持绝对路径,例如 open("/my_files/notes.txt")。你需要支持特殊的 "." 与 ".." 名称,当它们出现在 file path 参数中时,例如 open("../logs/foo.txt")。Child processes 应继承 parent 的 current working directory。第一个 user process 应以 root directory 作为其 current working directory。
实现对 hierarchical directory trees 的支持。 在基础 file system 中,所有文件都位于单个 directory。修改这一点,使 directory entries 可以指向文件或其他 directories。确保 directories 可以像任何其他文件一样扩展到超出其原始大小。
基础 file system 对文件名有 14 个字符的限制。你可以保留这一限制用于各个文件名组成部分,也可以扩展它。你必须允许完整 path names 远长于 14 个字符。
为每个 process 维护独立的 current directory。 启动时,将 file system root 设为初始 process 的 current directory。当一个 process 通过 exec system call 启动另一个 process 时,child process 继承其 parent 的 current directory。此后,两个 processes 的 current directories 相互独立,因此任一方改变自己的 current directory 都不会影响另一方。(这就是为什么在 Unix 下,cd 命令是 shell built-in,而不是外部程序。)
更新现有的 system calls,使得凡是由调用者提供文件名的地方,都可以使用绝对或相对 path name。 Directory separator 字符是正斜杠(/)。你还必须支持特殊文件名 . 与 ..,其含义与 Unix 中相同。
更新 open system call,使其也能打开 directories。对于对应 directory 的 file descriptor,你不应支持 read 或 write。你将改为为 directories 实现 readdir 与 mkdir syscalls。你应当支持对 directory 的 close,它只是关闭该 directory。
更新 remove system call,使其除了能删除普通文件外,还能删除空 directories(root 除外)。仅当 directories 不包含任何文件或 subdirectories(除 . 与 .. 外)时,才允许删除它们。你可以自行决定是否允许删除正被某个 process 打开、或正被用作某个 process 的 current working directory 的 directory。若允许,则必须禁止在已删除的 directory 中打开文件(包括 . 与 ..)或创建新文件的尝试。
以下代码可帮助你将 file system path 拆分为其各个组成部分。它支持测试所要求的全部功能。是否使用、在何处使用以及如何使用,由你决定。
/* Extracts a file name part from *SRCP into PART, and updates *SRCP so that the
next call will return the next file name part. Returns 1 if successful, 0 at
end of string, -1 for a too-long file name part. */
static int get_next_part(char part[NAME_MAX + 1], const char** srcp) {
const char* src = *srcp;
char* dst = part;
/* Skip leading slashes. If it's all slashes, we're done. */
while (*src == '/')
src++;
if (*src == '\0')
return 0;
/* Copy up to NAME_MAX character from SRC to DST. Add null terminator. */
while (*src != '/' && *src != '\0') {
if (dst < part + NAME_MAX)
*dst++ = *src;
else
return -1;
src++;
}
*dst = '\0';
/* Advance source pointer. */
*srcp = src;
return 1;
}
实现以下新的 system calls:
bool chdir(const char* dir)
将 process 的 current working directory 更改为 dir,后者可以是相对路径或绝对路径。成功返回 true,失败返回 false。
bool mkdir(const char* dir)
创建名为 dir 的 directory,后者可以是相对路径或绝对路径。成功返回 true,失败返回 false。若 dir 已存在,或 dir 中除最后一级外的任一 directory name 尚不存在,则失败。也就是说,mkdir("/a/b/c") 仅当 /a/b 已存在且 /a/b/c 尚不存在时才会成功。
bool readdir(int fd, char* name)
从 file descriptor fd 读取一个 directory entry,该 fd 必须表示一个 directory。若成功,将 null-terminated 的文件名存入 name(name 必须有 READDIR_MAX_LEN + 1 bytes 的空间),并返回 true。若 directory 中没有剩余 entries,则返回 false。
. 与 .. 不应由 readdir 返回。
若 directory 在打开期间发生变化,则可以接受某些 entries 完全未被读取,或被多次读取。除此之外,每个 directory entry 应按任意顺序被读取一次。
READDIR_MAX_LEN 定义在 lib/user/syscall.h 中。若你的 file system 支持比基础 file system 更长的文件名,你应将此值从默认的 14 增大。
bool isdir(int fd)
若 fd 表示 directory,返回 true;若表示普通文件,返回 false。
int inumber(int fd)
返回与 fd 相关联的 inode 的 inode number,该 fd 可以表示普通文件或 directory。
Inode number 持久地标识一个文件或 directory。在文件存在期间它是唯一的。在 Pintos 中,inode 的 sector number 适于用作 inode number。
我们已提供 ls 与 mkdir user programs;一旦实现了上述 syscalls,它们就很直接。我们也提供了 pwd,它就不那么直接了。shell 程序在内部实现了 cd。
pintos extract 与 pintos append 命令现在应接受完整 path names,前提是路径中使用的 directories 已经创建。这不应要求你付出任何显著的额外努力。
来源:https://cs162.org/static/proj/proj-filesys/docs/tasks/synchronization/
你的 project 代码应始终是 thread-safe 的,但对于 Project 3,你不得在整个 file system 外使用单个 global lock。在 buffer cache 外使用 global lock 是可以的,但 不得在持有 global lock 时执行 blocking I/O! 关键在于:相互独立的操作(例如操作不同文件,或同一文件的不同部分)应能并发地发起 disk I/O 操作,而不必等待另一方完成。若 Thread A 与 Thread B 正在执行独立操作,且 Thread B 并未因 I/O 而阻塞,则 Thread A 阻塞等待 Thread B 是可以的。
注意:在 multicore 系统上,即便 Thread A 与 Thread B 都未因 I/O 而阻塞,也可能希望允许它们并发执行,以更好地利用多个 cores。例如,有人可能更希望不要在 buffer cache 外使用 global lock,以便多个 cores 能同时扫描 buffer cache。但由于 Pintos 不支持 multicore systems,我们不要求这一点。
什么叫两个操作是 “independent”?对本 project 而言,若操作作用于不同的 disk sectors,则视为独立,并应允许此类操作并发执行。 若两个操作正在写入同一 sector 或扩展同一文件,则不视为独立,你可以序列化这些操作以保持数据一致性。不要求并发读。
以下是一些例子。假定我们有如下 file descriptors。
int notes = open("/my_files/notes.txt");
int test = open("/my_files/test.c");
read(notes) 与 write(test) 应被允许并发运行,因为它们操作存储在不同 sectors 上的两个不同文件。read(notes) 与 write(notes) 不必被允许并发运行,因为它们操作同一 sector。注意 open 从文件开头开始,因此它们操作的是 sector 0。read(notes) 与 read(notes) 也不必被允许并发运行,因为它们从同一 sector 读取。这一要求适用于所有 tasks。若你在 Project User Programs 中添加了 global file system lock,请记得移除它!
来源:https://cs162.org/static/proj/proj-filesys/docs/tasks/concept-check/
以下是需在你的 design document 中回答的概念性问题。这些问题不需要写任何代码,但你可能需要阅读并引用一些内容。
- Buffer cache 有 2 个可选功能你可以实现:write-behind 与 read-ahead。带有 write-behind 的 buffer cache 会周期性地将 dirty blocks flush 到 file system block device,这样若发生断电,系统就不会丢失那么多数据。没有 write-behind 时,write-back cache 仅需在以下情况将数据写到磁盘:(1)数据是 dirty 的且被从 cache 中淘汰,或(2)系统关闭。带有 read-ahead 的 cache 会预测系统接下来需要哪个 block,并在后台将其取入。Read-ahead cache 可以大幅提升顺序文件读取以及其他易于预测的文件访问模式的性能。请分别讨论每种功能的一种可能实现策略。无论你是否实际决定实现这些功能,都必须回答此问题。
来源:https://cs162.org/static/proj/proj-filesys/docs/tasks/testing/
- Implementation details
- Adding file system tests to Pintos
Pintos 已包含针对 file system 功能的 test suite,但它并不覆盖 buffer cache。对本 project,你必须实现下列测试用例中的 两个:
- 通过测量 cache hit rate 来测试你的 buffer cache 的有效性。首先,重置 buffer cache。接着,打开一个文件并顺序读取,以确定 cold cache 的 cache hit rate。然后关闭它,重新打开,并再次顺序读取,以确保 cache hit rate 有所提升。
- 测试你的 buffer cache 将写合并到同一 sector 的能力。每个 block device 都维护一个 read_cnt counter 与一个 write_cnt counter。按 byte-by-byte 写入一个至少 64 KiB 的大文件(即最大允许 buffer cache 大小的两倍)。然后按 byte-by-byte 读回。设备写的总数应大约为 128 量级,因为 64 KiB 是 128 个 blocks。
- 测试你的 buffer cache 在不先读取的情况下将完整 blocks 写到磁盘的能力。例如,若你向文件写入 100 KiB(200 blocks),你的 buffer cache 应执行 200 次对 block_write 的调用,但对 block_read 的调用应为 0 次,因为恰好写入了 200 blocks 的数据。对 inode metadata 的读操作仍然可以接受。如前所述,每个 block device 都维护一个 read_cnt counter 与一个 write_cnt counter。你可以用它来验证你的 buffer cache 没有引入不必要的 block reads。若你的 buffer cache 不具备这一性质,则实现上面列出的另外两个选项。
你应专注于为通用的 buffer cache 特性编写测试,而不是为你特定的 buffer cache 实现编写测试。你应以对底层 buffer cache 实现的最少假设来编写测试用例,但你也被允许按需做出尽可能多的关于 buffer cache 的基本假设,因为若不这样做就很难编写 buffer cache 测试。请运用你的最佳判断,编写可能在不重写全部内容的情况下适配到其他小组 project 的测试用例。写完测试用例后,确保在 filesys/ 目录中运行 make check 时它们会被执行。
你应将两个测试用例添加到 filesys/extended test suite,从 filesys 目录运行 make check 时会包含该 suite。所有 filesys 与 userprog 测试都是 “user program” 测试,这意味着它们只能通过 system calls 与 kernel 交互。由于 buffer cache 信息与 block device 统计目前并未暴露给 user programs,你必须创建新的 system calls 来支持你的两个新 buffer cache 测试。 你可以通过修改以下文件(及其相关 header files)来创建新的 system calls:
lib/syscall-nr.h
定义 syscall numbers 与 symbolic constants。该文件同时被 user programs 与 kernel 使用。
lib/user/syscall.c
User programs 的 syscall 函数。
userprog/syscall.c
Syscall handler 实现。
编写测试用例时需牢记的一些事项:
- User programs 只能访问 C standard library 的一个有限子集。你可以在 lib/ 中找到 user library。
- User programs 不能直接访问 kernel 中的变量。
- User programs 无法使用 malloc,因为 brk 与 sbrk 未实现。User programs 的 stack 大小也有限。若你需要大型 buffer,请将其设为 static global variable。
- Pintos 启动时有 4MB 内存,file system block device 默认大小为 2MB。不要使用超过这些大小的数据结构或文件。
- 你的测试应使用 msg() 而不是 printf()(它们有相同的函数签名)。
你可以通过修改以下文件(均位于 tests/filesys/extended 内)向 filesys/extended suite 添加新的测试用例:
Make.tests
filesys/extended test suite 的入口点。你需要将测试名称添加到 raw_tests 变量中,以便 test suite 能找到它。
my-test-1.c
这是你的测试代码(你可以使用任意名称,“my-test-1” 只是一个例子)。你的测试应定义一个名为 test_main 的函数,其中包含一个 user-level program。这是测试用例的主体,应进行 syscalls 并打印输出。使用 msg() 函数而不是 printf。
my-test-1.ck
每个测试都需要一个 .ck 文件,它是一个用于检查测试程序输出的 Perl 脚本。若你不熟悉 Perl,不必担心!通过一些合理猜测,你大概也能完成这一部分。你的 check 脚本应使用定义在 tests/tests.pm 中的 subroutines。最后调用 pass 以打印 “PASS” 消息,这会告知 Pintos test driver 你的测试已通过。
my-test-1-persistence.ck
Pintos 期望每个 filesys/extended 测试用例都有第二个 .ck 文件。每个测试用例运行后,kernel 会使用同一 file system disk image 重新启动,然后 Pintos 将整个 file system 保存为 tarball 并导出到 host machine。*-persistence.ck 脚本检查 file system 的 tarball 是否包含正确的结构与内容。若你的测试用例不需要,你不必在此文件中做任何检查。 不过,你仍应在此文件中调用 pass,以满足 Pintos testing framework。
来源:https://cs162.org/static/proj/proj-filesys/docs/deliverables/
来源:https://cs162.org/static/proj/proj-filesys/docs/deliverables/design/
-
Document
-
Data Structures and Functions
-
Algorithms
-
Synchronization
-
Rationale
-
Review
-
Grading
在开始为 project 编写任何代码之前,你需要为每个功能创建 design plan,并说服自己你的设计是正确的。你必须 提交一份 design document,并 与你的 TA 参加 design review。这将帮助你巩固对本 project 的理解,并在着手处理大型 codebase 之前拥有可靠的进攻计划。
免责声明:你的 design document 长度不得超过 15 页。请勿在 design document 中包含大段代码。超过此限制将导致成绩扣分。
与任何技术写作一样,你的 design document 需要干净、格式良好。我们在网站上提供了你必须使用的模板链接。模板可以在网站上找到。我们使用 Dropbox Paper,它支持类似 Google Docs 的实时协作,并额外提供技术写作支持(例如 code blocks、LaTeX)。不使用该模板或未使用代码格式将导致失分。 这样做的主要目的不是为了惩罚你,而是为了让 TA 易于阅读。
注意:请确保你使用新的 design doc 模板,因为它已从你过去几个 projects 使用的先前模板修改而来;如上所述,不使用此新模板将导致扣分。
开始时,打开模板,点击右上角的 “Create doc” 按钮。你可以将该 doc 分享给其他组员以协作。请确保点击的是你自己文档中的蓝色 “Share” 按钮,而不是模板上的。
对于你在每个 task 中添加或修改的每个函数(Concept Check 除外),你必须解释所提出设计的以下方面。我们建议你为每个函数创建一个 subsection,其中包含以下各方面。更多指导请查看模板中提供的示例。
列出你将添加或修改(若已存在)的任何 struct 定义、global 或 static 变量、typedefs 或 enumerations。这些应以 C 而非 pseudocode 书写。为每次修改包含简要说明(即一行注释)。更深入的解释应留给后续各节。
告诉我们你计划如何编写必要的代码。请用文字解释你设计的主要部分;若只有代码而没有关于你将如何实现设计的解释,本节将不得分。 你的描述应低于作业中给出的需求高层描述。不要重复 spec 上已有的内容。
另一方面,你的描述应高于代码本身。不要逐行说明你计划写什么代码。你可以在认为合适的地方使用小段 pseudocode 或 C。相反,你需要说服我们你的设计满足所有需求,尤其是任何 edge cases。我们期望你在准备 design document 时已通读 Pintos source code,并且在必要时你的 design document 应引用 Pintos source code 的相应部分以澄清你的实现。
列出所有跨 threads 与 processes 共享、且该函数会编辑的资源。对每个资源,解释它如何被访问(例如从 interrupt context),并描述你确保其被安全共享与修改的策略(即无 race conditions、deadlocks)。
类似地,若你认为该函数内没有任何内容需要 synchronization,也请说明你做出该决定的理由。若未给出推理说明,本节将不得分。
总的来说,最好的 synchronization 策略是简单且易于验证的。若你的 synchronization 策略难以解释,这通常表明你应简化策略。讨论你的 synchronization 方法的时间与内存开销,以及你的策略是否会显著限制 kernel 和/或 user processes/threads 的 concurrency。在讨论你的方法所允许的 concurrency 时,解释 threads 争用共享资源的频率,以及对可同时进入独立 critical sections 的 threads 数量的任何限制。你应力求避免过于粗粒度的 locking 策略。
Interrupt handlers 不能获取 locks。若你需要从 interrupt handler 访问被同步的变量,考虑禁用 interrupts。Locks 并不能阻止 thread 被 preempt。Threads 可能在 critical section 期间被中断。Locks 只保证 critical section 一次只被一个 thread 进入。
不要忘记将 memory deallocation 视为 synchronization 问题。若你想使用指向 struct thread 的 pointers,则需要证明在你使用这些 threads 时它们不会 exit 并被 deallocate。
若你创建了新函数,应考虑该函数是否可能被 2 个 threads 同时调用。若你的函数访问任何 global 或 static 变量,你需要表明不存在 synchronization 问题。
告诉我们为什么该函数的设计优于你考虑过的替代方案,或指出它可能有的任何缺点。你应思考你的设计是否易于理解、需要多少编码、算法的时间/空间复杂度,以及扩展你的设计以容纳额外功能会有多容易/困难。
提交 design doc 后,你将与你的 TA 安排 design review。日历报名链接将在 design doc 截止日期前的某个时间发布。在 design review 期间,你的 TA 会就你对本 project 的设计提问。你应准备好为自己的设计辩护,并回答 TA 可能就你的 design document 提出的任何澄清性问题。Design review 也是一个认识你的 TA、争取 participation points 的好机会。
Design document 与 design review 将一并评分。你的分数将反映你的设计有多令人信服,依据是你在 design document 中的解释以及在 design review 中的回答。若你无法参加 design review,请联系你的 TA 另行安排。无故缺席 design review 将导致 design 部分得 0 分。
来源:https://cs162.org/static/proj/proj-filesys/docs/deliverables/code/
- Checkpoints
- Testing
- Quality
代码将通过你的 groupX repo 提交到 GitHub。Pintos 附带一个你可以在 VM 上本地运行的 test suite。我们将在 autograder 上运行相同的测试,这意味着没有 hidden tests。因此,我们建议你尽可能在本地测试,因为 autograder 的带宽有限。
我们设置了 checkpoints 以指导你对本 project 的实现。Checkpoints 不会被评分,对你的成绩没有影响。不过,我们仍鼓励学生跟上 checkpoints。
你的 testing 代码也需要包含在你的 repo 中,放在相应文件夹下。
你的代码分数将主要由 autograder 分数决定。不过,你也将在代码质量的若干因素上被评分,包括但不限于:
- 你的代码是否存在任何重大的 memory safety 问题(尤其是与 strings 相关)、memory leaks、糟糕的错误处理,或 race conditions?
- 你的代码是否简单易懂?是否遵循一致的 naming convention?
- 你是否为复杂代码部分添加了足够的注释?
- 你是否在最终提交中留下了被注释掉的代码?
- 你是否复制粘贴代码,而不是创建可复用的函数?
- 你是否重新实现了 linked list 算法,而不是使用提供的 list manipulation 函数?
- 你的 Git commit history 是否充满了 binary files?
注意,只要你在 Project User Programs 中正确设置了 precommit hook,就不必担心手动强制执行代码格式(例如缩进、间距、长行换行、大括号的一致放置)。你可能也会发现偶尔运行 make format 来格式化代码很有帮助。
来源:https://cs162.org/static/proj/proj-filesys/docs/deliverables/report/
- Changes
- Reflection
- Testing
在完成 project 的代码后,你的小组将撰写一份反映本 project 的 report。虽然我们不期望你写一份冗长的 report,但我们要求的细节程度与 design document 相同。这里是 report 的模板。你的 report 应包含以下各节。
讨论自初始 design document 以来你所做的任何更改。解释你为何做出那些更改。如有必要,可以重述你在 design review 中与 TA 讨论过的内容。
讨论每位成员的贡献。务必具体说明每位成员在每个 task 的哪些部分上工作。反思整体工作环境,讨论哪些进展顺利、哪些方面有待改进。
对于你编写的 2 个测试用例中的每一个,请提供:
- 你的测试用例旨在测试的功能描述。
- 测试用例机制如何工作的概述,以及对预期输出的定性描述。
- 运行该测试用例时你自己的 Pintos kernel 的输出与结果。这些文件的扩展名为 .output 与 .result。
- 两个非平凡的潜在 kernel bugs,以及它们会如何影响该测试用例的输出。以 “If my kernel did X instead of Y, then the test case would output Z instead.” 的形式表达。你应为每个测试用例指出两个不同的 bugs,但可以在两个测试用例中使用相同的 bug。这些 bugs 应与你的测试用例相关(例如语法错误不算)。
此外,告诉我们你为 Pintos 编写测试的经历。Pintos testing system 有哪些可以改进的地方?你从编写测试用例中学到了什么?
来源:https://cs162.org/static/proj/proj-filesys/docs/deliverables/evaluations/
在完成上述所有组成部分后,你必须提交对小组成员的 evaluation。
你还将填写每位成员各自负责了什么的细节。虽然这与 report 中的 Report 部分类似,但由于 report 是协作文档,此处将作为如实陈述贡献的空间。
要提交 evaluations,请填写此 Google Form。理想情况下,为每位小组成员写 50 到 100 词。
该 evaluation 对你组内其余成员保持匿名。若我们注意到某些极端的分数权重,我们将联系并安排会议讨论任何小组问题。这些 evaluations 很重要且权重可观,因此请诚实、详尽地填写。关于 evaluations 如何被使用的更多信息,见 Grading。
来源:https://cs162.org/static/proj/proj-filesys/docs/deliverables/submission/
Design documents 与 final reports 应提交到 Gradescope 上各自对应的位置。你可以从 Dropbox Paper 导出文档:点击右上角的三个点,然后点击 “Export”。
确保你已 push 代码并有一个 autograder build。该 build 必须包含 testing 代码,你才能获得 testing 的分数。
来源:https://cs162.org/static/proj/proj-filesys/docs/deliverables/grading/
上述各组成部分的权重如下:15% Design,70% Code,15% Report。虽然 evaluations 并非明确计入成绩,但你的 project 分数会受其影响。为保持 evaluations 公平与匿名,我们不会公布 evaluations 如何被计入,也不会公布计入 evaluations 后计算得出的最终分数。
来源:https://cs162.org/static/proj/proj-filesys/docs/Plan/
- Checkpoint 1
- Checkpoint 2
- Final
我们根据我们以及往届学生的经验,为你提供了建议的实现顺序以及各 checkpoint 的规格。不过,这仅仅是建议,你也可以选择完全不同的方式来完成 project。
请记住,checkpoints 不会被评分。
从实现 buffer cache 开始;一个不错的 sanity check 是确保在实现 buffer cache 之后,所有 userprog 测试仍然通过。你应从使用 bounce buffers 开始实现,并在确认其行为符合预期后,再通过移除它们来完成实现,并确保移除后仍按预期工作。
若你决定先做 buffer cache,请确保在将其与 extensible files 和 subdirectories 的实现合并之前,先分别开发。这将有助于在合并前确保两部分都能工作,从而尽量减少可能出现的 debugging 问题。你也可以决定在测试完 extensible files 与 subdirectories 的实现之后,再在最后做 buffer cache。
实现对 extensible files 的支持。这将涉及对 inode structure 做适当修改,使其支持更大的文件大小,并允许文件增大。
你可以从 buffer cache 并行开始实现这两部分。因此,只要你在两者都完全实现并通过相应测试之前,将这两部分与 buffer cache 保持分离,你就可以在没有完全实现的 buffer cache 的情况下,同时开展 Extensible Files 与 Subdirectories 部分。 所以,尽管将 buffer cache 代码分离开来(例如放在另一个 branch,或交给队友的电脑等),然后继续做 extensible files 与 subdirectories 部分。
通过实现 subdirectory 支持来完成本 project。先实现你的 path resolution 函数,然后再实现本任务中的其余函数。
来源:https://cs162.org/static/proj/proj-filesys/docs/faq/
- How much code will I need to write?
- Can BLOCK_SECTOR_SIZE change?
- What is the largest file size that we are supposed to support?
- How should a file name like a\b be interpreted?
- How about a file name like /../x ?
- How should a file name that ends in / be treated?
- Can we keep a struct inode_disk inside struct inode ?
以下是我们 reference solution 的摘要。注意,该 diff 是相对于 Project User Programs 的 staff solution 生成的。Reference solution 只代表一种可能的解决方案。许多其他解决方案也是可能的,其中许多与 reference solution 差异很大。一些优秀的解决方案可能不会修改 reference solution 所修改的全部文件,也可能修改 reference solution 未修改的文件。
filesys/directory.c | 43 ++-
filesys/directory.h | 2
filesys/filesys.c | 182 +++++++++++-
filesys/filesys.h | 5
filesys/free-map.c | 32 +-
filesys/fsutil.c | 2
filesys/inode.c | 435 +++++++++++++++++++++++++------
filesys/inode.h | 6
threads/thread.c | 1
userprog/process.c | 4
userprog/process.h | 10
userprog/syscall.c | 110 +++++++
userprog/syscall.h | 5
13 files changed, 717 insertions(+), 120 deletions(-)
不能。BLOCK_SECTOR_SIZE 固定为 512。对于 integrated drive electronic(IDE)磁盘,该值是硬件的固定属性。其他磁盘不一定有 512-byte 的 sector,但为简单起见,Pintos 只支持那些有的磁盘。
我们创建的 file system partition 将为 8 MiB 或更小。不过,单个文件必须小于 partition,以容纳 metadata。在决定你的 inode organization 时需要考虑这一点。
多个连续的 slashes 等价于单个 slash,因此该文件名与 a/b 相同。
Root directory 是其自身的 parent,因此它等价于 /x/。
大多数 Unix 系统允许 directory 名称末尾有 slash,并拒绝以 slashes 结尾的其他名称。我们将允许这种行为,但你也可以选择直接拒绝以 slash 结尾的名称。
64-block 限制的目标是界定所缓存的 file system 数据量。若你在 kernel memory 的任何地方保留一块磁盘数据(无论是 file data 还是 metadata),则必须将其计入 64-block 限制。同样的规则适用于任何类似于一块磁盘数据的东西,例如没有 length 成员的 struct inode_disk。
这意味着你必须改变 inode 实现当前访问其对应 on-disk inode 的方式,因为它目前只是在 struct inode 中嵌入一个 struct inode_disk,并在创建时从磁盘读取对应 sector。保留 inode 的额外副本会破坏我们对你的 cache 施加的 64-block 限制。
你可以在 struct inode 中存储指向 inode data 的 pointer,但若这样做,应仔细确保这不会将你的操作系统限制为同时只能打开 64 个文件。你也可以存储其他信息,以帮助你在需要时找到 inode。类似地,你可以为 64 个 cache entries 中的每一个存储一些 metadata。
如果你愿意,可以将 free map 的一份缓存副本永久保留在内存中。它不必计入 cache 大小。
filesys/inode.c 中的 byte_to_sector 直接使用 struct inode_disk,而没有先从存储层次结构中读取该 sector。这不再可行。你需要修改 inode_byte_to_sector,使其在使用前先从 cache 获取 struct inode_disk。