操作系统运行环境与机制

操作系统运行环境与机制

📖 回顾:操作系统的主要工作

  • 程序的执行 启动程序、执行程序以及程序结束的工作
  • 完成与体系结构相关的工作
  • 完成应用程序所需的共性服务(提供各种基本服务,读盘、申请内存等等)
  • 性能、安全、健壮等问题

应用程序

———————— 虚拟机器界面 ---> 操作系统运行机制(系统调用)

操作系统

———————— 物理机器界面 ---> 操作系统运行环境(CPU状态、中断/异常机制)


1 处理器状态(模式) #

1.1 中央处理器(CPU) #

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

1.2 控制和状态寄存器 #

  1. 程序计数器(PC: Program Counter):记录将要取出的指令的地址
  2. 指令寄存器(IR: Instruction Register):记录最近取出的指令
  3. 程序状态字寄存器(PSW: Program Status Word):记录处理器的运行状态如条件码、 模式、控制位等信息。PSW一般包含以下内容:
    • 程序基本状态:

      • 等待/计算:当前处理器状态;
      • 目态/管态:当前处理器状态;
      • 条件码:体现当前指令执行结果的各种状态信息(如算术运算产生的正、负、零或溢出等);
      • 指令地址:下一条指令的存放地址
    • 中断码:保存程序执行时当前发生的中断事件;

    • 中断屏蔽位:指出程序执行中,是否要响应出现的中断事件。

Reference: 程序状态字

控制和状态寄存器的作用:

  1. 用于控制处理器的操作
  2. 在某种特权级别下可以〔被〕访问、修改

1.3 操作系统的需求——保护 #

  • 从操作系统的特征考虑
    • 并发、共享的数据安全
  • 对操作系统提出要求 ——> 实现保护与控制
  • 需要硬件提供基本运行机制:
    • 处理器具有特权级别,能在不同的特权级运行的不同指令集合
    • 硬件机制可将OS与用户程序隔离

1.4 处理器模式/状态(MODE) #

  • 现代处理器通常将CPU状态设计划分为两种、三种或四种
    • 操作系统〔至少〕需要两种CPU状态
      • 内核态(Kernel Mode):运行操作系统程序
      • 用户态(User Mode):运行用户程序
  • 程序状态字寄存器PSW中专门设置一位,根据运行程序对资源和指令的使用权限而设置不同的CPU状态

Program Status Word - OSDev Wiki

例:x86架构中的EFLAGS寄存器中使用2位来保存IO的权限级别(IOPL)

x86—EFLAGS寄存器详解_随心随意随缘的博客-CSDN博客

1.5 特权指令和非特权指令 #

  • 特权(privilege)指令:只能由操作系统使用、用户程序不能使用的指令
  • 非特权指令:用户程序可以使用的指令

📖 下列哪些是特权指令? 哪些是非特权指令?

特权指令:启动I/0 内存清零 修改程序状态字 设置时钟 允许/禁止中断 停机

非特权指令:控制转移 算术运算 取数指令 访管指令(可以使用户程序从用户态陷入操作系统内核态。)

1.6 实例:x86系列处理器 #

  • x86支持4个处理器特权级别

    特权环:R0、R1、R2和R3

    R0 是希望能运行操作系统的一些关键代码 所以 R0 相当于内核态。 R1是运行设备驱动程序和一些 I/O 处理的历程。 R2是运行一些受保护共享的代码,比如说一些语言编译环境。 R3是给用户程序使用。

    • 从R0到R3,特权能力由高到低
    • R0相当于内核态;R3相当于用户态;R1和R2则介于两者之间
    • 不同级别能够运行的指令集合不同
  • 目前(?2015?)大多数基于x86处理器的操作系统只使用了R0和R3两个特权级别

1.7 CPU状态之间的转换 #

  • 用户态 —> 内核态 :唯一途径:中断/异常/陷入机制
  • 内核态 —> 用户态:设置程序状态字 PSW

