Skip to content

6.1810 2024 第1讲:操作系统概述

什么是操作系统?

一个操作系统(O/S)是介于硬件和用户应用程序之间的软件层。

  • 硬件 (h/w): CPU、内存 (RAM)、磁盘、网络接口 (net) 等。
  • 用户应用程序: sh (shell), cc (编译器), 数据库 (DB) 等。
  • 内核服务: 文件系统 (FS), 进程管理, 内存管理, 网络协议栈等。
  • 系统调用: 应用程序请求内核服务的接口。

操作系统的目的

  • 多路复用 (Multiplex): 在多个应用程序之间共享硬件资源。
  • 隔离 (Isolate): 隔离应用程序以确保安全并控制 bug 的影响范围。
  • 共享 (Share): 在相互协作的应用程序之间共享数据。
  • 抽象 (Abstract): 为上层应用提供硬件的抽象,以实现可移植性和便利性。

设计中的权衡

操作系统设计充满了挑战性的权衡:

  • 效率 vs. 抽象/可移植/通用性
  • 强大的接口 vs. 简单的接口
  • 灵活性 vs. 安全性
  • 可移植性 vs. 利用新硬件和新接口

课程结构

  • 在线信息:

  • 讲座:

    • 操作系统思想
    • 以 xv6(一个小巧的操作系统)为例,通过代码和书籍进行案例研究。
    • 实验背景知识
    • 操作系统相关论文
    • 要求: 每节课前提交一个关于阅读材料的问题。
  • 实验 (Labs):

    • 目的: 获得动手实践经验。
    • 大部分实验为期一周。
    • 类型:
      1. 系统编程: (例如第一个实验)
      2. 操作系统原语: (例如,线程切换)
      3. xv6 内核扩展: (例如,网络驱动)
    • 寻求帮助:
      • 在 Piazza 上提问和回答问题。
      • 参加助教的 Office Hours。
    • 注意: 鼓励讨论,但请不要查看他人的解决方案!
  • 评分:

    • 50% 实验 (基于你本地运行的相同测试)。
    • 30% 考试 (期中、期末)。
    • 15% 实验检查会议 (我们会随机抽查实验并提问)。
    • 5% 家庭作业问题。
    • 重点: 实验占分很高,请尽早开始!

UNIX 系统调用简介

应用程序通过“系统调用”与操作系统交互。你将在实验中大量使用、扩展和改进它们。

xv6 简介

  • xv6 是我们为本课程专门创建的一个操作系统,它模仿 UNIX,但简单得多。
  • 为什么是 UNIX? 它是许多现代操作系统(如 Linux, MacOS)的祖先。学习 xv6 有助于理解其他操作系统。
  • 你将能够完全理解 xv6 的每一部分——没有秘密。
  • xv6 在课程中有两个角色:
    1. 核心机制的示例。
    2. 大部分实验的起点。
  • xv6 运行在 RISC-V CPU 上,你将在 QEMU 模拟器中运行它。

系统调用示例

  1. read()write() (ex1.c: 复制输入到输出)

    • read()write() 是系统调用,它们看起来像函数调用,但实际上会以一种保持用户/内核隔离的方式跳转到内核中。
    • 文件描述符 (File Descriptor, FD): 第一个参数是一个小整数,告诉内核要操作哪个“打开的文件”。
    • UNIX 约定: FD 0 是标准输入,1 是标准输出。
    • read() 的第二个参数是要读入数据的内存地址,第三个参数是最大字节数。
  2. open() (ex2.c: 创建文件)

    • open() 创建或打开一个文件,并返回一个新的文件描述符。
    • 每个进程都有自己独立的文件描述符空间。
  3. fork() (ex3.c: 创建新进程)

    • fork() 创建一个调用进程的副本(子进程)。
    • 子进程拥有父进程内存、寄存器和文件描述符的副本。
    • 返回值: 在父进程中返回子进程的 PID,在子进程中返回 0
  4. exec() (ex4.c: 执行一个程序)

    • exec() 用一个可执行文件中的内容替换当前进程的内存。
    • 它会加载新的指令和数据,但保留文件描述符。
    • 如果成功,exec() 不会返回。
  5. wait() (ex5.c: fork + exec)

    • Shell 不能直接调用 exec(),否则它自己就退出了。
    • 标准流程: Shell fork() 一个子进程,子进程调用 exec(),父进程调用 wait() 等待子进程结束。
    • wait() 允许父进程获取子进程的退出状态。
  6. I/O 重定向 (ex6.c)

    • Shell 如何实现 echo hello > out
    • 子进程在调用 exec() 之前,先 close(1) 关闭标准输出,然后 open("out", ...)。因为 open 会返回最小的可用 FD,所以新的文件会获得 FD 1
    • exec 会保留文件描述符,所以 echo 的标准输出就自然地被重定向到了文件 out
  7. 管道 (Pipes) (ex7.c, ex8.c)

    • Shell 如何实现 ls | grep x
    • pipe() 系统调用创建一对相连的文件描述符。
    • 写入其中一个 FD 的数据可以从另一个 FD 读出。
    • Shell 创建一个管道,然后 fork() 两次,设置好子进程的输入输出,再分别 exec() 对应的命令。