OSTEP 第六章笔记
ch6 机制:受限直接执行笔记梳理
虚拟化CPU?
- 基本思想:运行一个进程一段时间,然后运行另一个进程,如此轮换 → 以某种方式时分共享(time sharing) CPU
- 挑战
- 性能:高性能,不增加系统开销
- 控制权:保持操作系统对 CPU 的控制权
基本技巧:受限直接执行(limited direct execution)
- 直接执行:直接在 CPU 上运行程序
- 在进程列表中为其创建一个进程条目,为其分配一些内存,将程序代码(从磁盘)加载到内存中,找到入口点(main()函数或类似的),跳转到那里,并开始运行用户的代码
- 问题1:受限制的操作
- 问题2:在进程之间切换
问题1:受限制的操作
我们采用的办法是引入一种新的处理器模式,称为用户模式。
- 在用户模式下运行的代码会受到限制。
内核模式
- 操作系统(或内核)就以这种模式运行
- 在内核模式下运行的代码可以做它喜欢的事情,包括特权操作,如发出I/O请求和执行所有类型 受限指令
如果用户希望执行某种特权操作,应该怎么做?
- 为了实现这一点,几乎所有的现代硬件都提供了用户程序执行系统调用的能力。系统调用允许内核小心地向用户程序暴露某些关键功能,例如访问文件系统、创建和销毁进程、与其他进程通信,以及分配更多内存。大多数操作系统提供几百个调用。
要执行系统调用,程序必须执行特殊的 trap 指令(跳入内核并将特权级别提升到内核模式)。
完成后,操作系统调用一个特殊的 return-from-trap 指令(返回到发起调用的应用程序,同时将特权级别降低到用户模式)
执行 trap 时,硬件需要存储足够的调用者寄存器,以便 OS 发出 return-from-trap 时能够正确返回。
例如,在x86上,处理器会将程序计数器、标志和其他一些寄存器推送到每个进程的内核栈上。return-from-trap 指令将从栈弹出这些值,并恢复执行用户模式程序。
trap 如何知道在 OS 内运行哪些代码?
- 内核通过在启动时设置陷入表(trap table)来实现。当机器启动时,它在内核模式下执行。操作系统做的第一件事,就是告诉硬件在发生某些异常事件(如硬盘中断/键盘中断/程序进行系统调用)时要运行哪些代码。
硬件通过提供不同的执行模式来协助操作系统。
- 用户模式:应用程序不能完全访问硬件资源
- 内核模式:操作系统可以访问机器的全部资源
还提供了特殊指令。
- 陷入(trap): 陷入内核
- 从陷入返回(return-from-trap): 从内核模式切换回用户模式
问题2:在进程之间切换
操作系统如何重新获得CPU的控制权,以便它可以在进程之间切换?
a. 协作方式:等待系统调用
- 在协作调度系统中,OS通过等待系统调用,或某种非法操作发生,从而重新获得CPU的控制权
- 但是如果某个进程假如无限循环从不进行系统调用呢?
b. 非协作方式:操作系统进行控制
- 利用 时钟中断 重新获得控制权
时钟中断(timer interrupt)
- 时钟设备可以被编程为每个几毫秒产生一次中断
- 中断发生时,当前正在运行的进程停止,操作系统中预先设置的中断处理程序会运行
首先,在启动时,操作系统必须通知硬件哪些代码在发生中断时运行。
其次,在启动过程中,操作系统也必须启动时钟。
在中断发生时,硬件要为正在运行的程序保存足够的状态,以便随后 return-from-trap 指令能够恢复正在运行的程序。
- 各种寄存器因此被保存(e.g. 进入内核栈)
保存和恢复上下文
如果调度程序决定切换到另一个程序,OS 会执行执行一些底层代码,即所谓的上下文切换
上下文切换(context switch):操作系统要做的就算为当前正在执行的进程保存一些寄存器的值(例如,到它的内核栈),并为即将执行的进程恢复一些寄存器的值(从它的内核栈)
上下文切换的过程:
- 为了保存当前正在运行的进程的上下文,操作系统会执行一些底层汇编代码,来保存通用寄存器、程序计数器,以及当前正在运行的进程的内核栈指针,然后恢复寄存器、程序计数器,并切换内核栈,供即将运行的进程使用。
- 通过切换栈,内核在进入切换代码调用时,是一个进程的上下文,在返回时,是另一个进程的上下文
- 当操作系统最终执行return-from-trap指令时,即将执行的进程编程了当前运行的进程
- 至此上下文切换完成
进程上下文切换时有两种寄存器保存/恢复
- 硬件保存用户上下文(运行进程的用户寄存器)
- 发生时钟中断时
- 硬件隐式地保存在用户模式下运行时的寄存器状态(程序计数器、通用寄存器等)
- 该程序运行时的数据、计算结果、下一条指令的地址等
- 保存到该进程的内核栈
- OS保存内核上下文(内核寄存器)
- 当操作系统决定从A切换到B时
- OS保存在内核模式下所使用的寄存器状态
- 保存在 A 的进程控制块 (PCB) 中
解决并发问题(这里我们先只是简单介绍一下)
- 操作系统可能简单的决定,在中断期间禁止中断。(当然,操作系统这样做必须小心。禁用中断事件过长可能导致丢失中断)
- 操作系统还开发了许多复杂的加锁方案
小结
实现 CPU 虚拟化的关键底层机制:受限直接执行(limited direct execution)
基本思路很简单:就让你想运行的程序在 CPU 上运行,但首先确保设置好硬件,以便在没有操作系统帮助的情况下限制进程可以执行的操作。
OS 先设置陷阱处理程序并启动时钟中断,然后在受限模式下运行进程。只有在执行特权操作,或者当它们独占 CPU 时间过长并因此需要切换时,才需要操作系统干预。
TIPS:重新启动是有用的
- 它让程序回到已知的状态
- 可以回收旧的或泄漏的资源(例如内存)
为什么系统调用看起来像过程调用:
它是一个过程调用,但隐藏在过程调用内部的是著名的 trap 指令。更具体的说,例如当你调用 open() 时,你正在执行的是 C 库的过程调用。C库中进行系统调用的部分是用汇编手工编码的