操作系统课堂笔记二-操作系统运行环境

操作系统运行环境

操作系统主要工作

  • 程序的执行
  • 完成与体系结构相关的工作(重点: 操作系统必须了解和适配硬件)
  • 完成应用程序所需的共性任务, 提供各种基础服务
  • 性能,安全,健壮性等问题

操作系统运行状态

中央处理器(CPU)

  • 一般由运算器, 控制器, 一系列寄存器以及高速缓存构成
  • 两类寄存器
    • 用户可见寄存器: 高级语言通过优化算法, 分配并且使用, 目的是减少内存的访问次数
    • 控制状态寄存器: 用于控制处理器的操作, 通常是操作系统代码使用

控制状态寄存器

  • 用于控制处理器的操作
  • 在某种特权级别下可以访问修改
  • 常见的几种寄存器
    • 程序计数器(PC-Program Counter): 记录要取出指令的地址
    • 指令寄存器(IR-Instruction Register): 记录最近取出的指令
    • 程序状态字寄存器(PSW-Program Status Word): 记录了CPU运行的状态, 如: 条件码,模式,控制位等信息. 通常会在psw中专门设置一位, 根据运行程序对资源和指令使用权限而设置不同的CPU状态。
  • 举例
    • X86架构中 EFLAGS寄存器 (IOPL: IO的权限级别)

用户态

  • 运行用户程序
  • 用户能够使用的指令叫做 非特权指令

核心态

  • 运行操作系统程序
  • 操作系统能够执行的指令叫做 特权指令

指令

  • 特权指令: 启动I/O, 内存清零, 修改程序状态字, 设置时钟, 允许/禁止中断, 停机
  • 非特权指令: 控制转移, 算术运算, 访管指令, 取数指令
  • 举例X86系列处理器
    • 支持4个级别 R0-R3 R0代表内核态 R3是用户态
    • 不同级别对应指令不同 包含关系
  • 陷入指令(访管指令): 是一条特殊的指令, 通过这条指令, 可以使用户程序向操作系统提出各种各样的请求,例如: int, trap, syscall, sysenter/sysexit

状态转换

  • 用户态->核心态
    • 唯一途径: 中断/异常/陷入机制
  • 内核态->用户态
    • 修改程序状态字psw

中断/异常机制

  • 操作系统中的中断/异常机制很重要, 可以比作汽车的发动机, 飞机的引擎. 也可以说操作系统由中断驱动或者事件驱动
  • 主要作用:
    • 外部设备发来的中断请求
    • 可使os捕获用户程序提出的服务请求
    • 防止用户程序执行过程中的破坏性活动
  • 概念: CPU对系统发生某个事件的一种反应, CPU会主动中断当前的任务, 保留现场, 切换到需要处理的事件, 然后回来继续上次的任务.
  • 特点
    • 事件是随机发生的
    • 自动处理
    • 可恢复
  • 为什么引入 中断机制?
    • 为了支持CPU和设备之间的并行操作。
    • 举例: CPU负责启动输入/输出设备去干活,然后CPU去处理别的事情, 当设备完成任务之后会向CPU汇报结果, 以寻求后续要做的事情, 那么汇报的过程就需要中断的参与, 让CPU先停下手头的活, 决策一下后续的工作。
  • 为什么引入异常机制?
    • 表示CPU在执行过程中自身出现了问题
    • 举例: 当出现算术溢出, 除零, 取数时的奇偶错, 地址越界, 陷入指令等异常时, 硬件会改变当前CPU的执行流程, 转到相应的错误处理程序或异常处理程序或执行系统调用。
  • 中断和异常的不同点?
    • 举例: 我们以小明看书为例, 看书的中途来了个电话, 小明记录好看到多少页然后去接电话之后回来继续看 这个叫 中断。 小明突然口渴了, 记录下看到哪一页然后去喝水, 最后再继续看书 这个叫异常,牛逼!

