中断、异常和系统调用

这三者都是应用程序和操作系统内核之间的接口。

作用和区别

硬件外设与计算机的交互的时候,为了快速响应,需要中断机制。

程序执行中出现了一些程序设计人员没有预料到的情况,如除0,导致当前指令执行失败,为了解决这种错误,需要异常处理机制。

在保证系统安全性的同时为用户应用程序提供灵活的访问接口,需要系统调用机制。应用程序主动向OS发出服务请求

响应和处理

中断:异步,持续进行,应用程序并不会感知到中断的存在,对用户透明

异常:同步,必须当前错误处理完了才能继续执行,处理方式是杀死或重新执行指令

系统调用:同步或异步,等待和持续

中断处理机制

实际上是对三种方式的统称。

硬件处理

  • CPU初始化时设置中断使能标志
  • 依据内部或外部事件设置中断标志
  • 依据中断向量调用相应中断服务例程
    • 中断:设备驱动
    • 异常:异常服务例程
    • 系统调用:系统调用表 -> 系统调用实现

此时三种情况都会索引中断向量表,根据自身的所需的处理转到不同的例程,及软件处理

内核软件处理

  • 现场保存(编译器): 汇编语言过程保存寄存器值并设置新的堆栈
  • 中断服务处理(服务例程)
  • 清除中断标记(服务例程)
  • 现场恢复(编译器)

中断嵌套

硬件中断服务例程可被打断,中断请求会保持到CPU做出响应

  • 不同中断源优先级不同,比如有更高速设备的请求
  • 有时需要临时禁止中断请求,比如电源处理

异常服务例程可被打断

  • 硬件中断优先级更高,如执行缺页处理时有磁盘I/O中断
  • 异常本身也可嵌套,如缺页处理时又出现缺页

系统调用与函数调用

系统调用:INTIRET 指令 ,调用时会有堆栈和特权级的切换,因此开销更大,具体开销有:

  • 切换引导机制
  • 建立内核堆栈
  • 验证参数
  • 内核态映射到用户态的地址空间
  • 内核态独立地址空间

函数调用:CALLRET 指令,没有堆栈切换

堆栈切换的意思是用户态和内核态使用不同的堆栈


基于X86的中断处理

中断和异常在不同的CPU上有不同的表现形式,X86分成中断和异常两种类型,但在实现方式上是统一的

中断源

中断

  • 外部中断:串口、硬盘、网卡、时钟…
  • 软件中断:THE INT n 指令,通常用于系统调用(陷入)

异常

  • 程序错误
  • 软件异常:INTO INT 3 BOUND
  • 机器检查出的异常

CPU与OS的中断处理

每个中断或异常关联一个中断服务例程ISR, 关系存储在中断描述符表IDT中,IDT的起始地址和大小保存在中段描述符表寄存器IDTR中

1. 确定ISR的地址(中断初始化)
  • CPU在执行完当前程序的每一条指令后,都会去确认在执行刚才的指令过程中中断控制器(如:8259A)是否发送中断请求过来,如果有那么CPU就会在相应的时钟脉冲到来时从总线上读取中断请求对应的中断向量;
  • CPU根据得到的中断向量(以此为索引)到IDT中找到该向量对应的中断描述符,中断描述符里保存着中断服务例程的段选择子
  • CPU使用IDT查到的中断服务例程的段选择子从GDT中取得相应的段描述符,段描述符里保存了中断服务例程的段基址和属性信息,此时CPU就得到了中断服务例程的起始地址,并跳转到该地址
2. 切换到ISR

段描述符中会设定ISR的特权级,如CS的低两位00 代表内核态,33 代表用户态

产生中断之后都会转为内核态,对于内核态 -> 内核态 与 用户态 -> 内核态 处理方式略有区别,前者只需压入EIP CS EFLAGS;后者有堆栈和特权级的切换,因此还要把用户态的堆栈地址ESP SS压到内核态堆栈

需要理解的一点是,程序的状态,实际上就是各个寄存器中的值,保存了这些值也就保存了程序的状态。

具体流程参见中断处理

保护端点和保护现场的区别

这是一个困扰了我很久的问题,硬件处理中的保护断点与软件处理中的保护现场究竟有什么区别?

保护断点:由系统自动完成,方便中断服务程序执行完后,可以返回到断点处继续运行。这里的断点指的是PC寄存器的内容,因为转入中断服务程序需要装载新的指令地址,一般是压栈。

保护现场:指的是进入中断服务程序或子程序后,由于寄存器有限,主程序和中断服务程序或子程序中用到相同的寄存器,所以为防止冲突,在中断服务程序前或在子程序前用进栈指令保护那些可能受到冲突的寄存器,然后在返回前恢复。也就是说,一个程序的运行状态,是用各种寄存器中的值来描述的,现场信息一般指的是PSW 、中断屏蔽寄存器和CPU 中某些寄存器的值