一、处理器状态
处理器由运算器、控制器、一系列寄存器和高速缓存构成
两类寄存器:
- 用户可见寄存器:高级语言编译器通过优化算法分配并使用之,以减少程序访问内存次数
- 控制和状态寄存器:用于控制处理器的操作,通常由操作系统代码使用
常见的控制和状态寄存器:
- 程序计数器(PC):记录将要取出的指令的地址
- 指令寄存器(IR):记录最近取出的指令
- 程序状态字(PSW:Program Status Word):记录处理器的运行状态,如条件码、模式、控制位等信息
操作系统具有并发和共享的特征,因此需要实现保护和控制
需要硬件提供基本的运行机制:
- 处理器具有特权级别,能在不同的特权级运行的不同指令集合
- 硬件机制可将OS与用户程序隔离
现代处理器通常将CPU状态设计划分为两种、三种或四种
在程序状态字寄存器PSW中专门设置一位,根据运行程序对资源和指令的使用权限而设置不同的CPU状态(IOPL)
操作系统有两种CPU状态:
- 内核态:运行操作系统程序
- 用户态:运行用户程序
-
特权指令:只能由操作系统使用、用户程序不能使用的指令
- 启动I/O,内存清零,修改程序状态字,设置时钟,允许/禁止中断,停机 非特权指令:用户程序可以使用的指令
- 控制转移,算术运算,访管指令,取数指令
X86支持4个处理器特权级别
特权环:R0、R1、R2和R3
- 从R0到R3,特权能力由高到低
- R0相当于内核态,R3相当于用户态,R1和R2则介于两者之间
- 不同级别能够运行的指令集合不同
目前大多数基于x86处理器的操作系统只用了R0和R3两个特权级别
CPU状态之间的转换:
- 用户态 → 内核态:中断/异常/陷入机制
- 内核态 → 用户态:设置程序状态字PSW
一条特殊的指令:陷入指令(又称访管指令)
提供给用户程序的接口,用于调用操作系统的功能(服务)
例如:int,trap,syscall,sysenter/sysexit
相关IA-32寄存器:
寄存器名称 | 本章相关内容 |
---|---|
EFLAGS | 标志位第12、13位IOPL是输入输出特权级位,在当前任务的特权级CPL(Current Privilege Level,CS段寄存器的一个两位字段,用以指明CPU的当前特权级)高于或等于输入输出特权级时,就可以执行像IN、OUT、INS、OUTS、STI、CLI和LOCK等指令而不会产生异常13(即保护异常),在当前任务特权级CPL为0时,POPF(从栈中弹出至标志位)指令和中断返回指令IRET可以改变IOPL字段的值 |
GDTR | 全局描述符表寄存器GDTR(Global Descriptor Table Register),是48位寄存器,用来保存全局描述符表(GDT)的32位基地址和16位GDT的界限 |
IDTR | 中断描述符表寄存器IDTR(Interrupt Descriptor Table Register),是48位寄存器,用来保存中断描述符表(IDT)的32位基地址和16位IDT的界限 |
LDTR | 局部描述符表寄存器LDTR(Global Descriptor Table Register),是16位寄存器,保存局部描述符表LDT段的选择符 |
TR | 任务状态寄存器TR(Task State Register)是16位寄存器,用于保存任务状态段TSS段的16位选择符 |
二、中断与异常机制
中断与异常机制是CPU对系统发生的某个事件作出的一种反应
CPU暂停正在执行的程序,保留现场后自动转去执行相应事件的处理程序,处理完成后返回断点,继续执行被打断的程序(随机,自动,可恢复)
主要作用:
- 及时处理设备发来的中断请求
- 可使OS捕获用户程序提出的服务请求
- 防止用户程序执行过程中的破坏性活动
- 等等…
-
中断的引入:为了支持CPU和设备之间的并行操作
- I/O中断,时钟中断,硬件故障 异常的引入:表示CPU执行指令时本身出现的问题
- 系统调用,页故障/页错误,保护性异常,断点指令,其他程序性异常(如算数溢出等)
类别 | 原因 | 异步/同步 | 返回行为 |
---|---|---|---|
中断(Interrupt) | 来自I/O设备、其他硬件部件 | 异步 | 总是返回到下一条指令 |
陷入(Trap) | 有意识安排的 | 同步 | 返回到下一条指令 |
故障(Fault) | 可恢复的错误 | 同步 | 返回到当前指令 |
终止(Abort) | 不可恢复的错误 | 同步 | 不会返回 |
1. 中断/异常机制的工作原理
-
硬件:中断/异常响应
- 捕获中断源发出的中断/异常请求,以一定方式响应,将处理器控制权交给特定的处理程序 软件:中断/异常处理程序
- 识别中断/异常类型并完成相应的处理
在每条指令执行周期的最后时刻扫描中断寄存器,查看是否有中断信号
- 若有中断,中断硬件将该中断触发器内容按规定编码送入PSW的相应位,称为中断码,通过查中断向量表引出中断处理程序
- 若无中断信号,继续执行下一条指令
中断向量:一个内存单元,存放中断处理程序入口地址和程序运行时所需的处理机状态字
中断处理程序:设计操作系统时,为每一类中断/异常事件编好相应的处理程序,并设置好中断向量表
- 保存相关寄存器信息
- 分析中断/异常的具体原因
- 执行对应的处理功能
- 恢复现场,返回被事件打断的程序
以设备输入输出中断为例:
- 打印机给CPU发中断信号,CPU处理完当前指令后检测到中断,判断出中断来源,并向相关设备发确认信号(硬件)
- CPU开始为软件处理中断做准备,处理器状态被切换到内核态,在系统栈中保存被中断程序的重要上下文环境,主要是程序计数器PC、程序状态字PSW(硬件)
- CPU根据中断码查中断向量表,获得与该中断相关的处理程序的入口地址,并将PC设置成该地址,新的指令周期开始时,CPU控制转移到中断处理程序(硬件)
- 中断处理程序开始工作,在系统栈中保存现场信息,检查I/O设备的状态信息,操纵I/O设备或者在设备和内存之间传送数据等等(软件)
- 中断处理结束时,CPU检测到中断返回指令,从系统栈中恢复被中断程序的上下文环境,CPU状态恢复成原来的状态,PSW和PC恢复成中断前的值,CPU开始一个新的指令周期(硬件)
2. 中断/异常机制实例
x86处理器:
- 中断:由硬件信号引发的,分为可屏蔽和不可屏蔽中断
- 异常:由指令执行引发的,比如除零异常,80x86处理器发布了大约20种不同的异常,对于某些异常,CPU会在执行异常处理程序之前产生硬件出错码,并压入内核态堆栈
- 系统调用:异常的一种,用户态到内核态的唯一入口
-
中断控制器(PIC或APIC)
- 负责将硬件的中断信号转换为中断向量,并引发CPU中断 实模式:中断向量表(Interrupt Vector)
- 存放中断服务程序的入口地址 保护模式:中断描述符表(Interrupt Descriptor Table)
- 采用门(gate)描述符数据结构表示中断向量 门描述符:描述控制转移的入口点
- 包括任务门(Task Gate),中断门(Interrupt Gate),陷阱门(Trap Gate),调用门(Call Gate) 中断门和陷阱门
- 指向段选择符,给出中断/异常程序的段内偏移量
- 通过中断门后系统会自动禁止中断,通过陷阱门则不会
- 确定与中断或异常关联的向量i
- 通过IDTR寄存器找到IDT表,获得中断描述符(表中第i项)
- 从GDTR寄存器获得GDT的地址,结合中断描述符中的段选择符,在GDT表获取对应的段描述符,从该段描述符中得到中断或异常处理程序所在的段基址
- 特权级检查:检查是否发生了特权级的变化,如果是,则进行堆栈切换(必须使用与新的特权级相关的栈)
- 硬件压栈,保存上下文环境,如果异常产生了硬件出错码,也将它保存在栈中
- 如果是中断,清IF位
- 通过中断描述符中的段内偏移量和段描述符中的基地址,找到中断/异常处理程序的入口地址,执行其第一条指令
三、系统调用机制
-
系统调用:用户在编程时可以调用的操作系统功能
- 系统调用是操作系统提供给编程人员的唯一接口
- 使CPU状态从用户态陷入内核态
- 每个操作系统都提供几百种系统调用(进程控制、进程通信、文件使用、目录操作、设备管理、信息维护等)
1. 系统调用机制工作原理
-
中断/异常机制
- 支持系统调用服务的实现 选择一条特殊指令:陷入指令(亦称访管指令)
- 引发异常,完成用户态到内核态的切换 系统调用号和参数
- 每个系统调用都事先给定一个编号(功能号) 系统调用表
- 存放系统调用服务例程的入口地址
将用户程序的参数传递给内核:
- 由陷入指令自带参数
- 通过通用寄存器传递参数(常用)
- 在内存中开辟专用堆栈区
高级语言视角:
#include <unistd.h>
int main() {
char string[5] = {‘H’, ‘e’, ‘l’, ‘l’, ‘o’, ‘!’, ‘\n’};
write(1, string, 7);
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
汇编语言视角:
.section .data
output:
.ascii “Hello!\n”
output_end:
.equ len, output_end - output
.section .text
.globl _start
_start:
movl $4, %eax
movl $1, %ebx
movl $output, %ecx
movl $len, %edx
int $0x80
end:
movl $1, %eax
movl $0, %ebx
int $0x80
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
当CPU执行到特殊的陷入指令时:
- 中断/异常机制:硬件保护现场,通过查中断向量表把控制权转给系统调用总入口程序
- 系统调用总入口程序:保存现场,将参数保存在内核堆栈里,通过查系统调用表把控制权转给相应的系统调用处理例程或内核函数
- 执行系统调用例程
- 恢复现场,返回用户程序
2. 基于x86处理器的Linux系统调用实例
-
陷入指令选择128号
- int $0x80 门描述符
- 系统初始化时:对IDT表中的128号门初始化
- 描述符的2、3两个字节:内核代码段选择符
- 0、1、6、7四个字节:偏移量(指向system_call())
- 门类型:陷阱门
- DPL:3,与用户级别相同
系统执行int $0x80指令:
- 由于特权级的改变,要切换栈,用户栈 → 内核栈,CPU从任务状态段TSS中装入新的栈指针(SS:ESP),指向内核栈
- 用户栈的信息(SS:ESP)、EFLAGS、用户态CS、EIP寄存器的内容压栈(返回用)
- 将EFLAGS压栈后,复位TF,IF位保持不变
- 用128在IDT中找到该门描述符,从中找出段选择符装入代码段寄存器CS
- 代码段描述符中的基地址+陷阱门描述符中的偏移量 → 定位system_call()的入口地址
中断发生后OS底层工作步骤:
- 硬件压栈:程序计数器等
- 硬件从中断向量装入新的程序计数器等
- 汇编语言过程保存寄存器值
- 汇编语言过程设置新的堆栈
- C语言中断服务程序运行(例:读并缓冲输入)
- 进程调度程序决定下一个将运行的进程
- C语言过程返回至汇编代码
-
汇编语言过程开始运行新的当前进程
本周重点概念
CPU状态,内核态/用户态,特权指令/非特权指令,中断,异常,中断响应,中断向量,中断处理程序,系统调用,陷入指令,系统调用号,系统调用表