mit6.s081 Fall 2022系列笔记-第一课
关于操作系统中最著名的一门课就是麻省理工的 MIT-6.S081 了。这门课包含了讲解和实验(lab)两个部分,为我们展示了一个真正的通用操作系统应该是什么样子的,用户态应用程序如何在操作系统管理的硬件中运行,操作系统如何同时运行多个应用程序,如何最大限度的利用一台机器的资源,令我们能够清楚的知道一个操作系统内部的样子和工作原理。本系列文章是这门课的笔记记录。
这门课一共有 10 个 Lab,我们将随着实验来完成 xv6 操作系统的一些模块和功能。你也可以打开这门课的 schedule,这里包含了所有上课所需要的阅读材料、例子和视频资料。如果你无法访问 Youtube,那么 Bilibili 有搬运的视频。
目录
mit6.s081的目的
- 理解一个操作系统是如何设计和实现的
- 上手体验编写系统软件
- 对一个简单的操作系统进行拓展(vx6)
- 学习关于硬件是如何工作的
Lab实验的几个目标
- 编写一个网络栈驱动,可以在真实的互联网中发送数据包
- 重新设计一个内存分配器,以便它可以跨多个内核扩展
- 实现 fork 函数,并通过写时读(copy-on-write)令其高效。
操作系统有哪些共同的特性
许许多多的操作系统的实现方式各有不同,但是它们都具备一些共同的,我们软件所需要的一些特性。
它应该是抽象的
- 操作系统应该对于上层应用隐藏硬件的复杂细节,使其变得简单易用。
- 必须要保证高性能。
- 必须支持许多不同的应用。
它应该是复用的
- 操作系统必须能够允许多个应用共享一个硬件(例如内存、CPU和硬盘)。
- 应用之间彼此分离,以保证安全性,避免一个应用由于 Bug 崩溃而导致整个机器随之崩溃。
- 在保证应用之间相对分离的同时,也要允许它们进行有限的沟通。例如使用 vim 程序写的一段 C 程序,还要能够使用编译器进行编译,它们通过文件系统进行沟通。
所以,一个良好的操作系统的设计非常复杂,但是系统编程人员使用起来却要非常的容易。一个操作系统的组织应该像下图那样,用户空间和内核空间彼此相对分离,通过系统调用(system call)进行沟通。
操作系统抽象
操作系统抽象了许多东西,令其易于理解和操作。例如在 Unix 中,所有的文件、硬件,包括网络接口都被抽象成了文件。读写文件的操作即是对于硬件(例如硬盘、网卡)进行数据交换和操作。
在 C 语言中,我们使用 open
函数打开一个文件,并返回一个文件描述符(这是一个特定的数据结构)。对于这个文件描述符(file descriptor)的读和写,也就相当于对文件的读和写。这也是一种抽象。
系统调用
在一个操作系统中,用户态的应用程序使用系统调用(system call)
来打开文件,读写文件,也就是调用 open
函数。那么为什么要有系统调用呢?为什么应用程序不直接在磁盘中读写文件。
一个很显然的问题就是——安全。
如果允许任意用户态应用程序操作重要的硬件,例如 CPU、内存、硬盘等,那么一旦程序出错,这些硬件也会随之出错。如果此时操作系统中还有其他用户的进程,这时候就会发生不可挽回的灾难。
又例如,如果用户应用程序可以随意操作内存,那么一些高权限用户的重要数据是否会泄露,其他用户的程序可以从内存中拷贝数据,这显然是不安全的。
所以,处于种种原因,我们令用户程序运行在用户态中,内核(高优先级应用)运行在内核态。内核态的程序处于 0 这个优先级,而用户态的程序运行在 3 这个优先级。X86 处理器中,程序共分为四个优先级,分别是 0,1,2,3. 其中 0 优先级最高,3 优先级最低。Unix 只使用了 0 和 3 这两个优先级。
如果用户态程序需要使用一些高优先级的功能,那么可以通过系统调用(system call)
来陷入内核中,让内核帮其完成,之后再返回结果到用户态程序中去,这样就保证了操作系统的安全。
实验准备
接下来将会展示几个使用系统调用(system call)
的例子,这些程序运行在教学用的小型操作系统(xv6)
中。所以我们首先要搭建一个实验环境。访问其课程页面即可看见如何安装一些工具。这里做一个简单的叙述。
Windows
首先安装 WSL ,你可以在 Widnows 自带的应用商店中下载安装。这里安装 Ubuntu 20.04.
注意,确保计算机已经打开 虚拟机平台 和 适用于 Linux 的 Windows 子系统 这两个功能。在 控制面板->程序->启用或关闭Windows功能 中开启。
输入以下命令来安装所需要的工具,或许你需要换源,USTC Mirror For Ubuntu。
sudo apt-get update && sudo apt-get upgrade sudo apt-get install git build-essential gdb-multiarch qemu-system-misc gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu
克隆 xv6 系统源代码。
git clone git://github.com/mit-pdos/xv6-riscv.git git clone https://ghproxy.com/https://github.com/mit-pdos/xv6-riscv.git # 国内请使用代理链接
然后进入 xv6-riscv 目录中,使用 make qemu
进行编译和启动 qemu 虚拟机。使用 Ctrl + A X 来退出虚拟机。
cd xv6-riscv make qemu
可以使用 ls cat grep 等常用命令。
几个系统调用的例子
这里,我将会写几个使用系统调用的具体程序,以便理解和掌握。它们分别是:
- copy
- open
- fork
- forkexec
- redirect
可以在课程网站查看具体源代码。LEC1 examples
copy
此程序会将从标准输入(shell)读取的字符写入到标准输出(屏幕)中去。
// copy.c: copy input to output. #include "kernel/types.h" #include "user/user.h" int main() { char buf[64]; while(1){ int n = read(0, buf, sizeof(buf)); if(n <= 0) break; write(1, buf, n); } exit(0); }
read 函数的第一个参数是 source(读取源,就是从哪里读取),第二个参数是缓冲区,这里是一个 64 字节的缓冲区,第三个参数是缓冲区的长度。read 将返回实际读取的字节数量。
这里需要注意的是,read 函数的第一个参数 0,指的是从标准输入中读取,0 默认指向标准输入,而 1 默认指向标准输出。这里也可以是一个文件描述符 fd,也就是 FILE* 指针。
在 xv6 中运行
首先需要在 xv6-riscv/user 目录中写入程序的源代码,之后在根目录中的 Makefile 文件中添加进去,在文件的第 118 行,重新 make qemu 即可找到这个程序。
open
open 程序将会打开一个文件,并返回一个数据结构(FILE指针)来代表这个文件。这就是一种抽象。
// open.c: create a file, write to it. #include "kernel/types.h" #include "user/user.h" #include "kernel/fcntl.h" int main() { int fd = open("output.txt", O_WRONLY | O_CREATE); write(fd, "ooo\n", 4); exit(0); }
此时的 open 函数中 O_WRONLY 代表只写,O_CREATE 代表如果不存在则创建文件。write 函数将会在文件中写入四个字符。运行之后,使用 cat 命令查看 output.txt 文件中的内容。
其余系统调用(system call)
例子请查看课程网站或者视频。
3条评论