事件

  • 中断(外中断)
    • I/O 中断: 键盘上control+c,打印机结束了, 读盘结束
    • 时钟中断: 设置定时器到点了, 时间片到了
    • 硬件故障: 笔记本电脑没电了, 读内存奇偶校验错误
  • 异常(内中断)
    • 系统调用
    • 页故障/页错误: 程序运行时候需要从磁盘load到内存(缺页异常)
    • 保护性异常: 内存空间标记为只读(执行写操作), 访问内存空间越界
    • 断点指令: 程序debug
    • 程序性异常: 算术溢出, 栈溢出, 除零
  • 所以中断和异常又可以理解为程序外部遇到的问题叫做中断, 程序内部问题叫做异常中断是正在运行的外部程序所不期望的, 异常是由正在执行的指令引发的。

中断异常小结

类别 原因 异步/同步 返回行为
中断Interrupt 来自I/O设备,其他硬件 异步 返回下一条指令
陷入Trap 有意识安排的 同步 返回下一条指令
故障Fault 可恢复的错误 同步 返回当前指令
终止Abort 不可恢复的错误 同步 不返回

操作系统运行机制

操作系统需求-保护

  • 首先我们为什么需要保护, 我们知道, 操作系统四大特征中并发和共享容易导致一些问题, 比如多个进程之间竞争资源, 那么我们就需要操作系统给我们提供一个保护措施, 使得应用程序间相互不干扰, 应用程序和操作系统之间不干扰。

需要硬件提供的运行机制

  • 处理器具有特权级别, 能在不同特权级运行的不同指令集合
  • 硬件机制可将os和用户程序隔离

中断异常机制工作原理

  • 中断和异常是现在计算机的核心机制之一, 软硬件相互配合而使计算机系统得以发挥充分的作用。

硬件和软件

  • 硬件做了什么? --中断/异常响应
    • 硬件捕获发出的中断/异常请求, 以一定的方式响应, 将处理器的控制权交给特定的程序。
  • 软件做了什么? --中断/异常处理程序
    • 识别中断/异常类型并完成相应处理

中断响应

  • 发现中断, 接收中断的过程, 中断硬件部件完成。
  • 处理器控制部件中设有中断寄存器, CPU何时做响应中断的工作?
    • CPU->取指令->执行下一条指令->扫描中断寄存器, 看是否有中断信号->有则往下看,否则继续执行下一条指令
    • 当中断信号发生时, 中断硬件按照规定编码将中断触发器内容送入PSW相应位, 称为中断码,通过查中断向量表引出中断处理程序。
  • 中断向量表
    • 中断向量: 一个内存单元, 存放中断处理程序入口地址和程序运行时所需的处理机状态字。
    • 若干个中断向量构成了中断向量表, 中断向量表存储的是各个中断程序的入口地址以及出现中断的原因(其实就是送入psw里面的东西), 程序会按照中断号或者异常类型把控制权转移给对应的中断处理程序
  • 下面通过一张图我们了解一下Linux中的中断向量表,转自这里
    在这里插入图片描述
  • 下面按照自己的理解总结下中断响应的流程
    在这里插入图片描述
  • 中断处理程序
    • 为每一类中断/异常事件编好相应的处理程序,并设置好中断向量表
    • 系统运行是如果响应中断, 中断硬件部件将CPU控制权转交给中断处理程序,下面看下中断处理程序做了哪些事:
      • 保存相关寄存器信息
      • 分析中断/异常原因
      • 执行对应的处理功能
      • 恢复现场
  • 总的来说就是两句话: 软件提前设置好, 硬件部件来执行

相关案例

  • 我们以打印机输入输出中断为例描述一下

  • 硬件方面:

    • 打印机给CPU发出中断信号
    • CPU处理完当前指令, 判断中断来源并向相关设备发送确认信号
    • 处理器状态切换到核心态
    • 在系统中保存上下文, 主要是程序计数器PC和状态字寄存器PSW
    • CPU根据中断码查中断向量表, 查到程序入口地址, 将PC设置成该地址。当新的指令周期开始时,CPU转移到中断处理程序
  • 软件方面

    • 在系统栈中保存现场信息
    • 检查I/O设备状态
  • 中断处理结束后, CPU检测到中断返回指令,从系统堆栈中恢复被中断程序的上下文, 也就是将psw和pc设置回原来的值, 等下一个指令周期继续运行。(硬件完成)