📖 访管指令(陷入指令):

提供给用户程序的接口,用于调用操作系统的功能(服务)。 如:inttrapsyscallsysenter/sysexit

2 中断与异常机制 #

中断/异常对于操作系统的重要性就好比:汽车的发动机、飞机的引擎。操作系统是由“中断驱动”或者“事件驱动”的。中断与异常机制主要用来:1)及时处理设备发来的中断请求,2)可使OS捕获用户程序提出的服务请求防止用户程序执行过程中的破坏性活动。3)...

中断与异常可以概括为:1)CPU对系统发生的某个事件作出的一种反应,2)CPU暂停正在执行的程序,保留现场后自动转去执行相应事件的处理程序,处理完成后返回断点继续执行被打断的程序。

中断与异常的特点:

  • 随机发生
  • 自动处理— 硬件自动完成这一过程
  • 可恢复

中断(外中断):外部事件,正在运行的程序所不期望发生的

  • I/O中断:等待输入、扫描、接收到网络包等等
  • 时钟中断:CPU时间片结束、定时器到时等等
  • 硬件故障:电池即将耗尽等等

异常(内中断):由正在运行的指令引发

  • 系统调用
  • 页故障/页错误 (缺页异常—文件未读入内存)
  • 保护性异常(磁盘不可写等等)
  • 断点指令(程序调试等等)
  • 其他程序性异常(算术溢出,内存溢出等等)
类别原因异步/同步返回行为
中断 Interrupt来自I/O设备、其他硬件部件异步总是返回到下一条指令
陷入 Trap有意安排同步返回到下一条指令
故障 Falut可恢复的错误同步返回到当前指令
终止 Abort不可恢复的错误同步不会返回

2.1 为什么引入中断与异常 #

  • 中断的引入:为了支持CPU和设备之间的并行操作。

    当CPU启动设备进行输入/输出后,设备便可以独立工作,CPU转去处理与此次输入/输出不相关的事情;当设备完成输入/输出后,通过向CPU发中断报此次输入/输出的结果,让CPU决定如何处理以后的事情。

  • 异常的引入:表示CPU执行指令时本身出现的问题。

    如算术溢出、除零、取数时的奇偶错,访存地址时越界或执行了“陷入指令”等,这时硬件改变了CPU当前的执行流程,转到相应的错误处理程序或异常处理程序或执行系统调用。

2.2 原理 #

  • 中断/异常机制是现代计算机系统的核心机制之一。

    硬件和软件相互配合而使计算机系统得以充分发挥能力。

  • 硬件该做什么事?--中断/异常响应

    捕获中断源发出的中断/异常请求,以一定方式响应,将处理器控制权交给特定的处理程序。

  • 软件要做什么事?--中断/异常处理程序

    识别中断/异常类型并完成相应的处理。

2.2.1 中断响应 #

发现中断,接收中断的过程。由中断硬件部件完成。

处理控制器部件中设有中断寄存器

处理控制器是计算机系统中的一个重要组成部分,它由硬件和软件两部分组成。硬件部分包括处理器、寄存器、高速缓存等,软件部分包括操作系统和应用程序。处理控制器的主要功能是处理和控制计算机系统中的数据流和输入输出,以及解决性能、安全、健壮等问题。在计算机系统中,处理控制器是负责管理和协调各个硬件设备和软件应用程序之间通信和数据传输的关键部件。它可以通过操作系统运行机制(系统调用)和物理机器界面来与操作系统和硬件进行交互。

中断寄存器是计算机中一种特殊的寄存器,用于存储中断处理程序的返回地址和处理状态。

当计算机遇到中断事件时,处理器会自动将当前程序的执行状态保存到堆栈中,并将处理器的控制权转移给中断处理程序。当中断处理程序完成后,处理器会从堆栈中恢复原程序的执行状态,并将控制权返回给原程序继续执行。

