|

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)进行沟通。

mit6.s081 Fall 2022系列笔记-第一课
os organization

操作系统抽象

操作系统抽象了许多东西,令其易于理解和操作。例如在 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功能 中开启。

mit6.s081 Fall 2022系列笔记-第一课
mit6.s081 Fall 2022系列笔记-第一课 6

输入以下命令来安装所需要的工具,或许你需要换源,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
mit6.s081 Fall 2022系列笔记-第一课
mit6.s081 Fall 2022系列笔记-第一课 7

可以使用 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 即可找到这个程序。

mit6.s081 Fall 2022系列笔记-第一课
mit6.s081 Fall 2022系列笔记-第一课 8

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 文件中的内容。

mit6.s081 Fall 2022系列笔记-第一课
mit6.s081 Fall 2022系列笔记-第一课 9

其余系统调用(system call)例子请查看课程网站或者视频。

类似文章

3条评论

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注