系统调用机制

  • 操作系统向用户程序提供的接口

系统调用

  • 系统调用是什么?
    • 全称操作系统功能调用, 指的是用户在编程时可以调用操作系统的功能
  • 系统调用的作用?
    • 系统调用是操作系统和程序员交互的唯一入口
    • 将CPU状态从用户态切换到核心态
  • 典型的系统调用
    • 操作系统系统调用接口很多,举几个例子:
    • 进程控制
    • 进程通信
    • 文件使用
    • 目录操作
    • 设备管理
    • 信息维护
  • 系统调用,库函数,API,内核函数之间的区别
    • 应用程序->C库函数/API->系统调用->内核函数处理(常见情况)
    • 应用程序->系统调用->内核函数处理
    • 所以,区别在于, 系统调用全是内核函数封装起来形成的接口, 而C库函数和API只有部分(个人理解)

系统调用机制设计与执行过程

  • 利用硬件提供的中断/异常机制, 支持系统调用服务的实现
  • 选择一条特殊的指令, 陷入指令(也叫做访管指令), 引发异常, 完成用户态->内核态的切换(所有的系统调用都是通过该指令进入内核)
  • 每个系统调用事先给好一个编号, 也叫功能号(访管指令其实是通过传参的方式进入系统)
  • 系统调用表: 存放系统调用服务的入口地址
  • 如何将参数传递给内核?
    • 由陷入指令自带参数实现: 陷入指令能够携带的参数有限, 还有携带功能号
    • 通过通用寄存器传递参数实现(最为常用): 这些寄存器是操作系统和用户都能访问的,因此寄存器个数决定了传参的个数
    • 内存中开辟专用堆栈来实现

系统调用实战

  • 说了这么多, 我们来一段代码看下系统调用的过程
#代码文件是helloworld.c
#include<unistd.h>
int main(int argc, char const *argv[])
{
    /* code */
    char hello[5] = {"1", "2", "3", "4", "5"};
    write(1, hello, 7);
    return 0;
}
  • 我们上面这段代码应该会经过两次系统调用, 一次在write函数,一次在于return
  • 执行如下命令查看汇编代码:
$ gcc -S helloworld.c -o hello.s
$ cat hello.s
	.section	__TEXT,__text,regular,pure_instructions
	.build_version macos, 10, 14
	.globl	_main                   ## -- Begin function main
	.p2align	4, 0x90
_main:                                  ## @main
	.cfi_startproc
## %bb.0:
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register %rbp
	subq	$32, %rsp
	movl	$1, %eax // 主要看这一行开始 write(1...)就是代表功能号1 这一行就是给寄存器传参
	movl	$7, %ecx
	movl	%ecx, %edx
	leaq	-21(%rbp), %r8
	movl	$0, -4(%rbp)
	movl	%edi, -8(%rbp)
	movq	%rsi, -16(%rbp)
	movl	l_main.hello(%rip), %ecx
	movl	%ecx, -21(%rbp)
	movb	l_main.hello+4(%rip), %r9b
	movb	%r9b, -17(%rbp)
	movl	%eax, %edi
	movq	%r8, %rsi
	callq	_write
	xorl	%ecx, %ecx
	movq	%rax, -32(%rbp)         ## 8-byte Spill
	movl	%ecx, %eax
	addq	$32, %rsp
	popq	%rbp
	retq
	.cfi_endproc
                                        ## -- End function
	.section	__TEXT,__const
l_main.hello:                           ## @main.hello
	.ascii	"Hello"

系统调用执行过程

  • CPU接到特殊的陷入指令将进行如下操作
  • 中断/异常机制: 硬件保护现场; 通过查中断向量表(也就是拿到中断程序的入口地址) 将CPU控制权交给中断处理程序
  • 系统调用总入口程序: 保存现场; 将(寄存器中的参数) 传递给内核中的堆栈, 然后查找系统调用表, 将控制权交给相关系统调用过程或者内核函数。
  • 执行系统调用过程
  • 恢复现场返回用户程序

参考

[1] 操作系统

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章