在x86架构中,中断寄存器被称为EFLAGS寄存器,它包含了处理器的一些状态信息,如进位标志、零标志和符号标志等。此外,EFLAGS寄存器还包含了中断标志位(IF)。当IF标志位被设置为1时,处理器允许中断事件的发生,否则处理器会忽略所有中断事件。这个标志位可以被操作系统用来控制中断的开关。

总之,中断寄存器是计算机中非常重要的一个硬件寄存器,它是中断处理机制的核心。中断处理机制可以使计算机在运行过程中能够及时响应外部事件,提高计算机的效率和稳定性。

interrupt

中断响应的简单过程

2.2.2 中断向量表 #

  • 中断向量

    一个内存单元,存放 中断处理程序入口地址和程序运行时所需的处理机状态字。

interrupt-vetor

中断向量

执行流程:按中断号/异常类型的不同,通过中断向量表转移控制权给中断处理程序。

📖 Linux中的中断向量表

向量范围用途
0~19不可屏蔽中断和异常
0除0
1单步调试
4算术溢出
6非法操作数
12栈异常
13保护性错误
14缺页异常
20~31Intel保留
32~127外部中断(IRQ)
128(0x80)用于系统调用的可编程异常
129~238外部中断
239本地APIC时钟中断
240本地APIC高温中断
241~250Linux保留
251~253处理器间中断
254本地APIC错误中断
255本地APIC伪中断

2.2.3 中断处理程序 #

设计操作系统时,为每一类中断/异常事件编好相应的处理程序,并设置好 中断向量表

系统运行时若响应中断,中断硬件部件将CPU控制权转给中断处理程序:

  • 保存相关寄存器信息
  • 分析中断/异常的具体原因
  • 执行对应的处理功能
  • 恢复现场,返回被事件打断的程序

2.2.4 中断/异常处理流程 #

interrupt-handle-flow

中断处理流程

中断/异常机制小结:以设备输入输出中断为例:

  • 打印机给CPU发中断信号
  • CPU处理完当前指令后检测到中断,判断出中断来源并向相关设备发确认信号
  • CPU开始为软件处理中断做准备:
    • 处理器状态被切换到内核态
    • 在系统栈中保存被中断程序的重要上下文环境,主要是程序计数器PC程序状态字PSW
  • CPU根据中断码查中断向量表,获得与该中断相关的处理程序的入口地址,并将PC设置成该地址,新的指令周期开始时,CPU控制转移到中断处理程序
  • 中断处理程序开始工作
    • 在系统栈中保存现场信息
    • 检查I/0设备的状态信息,操纵I/O设备或者在设备和内存之间传送数据等等

      📖 中断处理程序的处理,通常分为两类:

      • I/O操作正常结束
        • 若有程序正等待此次I/O的结果,则应将其唤醒
        • 若要继续I/O操作,需要准备好数据重新启动I/O
      • I/O操作出现错误
        • 需要重新执行失败的I/O操作
        • 重试次数有上限,达到时系统将判定硬件故障
  • 中断处理结束时,CPU检测到中断返回指令,从系统栈中恢复被中断程序的上下文环境,CPU状态恢复成原来的状态,PSW和PC恢复成中断前的值,CPU开始一个新的指令周期

2.3 x86中的中断与异常机制 #

  • 中断

    由硬件信号引发的,分为可屏蔽和不可屏蔽中断

  • 异常

    • 由指令执行引发的,比如除零异常
    • 80x86处理器发布了大约20种不同的异常
    • 对于某些异常,CPU会在执行异常处理程序之前产生硬件出错码,并压入内核态堆栈
  • 系统调用

    异常的一种,用户态到内核态的唯一入口

  • 中断控制器(PIC或APIC)

    负责将硬件的中断信号转换为中断向量,并引发CPU中断

  • 实模式:中断向量表(Interrupt Vector)

    • 存放中断服务程序的入口地址
      • 入口地址 = 段地址左移4位 + 偏移地址
      • 不支持CPU运行状态切换
      • 中断处理与一般的过程调用相似
  • 保护模式:中断描述符表(IDTInterrupt Descriptor Table)

    采用门(gate)描述符数据结构表示中断向量

