Loading...

MIT6.S081这门课程的标题是Operating System Engineering,主要讲的就是操作系统。授课教授是Robert Morris和Frans Kaashoek,两位都是非常出名的程序员。

课程是基于一个类似于Unix但是简单的多的教学操作系统XV6来讲解,虽然不是原汁原味的Linux,但是对于理解Linux的工作方式和结构是足够了。

课程网站🔗:https://pdos.csail.mit.edu/6.828/2021/schedule.html

课程所用教材🔗:https://pdos.csail.mit.edu/6.828/2021/xv6/book-riscv-rev2.pdf

课程翻译文档🔗:https://mit-public-courses-cn-translatio.gitbook.io/mit6-s081/

xv6中文文档🔗:https://th0ar.gitbooks.io/xv6-chinese/content/index.html

这门课用到的所有资源和作业实现汇总🔗:https://github.com/PKUFlyingPig/MIT6.S081-2020fall

1.1 课程内容简介

课程目标:

  • 理解操作系统的设计和实现。设计是指整体的结构,实现是指具体的代码是什么样
  • 通过xv6的操作系统进行实际动手操作,并结合课程配套的实验,你可以获得扩展操作系统,修改并提升操作系统的相关经验,并且能够通过操作系统借口,编写系统软件。

操作系统的目的:

  • 抽象硬件:通常来说,你会买一个计算机,里面包含了CPU,memory,但这是非常低层级的资源。幸好我们有一些应用程序实现了高层级的接口和抽象,例如进程和文件系统。这些高层级的接口和抽象方便了应用开发也提供了更好的移植性。
  • (multiplex) 在多个应用程序之间共用硬件资源:你可以在一个操作系统同时运行文本编辑器,程序编译器,多个数据库等等。
  • (isolation) 因为在操作系统中可能同时运行很多程序,即使程序出现了故障,多个程序之间互不干扰就变得非常重要。
  • (sharing) 不同的活动之间有时又想要相互影响,比如说数据交互,协同完成任务等。举个例子,我通过文本编辑器创建了一个文件,并且我希望我的编译器能读取文件,我绝对想要数据能共享。
  • (Security/Permission System/Access Control System) 在很多场景下,用户并不想要共享,比如你登录到了一个公共的计算机,例如Athena,你不会想要其他人来读取你的文件。所以在共享的同时,我们也希望在没有必要的时候不共享。
  • (Perform) 操作系统需要至少不阻止应用程序获得高性能,甚至需要帮助应用程序获得高性能:如果你在硬件上花费了大量的金钱,你会期望你的应用程序拥有硬件应该提供的完整性能,但是很多时候你只负责应用程序编程,你会期望操作系统也必须保证自身提供的服务不会阻止应用程序获得高性能。
  • 同一个操作系统需要能够支持大量不同的用户场景:对于大部分操作系统,必须要支持大量不同类型的应用程序,或许这是一个笔记本,正在运行文本编辑器,正在运行游戏,或许你的操作系统需要支持数据库服务器和云计算。通常来说,设计并构造一个操作系统代价是非常大的,所以人们总是希望在相同的操作系统上,例如Linux,运行大量的任务。我认为大部分人都已经跑过Linux,并使用了我刚刚描述的所有的场景。

1.2操作系统结构

过去几十年,人们将一些分层的设计思想加入到操作系统中,并运行的很好。

这里实际上就是操作系统内部组成,当我想到这里的组织结构时,我首先会想到用一个矩形表示一个计算机,这个计算机有一些硬件资源,我会将它放在矩形的下面,硬件资源包括了CPU,内存,磁盘,网卡。所以硬件资源在最低一层。


space的分类:

  • (架构的最上层) user space:运行各种各样的程序,例如:文本编辑器(VI),C编译器(CC),Shell
  • kernel space:
    • 计算机资源的守护者
    • first & only & always
    • 维护每一个用户的空间进程和大量的数据结构
    • 内置的服务,例如文件系统:实现文件名,文件内容和目录,将文件存储在磁盘中,user space的程序和kernel space中的文件系统进行交互,文件系统再与磁盘交互

这门课的关注点

  • kernel
  • 连接kernel与use space program的接口
  • kernel内软件的架构

kernel中的服务

  • 文件系统:管理文件内容并找出文件具体在磁盘中的哪个位置。维护了一个独立的命名空间,其中每个文件都有文件名,并且命名空间中有一个层级的目录,每个目录包含了一些文件。所有这些都被文件系统所管理。

  • 进程管理系统:每一个用户空间程序都被称为一个进程,它们有自己的内存和共享的CPU时间。同时,Kernel会管理内存的分配。不同的进程需要不同数量的内存,Kernel会复用内存、划分内存,并为所有的进程分配内存。

  • (security)Access Control:当一个进程想要使用某些资源时,比如读取磁盘中的数据,使用某些内存,Kernel中的Access Control机制会决定是否允许这样的操作。

在一个真实的完备的操作系统中,会有很多很多其他的服务,比如在不同进程之间通信的进程间通信服务,比如一大票与网络关联的软件(TCP/IP协议栈),比如支持声卡的软件,比如支持数百种不同磁盘,不同网卡的驱动。所以在一个完备的系统中,Kernel会包含大量的内容,数百万行代码。


program如何与kernel交互:system call
program和kernel之间的接口:API

instance

如果program想要打开一个文件,他会调用名为open的system call,并且把文件名作为参数传给open。假设现在要打开一个名为out的文件,同时还希望写入参数,那么还有个而外的参数,在这里这个参数的值为1,表明我想要写文件。

1
2
3
4
fd=open("out",1)
//arg1:文件名
//arg2:是否要写文件

