中断理论部分
中断是硬件和软件交互的一种机制,可以说整个操作系统,整个架构都是由中断来驱动的。中断的机制分为两种,中断和异常,中断通常为 设备触发的异步事件,而异常是
执行指令时发生的同步事件。本文主要来说明
外设触发的中断,总的来说一个中断的起末会经历设备,中断控制器,
&
三个阶段:设备产生中断,中断控制器接收和发送中断,
&
来实际处理中断。
本文来捋一捋中断需要知道的一些理论知识,主要也是从这三个阶段来说,实际两个阶段,其中第一个阶段设备如何产生信号不讲,超过了操作系统的范围,也超过了我的能力范围。不过在讲解各个硬件比如说磁盘,串口时会涉及到一些这些硬件何时会触发中断。各种硬件外设有着自己的执行逻辑,有各种形式的中断触发机制,比如边沿触发,电平触发等等。总的来说就是向中断控制器发送一个中断信号,中断控制器再作翻译发送给
,
再执行中断服务程序对中断进行处理。
说到中断控制器,是个什么东西?中断控制器可以看作是中断的代理,外设是很多的,如果没有一个中断代理,外设想要给 发送中断信号来处理中断,那只能是外设连接在
的管脚上,
的管脚是很宝贵的,不可能拿出那么多管脚去连接外设。所以就有了中断控制器这个中断代言人,所有的
外设连接其上,发送中断请求时就向中断控制器发送信号,中断控制器再通知
,如此便解决了上述问题。
中断控制器的发展可分为 和
两个阶段,前者适用于单处理器,在单处理器的时代叱咤风云,风靡全球,不过到了现代的多处理器时代不行了,渐渐地被更高级的
所代替。但
还是值得了解了解的,来看看
中断控制器 PIC
[站外图片上传中...(image-5e429c-1704199812111)]
[站外图片上传中...(image-aa97b0-1704199812111)]
这就是中断控制器 芯片,我们只需要了解:
-
,8 个引脚,每个引脚可以连接一个外设,也就是说一个
能支持 8 个中断。
-
通过
,
(
) 与
(
)通信
一个 8259A 芯片只能支持 8 个中断,的确有点少了,所以有了级联:
<img src="https://cdn.jsdelivr.net/gh/Rand312/Rand_Picture_System@main/xv6/8259级联.131orxsswt68.png" style="zoom:50%;" />
所谓级联,就是将多个 给串起来,通常方式为将一个
的引脚分出来连接另一个
,就如上图所示。一般两个
级联的时候将主
的
分配出去连接从
。
的引脚
是有优先级的,编号越低,优先级越高,
,前 32 个是留给异常使用和架构保留的。
最重要的几个寄存器如下所示:
寄存器
-
,
,共 8 位,对应
8 个中断引脚。当某个引脚接收到中断信号时,
中相应的位 置 1,表示
已收到该设备的中断请求但还未交由
处理
-
,
,共 8 位,同样的每位表示对应引脚表示的中断,当
中某个中断请求提交给
时,IRR 相应的位被清 0,
相应的位就被置 1,表示
正处理该中断。
-
,
,共 8 位,同样的每位表示对应引脚表示的中断,某位置 1 表示屏蔽相应的中断,清 0 表示允许相应的中断。
-
,
,优先级仲裁寄存器,有多个中断同时发生时,它能找出哪个中断优先级最高
-
,
,在
里面
不是一个寄存器,它是操作命令寄存器中的一个
,写
表示中断结束。
还有其他的寄存器比如初始化命令寄存器,操作命令寄存器,这里我们后面讲述
并不会使用
,所以这里也就不说明了,最后来看看通过
的中断流程:
PIC 中断流程
-
引脚接收到中断信号,若该中断没有被屏蔽,那么
中相应的位置 1
-
通过
向
发送
信号
-
通过
引脚发送应答信号给
-
收到应答信号后,将
中最高优先级的中断相应的位清 0,将
相应的位置 1
-
再次发送
信号给
,
收到后将
送到数据线
-
根据
索引
中的门描述符,执行中断服务程序
- 中断处理完成之后写
,将
中相应的位清 0 表示中断完成
高级中断控制器 APIC
上述就是中断控制器 的内容,
PIC
只用于单处理器,对于如今的多核多处理器时代,PIC
无能为力,所以出现了更高级的中断控制器 APIC
,APIC
(
) 高级可编程中断控制器,
APIC
分成两部分 LAPIC
和 IOAPIC
,前者 LAPIC
位于 内部,每个
都有一个
LAPIC
,后者 IOAPIC
与外设相连。外设发出的中断信号经过 IOAPIC
处理之后发送给 LAPIC
,再由 LAPIC
决定是否交由 进行实际的中断处理。
[站外图片上传中...(image-86962c-1704199812111)]
可以看出每个 上有一个
LAPIC
,IOAPIC
是系统芯片组一部分,各个中断消息通过总线发送接收。关于 APIC
的内容很多也很复杂,详细描述的可以参考 开发手册卷三,本文不探讨其中的细节,只在上层较为抽象的层面讲述,理清
APIC
模式下中断的过程。
下面就分别来看看 和
:
IOAPIC
主要负责接收外部的硬件中断,将硬件产生的中断信号翻译成具有一定格式的消息,然后通过总线将消息发送给一个或者多个 LAPIC。
主要组成如下:
- 24 个中断管脚,一个
支持 24 个中断
- 一张 24 项的中断重定向表(
,
),每个表项都是一个 64 位的寄存器
- 一些可编程寄存器,例如窗口寄存器,版本寄存器等等
- 通过
总线发送和接收
信息的一个信息单元
我们了解一个硬件主要就是了解它的寄存器, 有两个内存映射的寄存器,
和
寄存器,通过
的方式访问
的其他寄存器。
其他寄存器一览:
[站外图片上传中...(image-574ba4-1704199812111)]
内存映射的两个寄存器
[站外图片上传中...(image-d4823a-1704199812111)]
这两个寄存器是内存映射的,IOREGSEL
,地址为 ;
IOWIN
,地址为 。
IOREGSEL
用来指定要读写的寄存器,然后从 IOWIN
中读写。也就是常说的 index/data
访问方式,或者说 ,用
index
端口指定寄存器,从 data
端口读写寄存器,data
端口就像是所有寄存器的窗口。
而所谓内存映射
,就是把这些寄存器看作内存的一部分,读写内存,就是读写寄存器,可以用访问内存的指令比如 mov
来访问寄存器。还有一种是 IO端口映射
,这种映射方式是将外设的 IO端口(外设的一些寄存器)
看成一个独立的地址空间,访问这片空间不能用访问内存的指令,而需要专门的 in/out
指令来访问。
IOAPIC 寄存器
ID Register
索引为 0
:ID
Version Register
索引为 1
表示版本,
表示重定向表项最多有几个,这里就是 23(从 0 开始计数)
Redirection Table Entry
重定向表项,IOAPIC
有 24 个管脚,每个管脚都对应着一个 64 位的重定向表项(也相当于 64 位的寄存器),索引为 ,重定向表项的格式如下所示:
[站外图片上传中...(image-977a7-1704199812111)]
[站外图片上传中...(image-de719c-1704199812111)]
[站外图片上传中...(image-706e5d-1704199812111)]
这个表项/寄存器包含了该中断的所有属性信息,以什么方式触发中断,传送的方式状态,管脚极性等等,这是 大佬在他的
中总结出来的,很全面也很复杂,只说几点:
-
和
字段决定了该中断发送给哪个或哪些
-
,中断控制器很重要的一项工作就是将中断信号翻译成中断向量,这个中断向量就是
的索引,
里面的中断描述符就存放着中断处理程序的地址。在
中,
,而在
模式下,
对应的
由操作系统对
初始化的时候设置分配。
-
的管脚没有优先级之分,不像
的
的优先级比
的优先级要高,而
对中断优先级的区分在于管脚对应的重定向表项的
字段。
IOAPIC 总结
由上,对 的工作总结:当
的管脚接收到外设发来的中断信号后,根据相应的重定向表项格式化出一条中断消息,然后发送给
字段列出的
。
LAPIC
LAPIC
要比 IOAPIC
复杂的多,先看张总图:
[站外图片上传中...(image-12c6a9-1704199812111)]
本文不会说这么多,也不可能说这么多,有的我也是不太明白,超出我的能力范围,这里只是来看看对其简单编程需要了解的部分:
其主要功能是接收中断消息然后交由
处理,再者就是自身也能作为中断源产生中断发送给自身或其他
。所以其实
能够收到三个来源的中断:
- 本地中断:时钟,温度监测等
- 外部中断:
发来的
-
:处理器间中断,其他
发来的
手册里面做了更精细复杂的分类,私以为了解这三大类就行了。了解
从它的一些重要寄存器入手,通过这些寄存器的作用来了解 LAPIC 如何工作的:
IRR(Interrupt Request Register)
中断请求寄存器,256 位,每位代表着一个中断。当某个中断消息发来时,如果该中断没有被屏蔽,则将 对应的 bit 置 1,表示收到了该中断请求但
还未处理。
ISR(In Service Register)
服务中寄存器,256 位,每位代表着一个中断。当 中某个中断请求发送给
时,
对应的位上便置 1,相应的
位清零,表示 CPU 正在处理该中断。
TMR(Trigger Mode Register)
触发模式寄存器,256 位,每位代表一种中断的触发模式,若中断的触发模式为 (边沿)触发则相应的位清 0,若触发模式为电
(电平) 触发则置 1。
上面三种寄存器如下图所示:
[站外图片上传中...(image-d511fb-1704199812111)]
EOI(End of Interrupt)
中断结束寄存器,32 位,写 EOI 表示中断处理完成。写 寄存器会导致
清理
的对应的位,对于
触发的中断,还会向所有的
发送
消息,通告中断处理已经完成,通常写 0 就行。
ID
[站外图片上传中...(image-301fd-1704199812111)]
用来唯一标识一个 ,
与
一一对应,所以也用
来标识
。
分为
和
,但是如上图手册所示
一般叫做
,这里我为了将两者区分开就写得是
TPR(Task Priority Register)
[站外图片上传中...(image-180a9b-1704199812111)]
任务优先级寄存器,确定当前 能够处理什么优先级别的中断,
只处理比
中级别更高的中断,比它低的中断暂时屏蔽掉,也就是在
中继续等到,直到
的优先级下降到低优先级中断能够被处理。这个机制使得操作系统能够暂时屏蔽低优先级中断,防止打扰高优先级的处理。
一共有 256 种中断,所以 只用到了
,另外
,
为每个中断对应的中断向量号。所以
的高 4 位表示优先级别,有
个取值。
如果优先级别设置为 15,则不会接受任何中断,如果优先级别设置为 0,表示接受所有中断,这也是 设置的默认值。另外一些特殊中断如
不可屏蔽的中断不受
的规则限制。
PPR(Processor Priority Register)
处理器优先级寄存器,表示当前正处理的中断的优先级, 的值为
中正服务的最高优先级中断和
两者之间选取优先级较大的
IF TPR[7:4] ≥ ISRV[7:4]
PPR[7:0] = TPR[7:0]
ELSE
PPR[7:4] = ISRV[7:4] AND PPR[3:0] = 0
在 中等待的中断,只有优先级别高于
的才会被送到
处理,所以
就是靠间接控制
来实现暂时屏蔽比
优先级小的中断的。
SVR(Spurious Interrupt Vector Register)
[站外图片上传中...(image-b5ea67-1704199812111)]
伪中断寄存器, 每响应一次
(可屏蔽中断),就会连续执行两个
周期。在
中有描述,当一个中断在第一个
周期后,第二个
周期前变为无效,则为伪中断,也就是说伪中断就是中断引脚没有维持足够的有效电平而产生的。
这里主要用到 ,可以通过将这位置 1 来使
工作,原话
。
ICR(Interrupt Command Register)
[站外图片上传中...(image-11a41b-1704199812111)]
中断指令寄存器,当一个 想把中断发送给另一个
时,就在
中填写相应的中断向量和目标
标识,然后通过总线向目标
发送消息。
寄存器的字段和
重定向表项较为相似,都有
,
,
等等。
这是处理器之间发送中断消息,所以又叫 (
)消息。在启动的时候我们就用到了这个寄存器,
向
发送
消息,当时
发送了
消息给 AP,这里我们就清楚了实际就是设置
为
、
。当然这期间还根据规范设置了其他字段。
本地中断
本身还能作为中断源产生中断,
(
) 就是自身作为中断源的一个配置表,总共 7 项,每项 32 位,同
,每一项也是一个寄存器,如下所示:
[站外图片上传中...(image-1add1-1704199812111)]
我们主要关注时钟中断, 自带一个时钟,我们可以在
中配置
一项来使用这个时钟。
,设置时钟计数的模式,
-
,一次性的倒数计时,
,周期性的倒数计时,
-
,使用 64 位的时间戳计数器。
一般使用 来周期性的产生时钟中断,周期性的从某个数递减到 0,如此循环往复。这个数设置在
寄存器
[站外图片上传中...(image-f3187b-1704199812111)]
寄存器里面存放
,也就是从哪个数开始倒数。而
存放当前初始计数值,每当计时器
到 0 ,产生时钟中断时,
就会自动地从
重新加载,接着新一次的倒数,所以其实
似乎没什么用,xv6 里面也没用到这个寄存器。另外如果向
寄存器里面写 0 的话会停止计时器。
递减得有个频率,这个频率是系统的总线频率再分频,分频系数设置在 寄存器
[站外图片上传中...(image-9c5420-1704199812111)]
LAPIC 总结
对上面 的寄存有一定了解后,对
的工作应该也有一定了解了,对于从
发来的中断消息,首先判断自己是否接收这个消息,这要根据重定向表项中的
,
来判断:
为 0 时表示物理模式,
字段表示一个
,
根据
寄存器比对判断是否由自己来接收。
为 1 时表示逻辑模式,
需要另外两个寄存器
和
来辅助判断,具体判断方式很复杂,逻辑模式分为
和
,
又分为
和
,了解就好,感兴趣的参考手册或者
,有着很详细的讲解,这里不赘述。
判断不该自己接收就忽略,否则接收该中断, ,
,
,
,
等寄存器配合使用,来决定是否将该中断发送到
进行处理,此外如果中断类型为
等特殊中断是直接发送给
进行处理,不需要上述步骤。
要实现处理器间中断,一个处理器想把中断发送给另一个处理器时,就在 中填写相应的中断向量和目标
,然后通过总线向目标
发送消息。
自己也可以作为中断源,可在
中配置相关中断,主要留意时钟中断的设置,
就是使用
自带的时钟来周期性产生时钟中断。
APIC 部分中断流程总结
某个引脚收到了对应外设发出的中断信号
根据引脚对应的重定向表项,将中断信号翻译成中断消息,然后发送给
字段列出的
根据消息中的
,
,自身的寄存器
,
,
来判断自己是否接收该中断消息,不是则忽略
如果该中断是
/
/
/
/
,直接送
执行,因为这些中断都是负责特殊的系统管理任务。否则的话将
相应的位置 1,等待
来处理。
从
中挑选优先级最大的中断,相应位置 0,
相应位置 1,然后送
执行。
执行
中的中断服务程序来处理中断。
中断处理完成后写
表示中断处理已经完成,写
导致
相应位置 0,对于
触发的中断,还会向所有的
发送
消息,通知中断处理已经完成。
CPU AND OS 处理中断
上述为中断控制器部分,主要功能就是接收外设的中断信号,然后交由 来处理。最开始我把这部分只归结到了
部分,后面想想处理中断是个软硬件协作的一个过程,有
硬件部分,也有
软件部分,但总归是真正处理中断的过程,我就把它们归结到一起了。
在实际谈处理中断前先来了解与中断相关的数据结构
中断描述符表&门描述符&中断向量
中断描述符表 里面存放的是门描述符,有三种门描述符,任务门,中断门,陷阱门:
[站外图片上传中...(image-147c04-1704199812111)]
任务门和任务状态段是 最开始提供的一种任务切换机制,可以使用任务门来切换任务,但因效率低下,现已经不使用,这部分在进程一节中还会提及。
中断门和陷阱门几乎一模一样,从描述符的结构来看就只有 的
字段不一样,实际上从运行过程上说两者的唯一区别是中断门会影响
的
位,而陷阱门不会。
通过中断门访问中断服务程序时, 会对
的
位清 0,即不允许其他中断打扰当前中断的执行,也就是中断的执行过程中关中断,在通过
指令从中断返回时恢复
位。而这里的恢复是指弹出栈中保存的
值到
寄存器,这在后面中断流程再详述。
中断门和陷阱门的格式与 中的段描述符很相像,段描述符描述符了一个段的位置和属性,同样的门描述符也描述了一个段的位置和属性。段的意思很灵活,就是指内存的一段数据信息,不是说只有代码段数据段才叫段,这里门描述符指向的段就是中断服务程序。
[站外图片上传中...(image-26d8f5-1704199812111)]
手册里面这个图画的不是很全,我重画了一张,跟段描述符差不多:
-
,描述符特权级,与
一起作特权级检查
-
,该段在内存中是否存在,0 不存在,1 存在
-
,0 表示系统段,1 表示非系统段,门结构都是系统段
-
,类型字段
-
段选择子:段内偏移
指示中断服务程序的地址
定位中断服务程序
所以 里面就存放着这些门描述符,门描述符又指向一个中断服务程序。在
中有段选择子来充当索引指向一个段描述符,在
中也有类似的结构,那就是中断向量
。它也是上述中断控制器
中多次出现的那个东西。
所以这里我们对中断控制器应有一个更清晰的认识,中断控制器就是接收中断然后将该中断的 传给
,
就会去
中索引门描述符,根据其中记录的
段选择子:段内偏移
获取中断服务程序,然后执行处理中断。
至于如果从逻辑地址 段选择子:段内偏移
经过段级转换到线性地址,线性地址又经过页级转换到物理地址,这个过程现在大家应该很熟悉了,就不再赘述,如果不是很清楚,可以看看前面启动部分的前导理论。
来看张定位中断服务程序的示意图:
[站外图片上传中...(image-92cbf7-1704199812111)]
IDT&IDTR
同 有个
指示
的位置,
也有个
指示
的位置
[站外图片上传中...(image-b7c820-1704199812111)]
里面存放着
的地址和界限,很简单,一眼过去应该就能明白,不多说。同
有 lgdt 指令来加载
的位置信息,IDTR 也有
来加载
的位置信息,指令格式为
LIDT m16&32
CPU AND OS部分的中断流程
和 OS 部分处理中断从
获取到中断控制器发来的
开始,这部分的中断流程可以分为三个步骤:
- 保存上下文
- 执行中断处理程序
- 恢复上下文
保存上下文
保存上下文又分为两部分:
- 硬件
部分
- 软件
部分
CPU 部分
根据
索引门描述符,它会根据描述符的
,描述符选择子中的
,
不可见部分的
进行特权级检查,这里特权级检查很复杂,不是之前说的一般情况下只需要判断
即可,但主要一点就是判断是否有特权级转移,我们一般就是用两种特权级,内核态 0,用户态 3。所以这里就是判断发生中断的前一刻处于哪个特权级,如果处于用户态那么就要从用户态进入内核态,否则就不需要。
保存上下文是保存在栈里面,如果没有特权级转移,发生中断前本身就在内核,那么就使用当前的内核栈保存上下文。如果有特权级转移,发生中断前处于用户态,那么就要进入内核态,进入用户态的一个重要标识就是换栈,换成内核栈。
什么叫做换栈,换栈就是更改 和
寄存器的值,换成内核栈就是将这两个寄存器换成内核栈的
和
,关键问题是:内核栈的
和
在哪?在
里面,
,
,任务状态段,这个结构我们后面进程会详细讲述,这里就只需要知道,
里面有用户态进入内核时需要的内核栈位置信息。
好了现在栈已经换成中断要使用的栈了, 需要在里面保存上下文,有哪些呢?如下图所示:
[站外图片上传中...(image-7c479c-1704199812111)]
上下文也分两种,一种是发生了特权级转移的情况,一种是未发生的情况,发生特权级转移的情况多了 和
,也很好理解,换了栈,当然要把旧栈信息保存到新栈对吧。
表示中断前的一些标识信息,
,
表示中断点,中断退出后任务就会从这儿继续执行。
表示错误码,有些中断会产生错误码
[站外图片上传中...(image-b91750-1704199812111)]
其主要部分就是选择子,所以它用来指明中断发生在哪个段上,其他字段 表示是否指向
,为 1 表示指向
,0 表示指向
/
,
为 0 表示
,
为 1 表示
,
表示中断源是否来自外部。
需要了解的是 Page Fault,缺页异常有错误码,它的错误码最后三位有不同的意思:
- bit0 U/S :
- bit1 W/R:
- bit2 P:
?????????????
我们关注错误码不是因为它有什么作用,当然有用也是有用的,只是这里主要关注它引起的格式问题,虽然有错误码的话 会自动压入,但是
时
不会自动弹出,
时
应指向
,所以错误码需要我们手动弹出。那没有错误码的怎么办呢?为此,我们手动地也压入一个值 0,那么栈里面的结构就统一了,如上图所示。栈中结构同意了,我们的操作也就统一了,
前先把
给弹出去,再执行
指令。
OS 部分
中断服务程序我也分为了三部分,中断入口程序,中断处理程序,中断退出程序。而中断入口程序主要就是用来保存上下文的,这里所谓的上下文就是各类寄存器的值,通常要高效的话,可以选择性的保存,但是省事简单的话直接一股脑儿地全保存了也没什么问题。另外通常这部分也把向量号 也压进去,比如
里就是保存完上下文之后就是这样:
<img src="https://cdn.jsdelivr.net/gh/Rand312/Rand_Picture_System@main/xv6/trapframe.15txa70wgum8.png" style="zoom:50%;" />
执行中断处理程序
这部分其实没啥说的,就是 来执行一个程序来处理中断,不同的中断处理程序肯定是不同的,实际的中断处理程序分布在各个章节详细讲述,这里就这样,了解流程就行。
恢复上下文
这部分其实也没啥说的,因为就是保存上下文的逆操作,我同样的分为两部分
先是 软件部分,执行中断退出程序,弹出通用寄存器,段寄存器等上下文
接着就是硬件 部分,执行
弹出
,
, eflags, (
,
,有特权级转移的话)。
开关中断
这里再说说开关中断的问题, 是能够屏蔽可屏蔽中断的,就是通过
的
位,
位为 1 表示允许中断,
为 0 表示屏蔽中断。
所以开关中断就是修改 的
位,有这么几种方式修改
寄存器值:
-
指令将
位置 1,
指令将
位清 0,这两个指令有使用条件:
,IOPL 也是 ELFAGS 里面的位域,指的是 IO 特权级,这里不深入展开,到进程一块讲述
的时候还会提及
-
指令将
压栈,还有中断时
也会压栈,压入栈中后我们就可以修改栈里面的
的值,待到后续
或者
中断退出将修改后的
弹出后,就相当于修改了
的值。
- 通过中断门进入中断时会将
的
位清 0.
通过更改 的
位来开关中断就只有这三种方法,所以通常我们处理中断时并不需要额外地做开关中断处理,为什么呢?因为硬件
已经帮我们做了,通过中断门进入中断时自动地关闭中断,然后
后又恢复中断。
中断流程总结
私以为上述说的中断流程应是很清楚的,只不过像对什么中断处理的分类,中断服务程序分类是我自己杜撰的,可能与您平时看到的不甚一样,不过我认为这样来看是要清楚些,这里将上述说的总结一番:
- 根据
去
中索引相应的门描述符
- 判断特权级是否发生变化,如果中断发生在用户态,则需要换成内核栈,切换到内核态
- 若发生特权级变化,需要保存用户态的
,
到内核栈,否则不需要保存,然后再保存
,
,
到内核栈中,如果有错误码,还要将错误码压进栈中
- 根据门描述符中的段选择子再去
中索引相应的段描述符得到段基址,与中断描述符里的段偏移量结合起来找到中断服务程序
- 中断入口程序保存上下文,中断处理程序实际处理中断,中断退出程序恢复一部分上下文
- 最后执行
恢复
保存的上下文,如果有特权级转移,则换到用户栈回到用户态。
- 中断完成,接着原任务执行
软件中断
最后这一部分简单说说软件中断(-
),注意软件中断和
里面上下半部分中的软中断机制是两个不同的概念。这里软件中断指的就是
指令来模拟一个
号中断。以前
中系统调用就是使用
0x80 来实现的。
软件中断源来自内部,所以它的处理流程没有中断控制器这个部分,而剩下的 &
部分可以说是一模一样,
,这个
就是
,后面的如何操作不赘述了,见前。只是说如果使用
指令来实现系统调用的话,总归是有点特殊的,留待系统调用再详述。
好了本节就这样吧,有什么问题还请批评指正,也欢迎大家来同我讨论交流学习进步。