MIT 6.S081 Lecture 1 Notes

One benefit of summer was that each day we had more light to read by.

开始学习MIT的操作系统6.S081课程与相关Lab。课程教授操作系统的基本概念并用xv6做一个case study用于分析与lab作业。xv6系统基于Unix version 6的接口,并模仿了Unix内部设计。关于一些基本的OS知识笔记就不记录了,简单写一下学习过程中比较新的启发与感悟。第一节课是讲义的第一章与笔记

Process and memory

一个 xv6 进程由用户空间与内核私有的每个进程状态组成。作为一个时分系统,xv6透明地在进程间切换CPU,进程切换会保存CPU寄存器,在切回时重置。系统调用 fork 创建新进程,父进程返回子进程PID,子进程返回0以示区分。系统调用 exec 用文件系统存储的一个程序文件替换原有进程。xv6用的文件格式是 ELF 格式。

Why not combine fork() and exec()?

fork复制文件描述符,而exec会保留之前的文件描述符。这样设计便于文件I/O的重定向,在 shell 程序实现的 fork + exec [+ wait] 的组合中,在 fork 与 exec 之间操作文件描述符(通过 close open 或者 dup)可以很方便的实现重定向。这也是为什么设计系统调用时不将 fork 与 exec 合并设计为一个系统调用的原因。Unix设计的思想在于内聚每种操作,但组合出通用的功能。

I/O and File descriptors

有一句名言如此说道:“在Unix里万物皆文件。”这句话实际描述的是Unix通过文件描述符将文件、目录、设备、管道、套接字等抽象化。文件描述符隐藏了底层的I/O的具体对象。内核为每个进程维护了一个文件描述符表,这当然在进程间也是隔离的。不论是 open socket pipe dup 系统调用,当每个进程需要分配新的描述符时,都会从表中取最小的未分配描述符数字。

除了描述符,一个文件当然需要记录当前读写的偏移 offset 等信息,这也由内核维护,但是为一个全局表。在 fork 与 dup 操作时,复制的描述符也共享原描述符的偏移等信息。但重复 open 同一个文件时, 却是文件描述符与偏移却不共享,相同的是 inode。p.s. 在CSAPP的IO章节中有很清晰的图说明这不同的共享情况。

有些 shell 不支持错误流重定向,如 xv6 的 shell 实现,但常常会实现如下的 dup 调用,

ls existing-file non-existing-file > tmp1 2>&1

命令最后的 2>&1 是指将描述符2写为描述符1的副本,在这个例子里就是将标准错误流重定向为标准输出流,而标注输出流之前已重定向到文件 tmp1,所以文件 tmp1 中不仅会有命令 ls 的正常结果,可能的错误输出也会在其中。

Pipes

管道是一块内核缓冲区,提供给进程一组读写文件描述符。通过系统调用read可从 in 描述符获取 out 描述符对应的数据,而系统调用write向out 描述符写入数据。这里用 in 和 out 分别代表 pipe 调用写的描述符数组第0个和第1个元素,这正好符合默认状态时 stdin 为 0 而 stdout 为 1 的规范。

如果管道暂时无数据,系统调用 read 会阻塞到有数据或者管道写端的所有描述符都已关闭。对于后者情况,这个 read 调用会返回 0 指示文件结束。这也是为什么在涉及多进程时,关闭写端描述符是十分重要的,否则读端一直阻塞下去。p.s. 一个常见的bug就是创建管道后 fork 一个子进程,某个读者忘记提前关闭写端描述符,或者写者写完数据但没有关闭描述符,导致读者一直阻塞。

xv6 shell 也是用符号 | 代表管道操作,例如

grep fork sh.c | wc -l 

其管道操作的实现是通过将管道左右两侧命令置为一个节点的左右儿子,为两儿子均调用 fork,做成一个二叉树结构。书中也讨论了其他实现如左右链式的复杂度与一些常见问题。

当然,创建临时文件也可以代替管道,如

echo hello world | wc
echo hello world >/tmp/xyz; wc </tmp/xyz

但是管道有四个优点,

  1. 管道可以被内核自动清理,临时文件却需要小心移除。
  2. 管道理论上可以传输任意长的数据流,写的数据被读出,缓冲区又会有新空间,但是临时文件却需要足够的磁盘空间。
  3. 管道允许如流水线一样的并行执行,临时文件相关的操作一定是串行的。
  4. 如果实现IPC(inter-process communication)管道的阻塞式读写比文件的非阻塞语义效率更高。

