中断
概念
操作系统是中断驱动的,整个操作系统离不开中断机制,所谓的中断机制,宏观上讲就是cpu在执行某件事情时可以暂停执行当前的任务而转去执行其他任务的能力。
分类
整个中断体系的示意图如下所示:
如图所示,三种类型的中断流程也在图中用三种颜色的箭头进行了标注。
-
中断
中断流程如图中红色箭头所示,中断指来源于处理器之外的硬件设备的中断事件,例如键盘输入或者网卡数据已满请求处理数据等,外设会请求中断并查询中断向量表或中断描述表(实模式下为向量表,保护模式下为描述表,其实本质没有什么差别,都存储着索引->系统调用类型的映射关系),找到对应驱动由其对外设进行操作。中断也有可屏蔽中断与不可屏蔽中断之分,通过两条引脚引到CPU上,两种中断分别从不同引脚传入cpu。所谓的不可屏蔽中断就是那些无法继续运行下去的中断,例如断电,例如内存读写出错。可屏蔽中断可通过设置EFLAG寄存器中的IF标志位选择是否响应中断。
-
异常
异常流程如图中橘黄色箭头所示,指当前运行指令引起的中断事件,例如地址越界访问,读区域进行写操作,缺页异常,算数溢出,硬件异常(cpu内部异常),这些异常都有由操作系统中的异常服务处理。
-
系统调用
系统调用如图中绿色箭头所述,用户调用的函数会主动的陷入内核,在linux中由int 0x80这样的一条指令主动陷入内核,内核函数可以有无数中,但是都由一个入口进入系统调用,在系统调用表上根据参数不同进行具体的调用服务选择。系统调用的中断与硬件无关,属于自愿性中断事件,应用程序主动向操作发出的服务请求,语义为请求os的服务。
中断源
- 中断:cpu以外的硬件设备,例如键盘,网卡等
- 异常:应用程序意想不到的行为,如对只读区域进行写操作,所读取的数据不在内存中等
- 系统调用:应用程序请求内核服务,例如read,write函数
响应方式
- 中断:异步
- 异常:同步
- 系统调用:异步、同步
中断响应过程
-
发现中断源,提出中断请求
1.1 访问中断寄存器,查看是否有中断记录
1.2 判断中断是否被屏蔽
1.3 更具中断优先级决定优先执行哪一个中断
-
中断当前程序的执行
2.1 保存当前程序执行的状态
转向操作系统的中断处理程序
系统调用
因为系统调用在中断比较特殊,作为开发也经常会遇到系统调用这个概念,所以这里特别解释西安系统调用
概念:System Call,指一些0特权级才能操作的函数。
区分:系统调用、库函数、API、内核函数
应用程序可以直接进行系统调用,但这种情况较少,一般都是通过一层库函数进行封装。由库函数去陷入内核进行系统调用,系统调用就是去调用一些内核函数,可能是单个内核函数,也可能是多个内核函数的封装,此外,很多内核函数是不允许外界调用。
实现系统调用的基础
- 中断异常机制
- 设计一条特殊指令,使得用户态和内核态得以切换(在linux中为 int 0x80)
- 设置系统调用号以及参数,因为所有的系统调用都从同一个入口进入内核,所以需要额外的编号对这些调用加以区分。参数一般放在栈上,但是用户态的栈是没法传递到内核态的,所以参数传递这块,以linux为例,是通过将参数放在通用寄存器上传递的。
- 初始化系统调用表,用于存放各个调用服务的入口地址
特殊指令 int 0x80
这里的int不是在高级语言中的那个整数int,int是一个指令。以x86 CPU手册为例:
INT n/INTO/INT 3—Call to Interrupt Procedure
The INT n instruction generates a call to the interrupt or exception handler specified with the destination operand. The destination operand specifies a vector from 0 to 255, encoded as an 8-bit unsigned intermediate value. The INT n instruction is the general mnemonic for executing a software-generated call to an interrupt handler.
INT n指令集通常会调用一个具体的中断、异常的handler,该handler的目标向量值范围是8位无符号整数的范围即0-255,INT n指令通常用于执行一个软中断。
翻译很别扭,说白了INT n指令会跳转到n这个值所代表的中断操作中,在linux中0x80代表着系统调用,所以 INT 0x80会使得应用程序主动陷入内核,根据参数查询具体的执行函数,再执行该函数。
至于linux源码中对这些参数的设置,在文末给出的参考链接中有这样的分析,可以自己跟着源码走一遍,还是比较清晰的,这里就不到处截图演示各种参数设置了。
Sample
#include <unistd.h>
int main(){
char string[5] = {'H','i','H','i','H','i','i'};
write(1,string,7);//高级语言中的write会翻译成以下汇编
return 0;
}
_start:
movl $4,%eax #eax寄存器存放系统调用号,这里write函数的系统调用编号为4
movl $1,%ebx
movl $output,%ecx
movl $len,%edx
int $0x80 #linux中通过int0x80陷入内核,进行系统调用
end:
movl $1,%eax #exit的系统调用号为1
movl $0,%ebx #设置返回值
int $0x80 #陷入内核,退出函数执行
系统调用过程详解
- 中断异常机制:硬件保护现场,查询中断向量表,cpu控制权转移至系统调用总入口程序。
- 系统调用总入口程序:保存现场,把参数保存到内核的堆栈中,查询系统调用表,将cpu控制权转交给相应内核函数
- 执行系统调用例程
- 恢复现场,返回用户进程
参考资料: