引言

引言

考虑一个Web server,操作系统的部分工作是使得开发类似web server这样的应用更加容易。从Web server的角度,操作系统可能要提供一些支持,比如:

  • Web server需要访问文件或调用外部程序计算并获取结果 - 操作系统怎么让不同的应用互相通信?
  • 客户端可以并发访问,为获得更好的体验,server同时处理多个客户端的请求 - 操作系统如何支持应用同时做多件事情
  • 随着业务增长,web server可能要部署在其他硬件上 - 操作系统如何屏蔽硬件差异,以避免重写web server?

操作系统定义

操作系统是一个软件层,管理计算机资源,为用户及其应用程序提供服务。

操作系统角色

操作系统作为硬件与应用的中间层,需要扮演三个角色:

  • 裁判(Referee):资源共享。应用共享硬件资源,需要操作系统决定资源何时分配给哪个应用。
  • 幻术师(Illusionist):提供一个物理硬件的抽象层,使得应用仿佛拥有完整的、资源不受限的计算机从而简化设计。例如应用不必关注物理内存分布、不必关注其他应用的资源竞争。
  • 粘合剂(Glue):为应用程序提供公共服务或组件,例如文件系统、统一的用户界面观感、IO。

其他系统的类似角色

除了操作系统,很多其他系统也有类似的设计挑战,比如,云计算、浏览器、数据库系统、互联网。以云计算为例:

  • Referee:资源在云上不同应用之间如何分配?
  • Illusionist:云的资源一直在变化,应该如何向应用开发者隔离硬件变化?
  • Glue:云上服务通常跨主机运行。云软件需要提供那些服务来协调不同的服务以及共享数据?

操作系统评估标准:

  • 可靠性和可用性:系统是否按照预期运行及其运行时间比例。
  • 安全性:是否能够防止恶意攻击及保护用户数据隐私。
  • 可移植性:系统是否易于适配新的硬件。
  • 性能:包括响应时间、吞吐量以及可预测性。
  • 流行度:应用程序、硬件、用户生态。

操作系统:过去现在与未来

  • 早期操作系统:操作系统主要是通过提供一些公共服务(如标准IO程序)来减少编程错误。
  • 批处理操作系统
    • 依次对每个任务加载、执行、卸载的步骤。在一个任务执行时,利用DMA,可以并行进行IO操作。
    • 多任务操作系统:允许多个任务并发执行,当一个任务需要IO给出CPU时,其他任务可以执行,提高了处理器利用率。也带来了更高的隔离需求。
    • 虚拟机最早用来解决批处理系统难以Debug的问题。
  • 分时操作系统:支持交互式使用,而不只是批处理。
  • 现代操作系统:随着计算机技术的发展,各种现代操作系统运行在不同的硬件上,包括个人电脑、智能手机、嵌入式设备、服务器等。
  • 未来操作系统展望:
    • 安全性和可靠性的更高要求
    • 操作系统需要适应底层硬件发展。数据中心向大规模、异构方向发展;无处不在的便携硬件,如各种可穿戴设备。

内核抽象

概述

  • 操作系统的一个核心角色是保护——把misbehaving的程序和用户隔离开来,以避免影响其他应用或操作系统本身。
  • 保护是实现某些操作系统其他目标的基础。如安全性、可靠性、隐私、公平资源分配。
  • **进程是内核提供的受保护执行的抽象**
  • 这种设计模式——可扩展的程序运行第三方脚本——在其他领域也有很多,比如浏览器。

进程抽象

  • 进程是程序运行实例,
    • 执行指令
      • program counter
      • 不同类型的指令:算术、分支、访问IO等
    • 访问内存
      • 代码
      • 数据
  • 操作系统通过进程控制块(PCB)管理和追踪进程
  • 本章只讨论简单的单线程进程

进程保护

操作系统如何防止进程危害其他进程或者操作系统本身?

  • 不考虑性能,一个假想的方案:由内核来执行用户进程的指令。
    • 对于用户态,在执行每个指令前检查其权限,如
      • 是否会跳转到其他进程内存?
      • 是否访问其他进程的数据?
  • 如果想要用户态代码直接在处理器上运行,硬件至少需要提供以下支持:
    • 特权指令:只能在内核态执行,不能在用户态执行;例如
      • 用户态代码不能修改自己能访问的内存范围
      • 用户态代码不能关闭中断
    • 内存保护
      • 用户进程只能读写自己的内存,而不能访问其他进程或kernel的内存
      • 内存保护的一种简单方案:每个进程通过base和bound两个寄存器指定合法的内存地址。
        • 相比虚拟地址方案,缺少很多特性。
    • 时钟中断:允许内核周期性重获处理器的控制权,才有可能进行多任务调度。

模式切换

  • 用户态到内核态
    • 外部中断
    • 处理器异常
    • 系统调用
  • 内核态到用户态
    • 新建进程
    • 切换进程
    • 从中断、异常、系统调用中返回
    • Upcall(内核向用户态发起的调用): 如linux下的信号

实现安全的模式切换

  • context switch 的实现至少应该满足:
    • 受限地进入内核:用户进程不能任意访问内核内存,硬件必须确保进入内核的入口点是由内核设置的入口点。
    • 处理器状态的原子切换:例如不能发生PC切换到内核态代码,但内存保护状态还没切换到内核态。
    • 中断处理对被中断的进程透明。
  • 需要软硬件共同实现,相关机制包括:
    • 中断向量
    • Interrupt stack/Kernel stack:处理器和内核配合,在中断或系统调用时切换栈
    • 中断屏蔽:有些内核代码执行过程不能被中断,需要屏蔽中断
    • 保存/恢复被中断的现场,主要是寄存器。

具体实现

  • x86中断处理的模式切换
  • 系统调用的实现
  • 创建新进程
  • 实现upcall

Case Study

  • 从BIOS到bootloader到Kernel启动过程
  • 虚拟机

编程接口

概述 - 如何使用进程抽象

  • 操作系统给应用提供的功能,包括
    • 进程管理
    • IO
    • 线程管理
    • 内存管理
    • 存储与文件系统
    • 网络
    • 图形接口与窗口管理
    • 认证与安全
  • 这些也是本章及本书接下来的核心: 从进程抽象出发,探讨操作系统设计的原则与实践

概述 - 操作系统的组织

  • 这些功能在哪实现(kernel还是用户库)
    • 用户态程序:如用户登陆管理
    • 用户态链接库:如窗口组件
    • kernel中,通过syscall访问:如进程调度、内存管理
    • kernel启动的内核态进程:如很多系统中的窗口管理
  • 权衡:
    • 灵活性
    • 可靠性
    • 性能
    • 安全性

进程管理

  • 早期批处理系统中,完全由kernel来管理进程
    • 用户提交任务,操作系统在合适的时间初始化并启动进程
  • 现代操作系统中,允许用户程序来创建和管理它们自己的进程
    • 如Shell、Web服务器、窗口管理器、浏览器、数据库、编译器等等
    • 每个用户程序管理按自己的逻辑管理进程,例如
      • Web服务器主进程监听请求,子进程处理请求
      • Shell运行用户输入的命令,还可以提供一些任务管理功能
  • 内核提供基本的进程管理相关的系统调用
    • Windows: `CreateProcess`
    • UNIX: `fork` & `exec` <!– 两种设计的比较 https://news.ycombinator.com/item?id=19622105 https://www.reddit.com/r/osdev/comments/lq9bbn/forkexec_vs_createprocess/ https://www.microsoft.com/en-us/research/uploads/prod/2019/04/fork-hotos19.pdf

用户态程序的进程管理和内核的进程管理各自做不同的事情: 内核基于通用的进程模型,提供其实现,并提供系统调用接口给用户程序。 用户程序根据自己的逻辑,建立自己的(业务?)进程模型,使用内核syscall接口实现。

进程需要wait,否则残留僵尸进程,背后的逻辑与文件打开后需要close是一样的,内核对于进程或文件都有对应的内核对象。wait/close是在做gabage collection。 –>

输入/输出

  • IO设备多种多样:例如键盘、鼠标、显示器、磁盘、以太网卡等
  • 选择1:给每种设备提供对应的编程接口
    • 问题:一旦有新的设备类型,就需要提供新的系统调用
  • UNIX的一个主要创新是通过单一的通用接口统一所有设备输入输出。UNIX IO接口的基本思想如下:
    • 统一:所有文件操作、设备IO、进程间通信都使用同一套系统调用:`open`, `close`, `read`, `write`。
    • 使用前先open
    • 面向字节
    • 内核缓冲读写:块设备或流设备的读写都需要经过内核,使得读写接口保持一致。使读写操作与具体设备解耦。
    • 显式close
  • 要实现进程间通信,还需要几个概念:
    • 管道:管道是内核缓冲,一个进程写入管道,另一个进程读取,从而实现通信。
    • IO重定向:标准输入输出+IO重定向可以使得进程不必关心输入/输出对应的实际文件。
    • IO多路复用:当server/client模式下,server通过多个pipe与多个client相连时。

案例研究

  • Shell实现
    • 上面的IO模型使得实现以下行为更为简单:
      • 程序可以是包含命令的文件(shell脚本):shell解释器不需要区分键盘键入的命令还是从文件读入的命令。
      • 程序可以使用文件作为标准输入/输出:标准输入/输出+IO重定向
      • 通过管道连接两个程序,实现生产者/消费者模型
  • 进程间通信:几个普遍使用的进程间通信方式都与IO模型紧密相关:
    • 生产者-消费者:管道
    • 客户端-服务器:客户端与服务器都是通过IO 接口与kernel交互来读写。
    • 文件系统

操作系统结构

操作系统要提供很多功能,有些需要在内核中实现,有些可以放在用户态实现(比如上面的shell)。 操作系统不同模块之间的交互也有很多,例如

  • 很多模块依赖同步原语
  • 虚拟内存管理模块依赖硬件的地址翻译
  • 文件系统和内存管理共享物理内存;它们可能都依赖磁盘驱动

因此操作系统设计需要权衡,更多在在kernel中实现

  • 有助于提高性能,以及模块间更好地集成
  • 但也会更不灵活、难以修改。

操作系统结构

  • 宏内核
    • 广泛使用的商用内核大部分选择宏内核,包括Windows, Linux, MacOS
    • 为了提高可移植性,大部分现代操作系统的硬件管理都包含两个特性:
      • 硬件抽象层
      • 驱动动态加载
  • 微内核
    • 在用户空间运行更多的操作系统服务
    • 例如窗口管理器在大多数操作系统中是用户态实现的
  • 对于操作系统开发者,微内核更容易模块化以及Deug
  • 但对于用户程序来说,微内核的好处并不显著(可能有潜在的可靠性提高),但很可能带来性能损失

未来方向

例如

  • 应用成为一个自成体系的小型操作系统
    • 当前的操作系统的设计中没有考虑这种情况。比如系统调用。
  • 资源分配决策对应用程序显式可见:传统上操作系统透明地进行资源分配决策,以提高整体性能。
    • 这种透明性意味着应用程序不知道资源使用情况,也无法做出优化。