File System

系统调用mknod会创建一种索引到设备的特殊文件。关联设备的是最大与最小 device number,也是mknod的两个参数。通过这两个 device numbers 识别一个内核设备。当有进程打开这个设备文件时,内核会将对这个文件的 read write 系统调用转换到内核的相应设备实现中。

一个文件名可以识别不同的文件。但是同一个文件,潜在是 inode ,可以有多个名字,即为 links 。每一个 link 由一个目录下的一个条目组成,条目包含文件名与对应 inode 的索引。每个 inode 维护文件的元数据,包括类型(文件、目录或设备),长度,文件内容在磁盘上的位置,其他索引到这个 inode 的 link 数量。

系统调用 fstat 通过文件描述符获取其索引的 inode 信息。xv6的 fstat 将 inode 信息填入如下结构体中,

#define T_DIR 1 // Directory
#define T_FILE 2 // File
#define T_DEVICE 3 // Device

struct stat {
    int dev; // File system’s disk device
    uint ino; // Inode number
    short type; // Type of file
    short nlink; // Number of links to file
    uint64 size; // Size of file in bytes
};

当然现实的OS的文件信息更为复杂,在自己系统上 man fstat 一下就可以看到对应stat结构体的字段。

系统调用 link 创建一个索引到现存的文件 inode 的 link,如下代码创建一个文件名为 a 又为 b,

open("a", O_CREATE|O_WRONLY);
link("a", "b");

对文件a的读写等价于对b的读写。每一个 inode 通过一个唯一的 inode number 被内核识别。在如上代码运行后,通过系统调用 fstat 能观察到二者的 ino 字段相同,且 nlink 字段为2。p.s. 这种link是hard link,至于 soft link 更像是一种路径,也可以解释为 symbolic link。

系统调用 unlink 将去掉一个链接名字。只有当一个文件的 link count 为0时(nlink减为0)且没有描述符索引到该文件时,该文件的 inode number (代表其他metadata)与磁盘空间才会被回收。一个常见的创建临时的无名 inode 的方式如下,

fd = open("/tmp/xyz", O_CREATE|O_RDWR);
unlink("/tmp/xyz");

当进程关闭描述符fd或退出,这个临时 inode 将被回收。Unix 在 shell 层提供了用户级的文件工具,如 mkdir ln rm等。这样设计允许任何人通过添加新的用户级程序以扩展命令行接口。顺带一提,命令 cd 是内建于shell中的,因为如果通过fork子进程再切换当前目录,父进程shell的工作目录不会改变。

Real world

Unix结合文件描述符、管道与各种操作它们的shell语法,提高了写通用程序的可能。其设计也激发了现实中的 softwar tools 文化,带来了 Unix 的繁荣。同时 shell 也是第一个所谓的 script language。Unix的系统调用在今天仍然活跃在 BSD, Linux 和 maxOS中。

通过 POSIX(Portable Operating System Interface) 标准,规范了Unix的系统调用。但 xv6 不是 POSIX 兼容的,它缺乏如 lseek 等系统调用,并且许多系统调用与标准不同。当然课程的目的只是提供一个简单的Unix-like 系统调用接口。现代内核提供了更多系统调用与更多的内核服务,如网络、窗口系统、用户级线程、各种设备驱动等。现代内核持续迅速的发展,提供了远超POSIX的特性。

Unix将不同资源的集成为文件名与文件描述符接口。这代表了资源即文件的思想,可以拓展到更多种类的资源,包括网络、图像资源等。但大部分Unix派生的OS并没遵循这一思想。 文件系统与描述符的确是一种强大的抽象,但也有操作系统使用其他抽象模型。Multics是Unix的前身,将文件存储抽象为类似内存的形式。这种复杂的设计接口,也使得Unix致力于降低其复杂度。

课程的xv6系统也不提供用户的抽象。在Unix视角,所有xv6进程作为root运行。 课程虽然主要讲xv6实现Unix-like接口,但是涉及的思想与概念不只是Unix。领略概念后可以此为骨架,学习其他操作系统。

Reference

  1. MIT 6.S081 2021
  2. xv6 book riscv
  3. lecture overview