这里看起来像是个函数调用,但是open是一个system call,它会跳到Kernel,Kernel可以获取到open的参数,执行一些实现了open的Kernel代码,或许会与磁盘有一些交互,最后返回一个文件描述符对象。上图中的fd全称就是file descriptor。之后,应用程序可以使用这个文件描述符作为handle,来表示相应打开的文件。

如果你想要向文件写入数据,相应的系统调用是write。你需要向write传递一个由open返回的文件描述符作为参数。你还需要向write传递一个指向要写入数据的指针(数据通常是char型序列),在C语言中,可以简单传递一个双引号表示的字符串(下图中的\n表示是换行)。第三个参数是你想要写入字符的数量。

1
2
3
4
5
6
fd=open("out",1)
write(fd,"hello\n",6)
//arg1:open函数的返回值
//arg2:写入数据的指针
//arg3:写入字符的数量

更有意思的系统调用是fork。fork是一个这样的系统调用,它创建了一个与调用进程一模一样的新的进程,并返回新进程的process ID/pid。

1
2
3
4
fd=open("out",1)
write(fd,"hello\n",6)
pid=fork()

这些系统调用看起来就跟普通的函数调用一样。系统调用不同的地方是,它最终会跳到系统内核中。

1.3 hard and insteresting

hard:

  • 内核编程困难:你在编写、修改,扩展内核,或者写一个新的操作系统内核时,你实际上在提供一个基础设施让别人来运行他们的程序。当程序员在写普通的应用程序时,应用程序下面都是操作系统。而当我们在构建操作系统时,在操作系统下面就是硬件了,这些硬件通常会更难处理。
  • 满足一些列矛盾的需求:efficient-abstract,powerful-simple api,flexible-secore

interesting:

  • 操作系统提供了大量的特性和大量的服务,但是它们趋向于相互交互。有时,这种交互以奇怪的方式进行,并且需要你大量的思考。
  • 操作系统需要能够满足广泛的使用场景。相同的操作系统需要既给数据库服务器使用,又给智能手机使用。

question

学生提问:系统调用跳到内核与标准的函数调用跳到另一个函数相比,区别是什么?

Robert教授:Kernel的代码总是有特殊的权限。当机器启动Kernel时,Kernel会有特殊的权限能直接访问各种各样的硬件,例如磁盘。而普通的用户程序是没有办法直接访问这些硬件的。所以,当你执行一个普通的函数调用时,你所调用的函数并没有对于硬件的特殊权限。然而,如果你触发系统调用到内核中,内核中的具体实现会具有这些特殊的权限,这样就能修改敏感的和被保护的硬件资源,比如访问硬件磁盘。我们之后会介绍更多有关的细节。


学生提问:对于应用程序开发人员来说,他们会基于一些操作系统做开发,真正的深入理解这些操作系统有多重要?他们需要成为操作系统的专家吗?

Robert教授:你不必成为一个专家。但是如果你花费大量时间来开发,维护并调试应用程序,你最终还是会知道大量操作系统的知识。不论你是否是有意要掌握这些知识,它们就是出现了,而你不得不去理解它们。


学生提问:对于一些例如Python的高阶编程语言(高阶是指离自然语言更接近,低阶是指离机器语言更接近如C,汇编),它们是直接执行系统调用呢,还是内部对系统调用进行了封装呢?

Robert教授:许多高阶的编程语言都离系统调用较远,这是一个事实。部分原因是很多编程语言想要提供可以在多个操作系统上运行的可移植的环境,所以它们不能依赖特定的系统调用。所以,对于这个问题的答案我认为是,如果你使用了Python,你在某种程度上就与系统调用接口隔离了。当然,在Python内部,最终还是要执行系统调用来完成相应的工作。当然,Python和许多其他的编程语言通常都有方法能直接访问系统调用

1.4 课程结构和资源

资源:

  • 6.s081
  • pizza

授课:

  • os基本概念
  • xv6代码
  • lab
  • paper

1.5 read,write,exit-system call

XV6是一个简化的类似Unix的操作系统,而Unix是一个老的操作系统。它是受Unix启发创造的,有着相同的文件结构,但是却要比任何真实的Unix操作系统都要简单的多。

XV6运行在一个RISC-V微处理器上,我们会在一个QEMU模拟器上运行XV6。

ls: 全称为list,目录内容显示命令

关于fd:

  • fd是什么: 是File descriptor 的缩写,中文名叫做:文件描述符。文件描述符是一个非负整数,本质上是一个索引值

  • 什么时候拿到的fd: 当打开一个文件时,内核向进程返回一个文件描述符( open 系统调用得到 ),后续 read、write 这个文件时,则只需要用这个文件描述符来标识该文件,将其作为参数传入 read、write。

  • fd 的值范围是什么? :在 POSIX 语义中,0,1,2 这三个 fd 值已经被赋予特殊含义,分别是标准输入( STDIN_FILENO ),标准输出( STDOUT_FILENO ),标准错误( STDERR_FILENO )。

    文件描述符是有一个范围的:0 ~ OPEN_MAX-1 ,最早期的 UNIX 系统中范围很小,现在的主流系统单就这个值来说,变化范围是几乎不受限制的,只受到系统硬件配置和系统管理员配置的约束。


wirite:写入文件描述符

  • ssize_t write(int fd, const void *buf, size_t count)
  • 向文件描述符fd写入count个字节的数据
  • 返回值:正常为实际写入的字节数

read:读取文件描述符

  • ssize_t read(int fd, void *buf, size_t count)
  • 从文件描述符fd读取count个字节的数据到缓冲区buf
  • 返回值:正常为实际写入的字节数

exit:退出进程

  • void exit(int status)
  • 使当前进程退出,并返回退出状态status