大学学操作系统原理的时候,感觉进程和文件似乎是介绍最多的两块内容,但慢慢发现,要想理解清楚操作系统的工作机制,如果按知识学习的先后顺序排,中断应当是比较靠前的才对,只有理解了中断的机制,才有可能真正理解进程,文件系统,设备等等其他的概念。
中断实际上为OS里很多概念的具体实现提供了一个基本的保证,比如进程的调度、设备的访问、用户态和内核态的切换,各种异常的处理等等都需要中断的参与,甚至连对临界资源的安全访问也需要中断的支持,这足以说明中断是可以作为一个了解操作系统工作原理和具体机制的入口点的,而不仅仅只是作为相关书籍里独立的一个章节来理解。
1. 中断和异常的概念区别
异常(exception)是同步的事件,典型的比如处理器执行某条指令时发现出错了等等。
关于它们的区别有两点是需要注意的:
1)平常所说的屏蔽中断是不包括异常的,即异常不会因为CPU的IF位被清(关中断,指令:cli)而受影响,比如缺页异常,即使关了中断也会触发CPU的处理。
2)通常说的int 80h这种系统调用使用的中断方式实际上硬件上是理解为异常处理的,因此也不会被屏蔽掉,这也很好理解,int 80h这种中断方式是程序里主动触发的,对于CPU来说属于同步事件,因此也就属于异常的范畴。
2. 中断(异常)处理过程
当CPU收到中断或者异常的信号时,它会暂停执行当前的程序或任务,通过一定的机制跳转到负责处理这个信号的相关处理程序中,在完成对这个信号的处理后再跳回到刚才被打断的程序或任务中。这里只描述保护模式下的处理过程,搞清楚了保护模式下的处理过程(更复杂),实模式下的处理机制也就容易理解了。
具体的处理过程如下:
0)中断响应的事前准备:
系统要想能够应对各种不同的中断信号,总的来看就是需要知道每种信号应该由哪个中断服务程序负责以及这些中断服务程序具体是如何工作的。系统只有事前对这两件事都知道得很清楚,才能正确地响应各种中断信号和异常。
[a] 系统将所有的中断信号统一进行了编号(一共256个:0~255),这个号称为中断向量,具体哪个中断向量表示哪种中断有的是规定好的,也有的是在给定范围内自行设定的。
中断向量和中断服务程序的对应关系主要是由IDT(中断向量表)负责。操作系统在IDT中设置好各种中断向量对应的中断描述符(一共有三类中断门描述符:任务门、中断门和陷阱门),留待CPU查询使用。而IDT本身的位置是由idtr保存的,当然这个地址也是由OS填充的。
下面的示意图显示了IDT的基本结构和IDTR是如何指示IDT的位置和长度的:
[b] 中断服务程序具体负责处理中断(异常)的代码是由软件,也就是操作系统实现的,这部分代码属于操作系统内核代码。也就是说从CPU检测中断信号到加载中断服务程序以及从中断服务程序中恢复执行被暂停的程序,这个流程基本上是硬件确定下来的,而具体的中断向量和服务程序的对应关系设置和中断服务程序的内容是由操作系统确定的。
1)CPU检查是否有中断/异常信号
对于异常和系统调用那样的软中断,因为中断向量是直接给出的,所以和通过IRQ(中断请求)线发送的硬件中断请求不同,不会再专门去取其对应的中断向量。
2)根据中断向量到IDT表中取得处理这个向量的中断程序的段选择符
3)根据取得的段选择符到GDT中找相应的段描述符
如下图显示了从中断向量到GDT中相应中断服务程序起始位置的定位方式:
4)CPU根据特权级的判断设定即将运行的中断服务程序要使用的栈的地址
6)保护当前程序的现场
官方文档[1]给出的栈变化的示意图如下:
7)跳转到中断服务程序的第一条指令开始执行
8)中断服务程序处理完毕,恢复执行先前中断的程序
这里有个地方需要注意:如果此次处理的是带有错误码(errorCode)的异常,CPU在恢复先前程序的现场时,并不会弹出errorCode,也就是说CPU似乎忘记了曾经压过一个errorCode入栈,因此要求相关的中断服务程序在调用iret返回之前需要主动弹出errorCode。
参考书目:
[1] Intel 64 and IA-32 Architectures Software Developers Manual Volume 1 Basic Architecture
[2] 《微型计算机接口技术及应用》,华中科技大学出版社,刘乐善 主编
[3] 《深入理解Linux内核》,第三版,中国电力出版社