2.3.1 中断门描述符表 #

四种类型门描述符:

  • 任务门(Task Gate)
  • 中断门(Interrupt Gate) 给出段选择符(Segment Selector)、中断/异常程序的段内偏移量(Offset) 通过中断门后系统会自动禁止中断(通过设置寄存器的中断标识位IF完成)
  • 陷阱门(Trap Gate) 与中断门类似,但通过陷阱门后系统不会自动禁止中断
  • 调用门(Call Gate)

2.3.2 中断/异常的硬件处理过程 #

  1. 确定与中断或异常关联的向量i

  2. 通过IDTR寄存器找到IDT表,获得中断描述符(表中的第i项)

  3. 从GDTR寄存器获得GDT的地址;结合中断描述符中的段选择符,在GDT表获取对应的段描述符;从该段描述符中得到中断或异常处理程序所在的段基址

    IDT段选择符→ GDT段描述符→ GDT段基址

  4. 特权级检查(2、3步中包含特权级检查)

  5. 检查是否发生了特权级的变化,如果是,则进行堆栈切换(必须使用与新的特权级相关的栈)

  6. 硬件压栈,保存上下文环境;如果异常产生了硬件出错码,也将它保存在栈中

  7. 如果是中断门,清IF位(中断标识位),陷进门不必如此做

  8. 通过中断描述符中的段内偏移量和段描述符中的基地址,找到中断/异常处理程序的入口地址,执行其第一条指令

中断异常硬件处理流程

x86的中断/异常的硬件处理过程

3 系统调用机制 #

应用程序通过访管指令(或中断/异常机制)来实现系统调用。

3.1 系统调用的概念 #

用户在编程时可以调用的操作系统功能。

  • 系统调用(System Call)是操作系统提供给编程人员的唯一接口
  • 使CPU状态从用户态陷入到内核态
  • 每个操作系统都提供了几百种系统调用(进程控制、进程通信、文件使用、目录操作、设备管理、信息维护等等)
系统调用

系统调用与库函数、API、内核函数的区别与联系

用户程序往往通过调用函数来进行系统调用,进入内核。

3.2 系统调用的设计 #

3.2.1 系统调用的要素 #

  1. 中断/异常机制

    支持系统调用服务的实现。

  2. 选择一条特殊指令:陷入指令(亦称访管指令)

    引发异常,完成用户态到内核态的切换 。

    所有的系统调用都使用这条指令。

  3. 系统调用号和 参数
    每个系统调用都事先给定一个编号(功能号) 。

    通过编号(功能号)来区分不同的系统调用

  4. 系统调用表

    存放系统调用服务例程的入口地址。

3.2.2 参数的传递 #

❓ 怎样实现用户程序的参数传递给内核?

常用的3种实现方法:

  • 由陷入指令自带参数:
    • 陷入指令的长度有限,且还要携带系统调用功能号,只能自带有限的参数。
  • 通过通用寄存器传递参数(常用)
    • 这些寄存器是操作系统和用户程序都能访问的,但寄存器的个数会限制传递参数的数量。
  • 在内存中开辟专用堆栈区来传递参数

3.2.3 系统调用示例分析 #

 1#include <unistd.h>
 2int main(){
 3	char string[5] = {'H', 'e', 'l', 'l', 'o', '!', '\n'};
 4	write(1, string, 7);    // 系统调用1
 5	return 0              // 系统调用2
 6}
 7
 8// 汇编语言编译后的代码(示例)
 9.section .data
10output:
11	ascii "Hello!\n"
12output_end:
13	.equ len, output_end-output
14
15.section .text
16.globl_start
17_start:
18	movl $4, %eax         # eax寄存器存放系统调用编号
19	movl $1, %ebx         # 通用寄存器传递参数
20	movl $output, %ecx    # 参数
21	movl $len,%edx        # 参数
22	int $0x80             # 陷入指令,引发一次系统调用
23end:
24	movl $1, %eax         # 以下是return 0
25	movl $4, %ebx
26	int $0x80

3.3 系统调用的执行过程 #

当CPU执行到特殊的陷入指令时:

  1. 中断/异常机制
    • 硬件保护现场,通过查询中断向量表(描述符表),把CPU的控制权转交给中断处理程序,或者叫系统调用总入口程序。
  2. 系统调用总入口程序
    • 保存现场,将系统调用参数保存在内核的堆栈中,查找系统调用表,把控制权交给对应的系统调用的处理程序或者内核函数
  3. 执行系统调用例程
  4. 恢复现场,返回用户程序

4 基于x86的Linux系统调用机制简介 #

  • 陷入指令选择128号
    • int $0x80
  • 门描述符
    • 系统初始化时:对IDT表中的128号门初始化
    • 门描述符的2、3两个字节:内核代码段选择符 0、1、6、7四个字节:偏移量(指向system_call())
    • 门类型:15 陷井门
    • DPL:3 与用户级别相同,允许用户使用该门描述符

📖 系统调用号示例

#define_NR_exit1
#define_NR_fork2
#define_NR_read3
#define_NR_write4
#define_NR_open5
#define_NR_close6
#define_NR_waitpid7
#define_NR_create8
#define_NR_link9
#define_NR_unlink10
#define_NR_execve11
#define_NR_chdir12
#define_NR_time13

系统执行INT $0x80指令后:

  • 由于特权级的改变,要切换栈 用户栈→内核栈 CPU从任务状态段TSS中装入新的栈指针(SS:ESP),指向内核栈。
  • 用户栈的信息(SS:ESP)、EFLAGS、用户态CS、EIP寄存器的内容压栈(返回用)
  • 将EFLAGS压栈后,复位TF,IF位保持不变
  • 用128在IDT中找到该门描述符,从中找出段选择符装入代码段寄存器CS
  • 代码段描述符中的基地址+陷阱门描述符中的偏移量 →定位 system_call()的入口地址

Linux系统调用执行流程

x86的Linux系统调用机制
  1. 用户态下调用C库的库函数,如write()
  2. 封装后的write()先做好参数传递工作,然后使用int 0x80产生一次异常
  3. CPU通过0x80号在IDT中找到对应的服务例程system_call(),调用之
    1. 陷入内核态
    2. 压栈
    3. 查询系统调用表,调用内核函数
  4. 执行完成后,通过ret_from_sys_call返回用户例程

中断发生后linux硬件底层的压栈顺序

中断发生后linux硬件底层的压栈顺序

中断发生后,OS底层的工作步骤:

  1. 硬件压栈:程序计数器等
  2. 硬件从中断向量装入新的程序计数器等
  3. 汇编语言过程保存寄存器值
  4. 汇编语言过程设置新的堆栈
  5. C语言中断服务程序运行(例:读并缓冲输入)
  6. 进程调度程序决定下一个将运行的进程
  7. C语言过程返回至汇编代码
  8. 汇编语言过程开始运行新的 当前进程

5 本章重点 #

  • 理解计算机系统的保护机制
    • 掌握处理器状态
    • 掌握特权指令与非特权指令
  • 掌握中断/异常机制
    • 掌握中断/异常的基本概念
    • 理解中断/异常机制的工作原理
  • 掌握系统调用机制
    • 掌握系统调用设计原理
    • 掌握系统调用执行过程
  • 重点阅读教材
    • 第1章相关内容:1.3、1.6
    • 第2章第52页图2-5及说明该图思路的段落
  • 重点概念