xv6(4)中断理论部分

中断理论部分

中断是硬件和软件交互的一种机制,可以说整个操作系统,整个架构都是由中断来驱动的。中断的机制分为两种,中断和异常,中断通常为 IO 设备触发的异步事件,而异常是 CPU 执行指令时发生的同步事件。本文主要来说明 IO 外设触发的中断,总的来说一个中断的起末会经历设备,中断控制器,CPU&OS 三个阶段:设备产生中断,中断控制器接收和发送中断,CPU&OS 来实际处理中断

本文来捋一捋中断需要知道的一些理论知识,主要也是从这三个阶段来说,emmm实际两个阶段,其中第一个阶段设备如何产生信号不讲,超过了操作系统的范围,也超过了我的能力范围。不过在讲解各个硬件比如说磁盘,串口时会涉及到一些这些硬件何时会触发中断。各种硬件外设有着自己的执行逻辑,有各种形式的中断触发机制,比如边沿触发,电平触发等等。总的来说就是向中断控制器发送一个中断信号,中断控制器再作翻译发送给 CPUCPU 再执行中断服务程序对中断进行处理。

说到中断控制器,是个什么东西?中断控制器可以看作是中断的代理,外设是很多的,如果没有一个中断代理,外设想要给 CPU 发送中断信号来处理中断,那只能是外设连接在 CPU 的管脚上,CPU 的管脚是很宝贵的,不可能拿出那么多管脚去连接外设。所以就有了中断控制器这个中断代言人,所有的 IO 外设连接其上,发送中断请求时就向中断控制器发送信号,中断控制器再通知 CPU,如此便解决了上述问题。

中断控制器的发展可分为 PICAPIC 两个阶段,前者适用于单处理器,在单处理器的时代叱咤风云,风靡全球,不过到了现代的多处理器时代不行了,渐渐地被更高级的 APIC 所代替。但 PIC还是值得了解了解的,来看看

中断控制器 PIC

[站外图片上传中...(image-5e429c-1704199812111)]

[站外图片上传中...(image-aa97b0-1704199812111)]

这就是中断控制器 8259A 芯片,我们只需要了解:

  • IRQ0-IRQ7,8 个引脚,每个引脚可以连接一个外设,也就是说一个 8259A 能支持 8 个中断。
  • 8259A 通过 INTINTA(Interrupt Acknowledge) 与 CPU INTR(Interrupt Request)通信

一个 8259A 芯片只能支持 8 个中断,的确有点少了,所以有了级联:

<img src="https://cdn.jsdelivr.net/gh/Rand312/Rand_Picture_System@main/xv6/8259级联.131orxsswt68.png" style="zoom:50%;" />

所谓级联,就是将多个 8259A 给串起来,通常方式为将一个 8259A 的引脚分出来连接另一个 8259A,就如上图所示。一般两个 8259A 级联的时候将主 8259AIRQ2 分配出去连接从 8259A

8259A 的引脚 IRQ 是有优先级的,编号越低,优先级越高,vector=IRQ编号+32,前 32 个是留给异常使用和架构保留的。

8259A 最重要的几个寄存器如下所示:

寄存器

  • IRRInterrup Request Register,共 8 位,对应 IRQ0-IRQ7 8 个中断引脚。当某个引脚接收到中断信号时,IRR 中相应的位 置 1,表示 PIC 已收到该设备的中断请求但还未交由 CPU 处理
  • ISRIn Service Register,共 8 位,同样的每位表示对应引脚表示的中断,当 IRR 中某个中断请求提交给 CPU 时,IRR 相应的位被清 0,ISR 相应的位就被置 1,表示 CPU 正处理该中断。
  • IMRInterrupt Mask Register,共 8 位,同样的每位表示对应引脚表示的中断,某位置 1 表示屏蔽相应的中断,清 0 表示允许相应的中断。
  • PRPriority Register,优先级仲裁寄存器,有多个中断同时发生时,它能找出哪个中断优先级最高
  • EOIEnd of Interrupt,在 PIC 里面 EOI 不是一个寄存器,它是操作命令寄存器中的一个 bit,写 EOI 表示中断结束。

8259A 还有其他的寄存器比如初始化命令寄存器,操作命令寄存器,这里我们后面讲述 xv6 并不会使用 PIC,所以这里也就不说明了,最后来看看通过 PIC 的中断流程:

PIC 中断流程

  1. IRQ 引脚接收到中断信号,若该中断没有被屏蔽,那么 IRR 中相应的位置 1
  2. PIC 通过 INTCPU 发送 INTR 信号
  3. CPU 通过 INTA 引脚发送应答信号给 PIC
  4. PIC 收到应答信号后,将 IRR 中最高优先级的中断相应的位清 0,将 ISR 相应的位置 1
  5. CPU 再次发送 INTA 信号给 PICPIC 收到后将 vector 送到数据线
  6. CPU 根据 vector 索引 IDT 中的门描述符,执行中断服务程序
  7. 中断处理完成之后写 EOI,将 ISR 中相应的位清 0 表示中断完成

高级中断控制器 APIC

上述就是中断控制器 PIC 的内容,PIC 只用于单处理器,对于如今的多核多处理器时代,PIC 无能为力,所以出现了更高级的中断控制器 APICAPIC(Advanced Programmable Interrupt Controller) 高级可编程中断控制器,APIC 分成两部分 LAPICIOAPIC,前者 LAPIC 位于 CPU 内部,每个 CPU 都有一个 LAPIC,后者 IOAPIC 与外设相连。外设发出的中断信号经过 IOAPIC 处理之后发送给 LAPIC,再由 LAPIC 决定是否交由 CPU 进行实际的中断处理。

[站外图片上传中...(image-86962c-1704199812111)]

可以看出每个 CPU 上有一个 LAPICIOAPIC 是系统芯片组一部分,各个中断消息通过总线发送接收。关于 APIC 的内容很多也很复杂,详细描述的可以参考 intel 开发手册卷三,本文不探讨其中的细节,只在上层较为抽象的层面讲述,理清 APIC 模式下中断的过程。

下面就分别来看看 IOAPICLAPIC

IOAPIC

IOAPIC 主要负责接收外部的硬件中断,将硬件产生的中断信号翻译成具有一定格式的消息,然后通过总线将消息发送给一个或者多个 LAPICIOAPIC 主要组成如下:

  • 24 个中断管脚,一个 IOAPIC 支持 24 个中断
  • 一张 24 项的中断重定向表(PRT,Programmable Redirection Table),每个表项都是一个 64 位的寄存器
  • 一些可编程寄存器,例如窗口寄存器,版本寄存器等等
  • 通过 APIC 总线发送和接收 APIC 信息的一个信息单元

我们了解一个硬件主要就是了解它的寄存器,IOAPIC 有两个内存映射的寄存器,indexdata 寄存器,通过 index/data 的方式访问 IOAPIC 的其他寄存器。

IOAPIC 其他寄存器一览:

[站外图片上传中...(image-574ba4-1704199812111)]

内存映射的两个寄存器

[站外图片上传中...(image-d4823a-1704199812111)]

这两个寄存器是内存映射的,IOREGSEL,地址为 0xFEC0\ 0000IOWIN,地址为 0xFEC0\ 0010hIOREGSEL 用来指定要读写的寄存器,然后从 IOWIN 中读写。也就是常说的 index/data 访问方式,或者说 adress/data,用 index 端口指定寄存器,从 data 端口读写寄存器,data 端口就像是所有寄存器的窗口。

而所谓内存映射,就是把这些寄存器看作内存的一部分,读写内存,就是读写寄存器,可以用访问内存的指令比如 mov 来访问寄存器。还有一种是 IO端口映射,这种映射方式是将外设的 IO端口(外设的一些寄存器) 看成一个独立的地址空间,访问这片空间不能用访问内存的指令,而需要专门的 in/out 指令来访问

IOAPIC 寄存器

ID Register
  • 索引为 0

  • bit24 - bit27:ID

Version Register
  • 索引为 1

  • bit0-bit7 表示版本,

  • bit16-bit23 表示重定向表项最多有几个,这里就是 23(从 0 开始计数)

Redirection Table Entry

重定向表项,IOAPIC 有 24 个管脚,每个管脚都对应着一个 64 位的重定向表项(也相当于 64 位的寄存器),索引为 0x10-0x3F,重定向表项的格式如下所示:

[站外图片上传中...(image-977a7-1704199812111)]

[站外图片上传中...(image-de719c-1704199812111)]

[站外图片上传中...(image-706e5d-1704199812111)]

这个表项/寄存器包含了该中断的所有属性信息,以什么方式触发中断,传送的方式状态,管脚极性等等,这是 ZX\_WING 大佬在他的 Interrupt\ in\ Linux 中总结出来的,很全面也很复杂,只说几点:

  • destination fielddestination mode 字段决定了该中断发送给哪个或哪些 LAPIC
  • vector,中断控制器很重要的一项工作就是将中断信号翻译成中断向量,这个中断向量就是 IDT 的索引,IDT 里面的中断描述符就存放着中断处理程序的地址PIC 中,vector = IRQ编号+32,而在 APIC 模式下,IRQ 对应的 vecotr 由操作系统对 IOAPIC 初始化的时候设置分配
  • IOAPIC 的管脚没有优先级之分,不像 PICIRQ0 的优先级比 IRQ1 的优先级要高,而 IOAPIC 对中断优先级的区分在于管脚对应的重定向表项的 vector 字段。

IOAPIC 总结

由上,IOAPIC 的工作总结:当 IOAPIC 的管脚接收到外设发来的中断信号后,根据相应的重定向表项格式化出一条中断消息,然后发送给 destination field 字段列出的 LAPIC

LAPIC

LAPIC 要比 IOAPIC 复杂的多,先看张总图:

[站外图片上传中...(image-12c6a9-1704199812111)]

本文不会说这么多,也不可能说这么多,有的我也是不太明白,超出我的能力范围,这里只是来看看对其简单编程需要了解的部分:

LAPIC 其主要功能是接收中断消息然后交由 CPU 处理,再者就是自身也能作为中断源产生中断发送给自身或其他 CPU。所以其实 LAPIC 能够收到三个来源的中断:

  • 本地中断:时钟,温度监测等
  • 外部中断:IOAPIC 发来的
  • IPI:处理器间中断,其他 LAPIC 发来的

inel 手册里面做了更精细复杂的分类,私以为了解这三大类就行了。了解 LAPIC从它的一些重要寄存器入手,通过这些寄存器的作用来了解 LAPIC 如何工作的:

IRR(Interrupt Request Register)

中断请求寄存器,256 位,每位代表着一个中断。当某个中断消息发来时,如果该中断没有被屏蔽,则将 IRR 对应的 bit 置 1,表示收到了该中断请求但 CPU 还未处理

ISR(In Service Register)

服务中寄存器,256 位,每位代表着一个中断。IRR 中某个中断请求发送给 CPU 时,ISR 对应的位上便置 1,相应的 IRR 位清零,表示 CPU 正在处理该中断

TMR(Trigger Mode Register)

触发模式寄存器,256 位,每位代表一种中断的触发模式,若中断的触发模式为 edge(边沿)触发则相应的位清 0,若触发模式为电 level(电平) 触发则置 1。

上面三种寄存器如下图所示:

[站外图片上传中...(image-d511fb-1704199812111)]

EOI(End of Interrupt)

中断结束寄存器,32 位,写 EOI 表示中断处理完成。写 EOI 寄存器会导致 LAPIC 清理 ISR 的对应的位,对于 level 触发的中断,还会向所有的 IOAPIC 发送 EOI 消息,通告中断处理已经完成,通常写 0 就行。

ID

[站外图片上传中...(image-301fd-1704199812111)]

用来唯一标识一个 LAPICLAPICCPU 一一对应,所以也用 LAPIC ID 来标识 CPUAPIC 分为 LAPICIOAPIC,但是如上图手册所示 LAPIC ID 一般叫做 APIC ID,这里我为了将两者区分开就写得是 LAPIC ID

TPR(Task Priority Register)

[站外图片上传中...(image-180a9b-1704199812111)]

任务优先级寄存器,确定当前 CPU 能够处理什么优先级别的中断,CPU 只处理比 TPR 中级别更高的中断,比它低的中断暂时屏蔽掉,也就是在 IRR 中继续等到,直到 TPR 的优先级下降到低优先级中断能够被处理。这个机制使得操作系统能够暂时屏蔽低优先级中断,防止打扰高优先级的处理。

一共有 256 种中断,所以 TPR 只用到了 bit0-bit7,另外 优先级别=vector/16vector 为每个中断对应的中断向量号。所以 Task Priority 的高 4 位表示优先级别,有 0-15 个取值。

如果优先级别设置为 15,则不会接受任何中断,如果优先级别设置为 0,表示接受所有中断,这也是 Linux 设置的默认值。另外一些特殊中断如 NMI 不可屏蔽的中断不受 TPR 的规则限制。

PPR(Processor Priority Register)

处理器优先级寄存器,表示当前正处理的中断的优先级,PPR 的值为 ISR 中正服务的最高优先级中断和 TPR 两者之间选取优先级较大的

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

IRR 中等待的中断,只有优先级别高于 PPR 的才会被送到 CPU 处理,所以 TPR 就是靠间接控制 PPR 来实现暂时屏蔽比 TPR 优先级小的中断的。

SVR(Spurious Interrupt Vector Register)

[站外图片上传中...(image-b5ea67-1704199812111)]

伪中断寄存器,CPU 每响应一次 INTR(可屏蔽中断),就会连续执行两个 INTA 周期。在 MP\ Spec 中有描述,当一个中断在第一个 INTA 周期后,第二个 INTA 周期前变为无效,则为伪中断,也就是说伪中断就是中断引脚没有维持足够的有效电平而产生的

这里主要用到 bit8,可以通过将这位置 1 来使 APIC 工作,原话 To enable the APIC

ICR(Interrupt Command Register)

[站外图片上传中...(image-11a41b-1704199812111)]

中断指令寄存器,当一个 CPU 想把中断发送给另一个 CPU 时,就在 ICR 中填写相应的中断向量和目标 LAPIC 标识,然后通过总线向目标 LAPIC 发送消息。ICR 寄存器的字段和 IOAPIC 重定向表项较为相似,都有 destination field, delivery mode, destination mode 等等。

这是处理器之间发送中断消息,所以又叫 IPI(InterProcessor Interrupt)消息。在启动的时候我们就用到了这个寄存器,BSPAPs 发送 IPI 消息,当时 BSP 发送了 INIT-SIPI-SIPI 消息给 AP,这里我们就清楚了实际就是设置 delivery modeINITStart-up。当然这期间还根据规范设置了其他字段。

本地中断

LAPIC 本身还能作为中断源产生中断,LVT(Local Vector Table) 就是自身作为中断源的一个配置表,总共 7 项,每项 32 位,同 IOAPIC,每一项也是一个寄存器,如下所示:

[站外图片上传中...(image-1add1-1704199812111)]

我们主要关注时钟中断,APIC 自带一个时钟,我们可以在 LVT 中配置 Timer 一项来使用这个时钟。

Timer Mode,设置时钟计数的模式,one-shot,一次性的倒数计时,periodic,周期性的倒数计时,TSC-deadline,使用 64 位的时间戳计数器。

一般使用 periodic 来周期性的产生时钟中断,周期性的从某个数递减到 0,如此循环往复。这个数设置在 TICR 寄存器

[站外图片上传中...(image-f3187b-1704199812111)]

TICR 寄存器里面存放 Initial Count,也就是从哪个数开始倒数。而 Current Count 存放当前初始计数值,每当计时器 count 到 0 ,产生时钟中断时,Current Count 就会自动地从 Intial Count 重新加载,接着新一次的倒数,所以其实 Current Count 似乎没什么用,xv6 里面也没用到这个寄存器。另外如果向 TICR 寄存器里面写 0 的话会停止计时器。

递减得有个频率,这个频率是系统的总线频率再分频,分频系数设置在 TDCR 寄存器

[站外图片上传中...(image-9c5420-1704199812111)]

LAPIC 总结

对上面 LAPIC 的寄存有一定了解后,对 LAPIC 的工作应该也有一定了解了,对于从 IOAPIC 发来的中断消息,首先判断自己是否接收这个消息,这要根据重定向表项中的 destination fielddestination mode 来判断:

destination mode 为 0 时表示物理模式,destination field 字段表示一个 APIC IDLAPIC 根据 ID 寄存器比对判断是否由自己来接收

destination mode 为 1 时表示逻辑模式,LAPIC 需要另外两个寄存器 LDRDFR 来辅助判断,具体判断方式很复杂,逻辑模式分为 flatclustercluster 又分为 flat clusterhierachical cluster,了解就好,感兴趣的参考手册或者 interrupt in linux ,有着很详细的讲解,这里不赘述。

判断不该自己接收就忽略,否则接收该中断, IRRISRTPRPPREOI 等寄存器配合使用,来决定是否将该中断发送到 CPU 进行处理,此外如果中断类型为 NMI 等特殊中断是直接发送给 CPU 进行处理,不需要上述步骤。

要实现处理器间中断,一个处理器想把中断发送给另一个处理器时,就在 ICR 中填写相应的中断向量和目标 Destination Feild,然后通过总线向目标 LAPIC 发送消息。

LAPIC 自己也可以作为中断源,可在 LVT 中配置相关中断,主要留意时钟中断的设置,xv6 就是使用 LAPIC 自带的时钟来周期性产生时钟中断。

APIC 部分中断流程总结

  1. IOAPIC 某个引脚收到了对应外设发出的中断信号

  2. IOAPIC 根据引脚对应的重定向表项,将中断信号翻译成中断消息,然后发送给 destination field 字段列出的 LAPIC

  3. LAPIC 根据消息中的 destination modedestination field,自身的寄存器 IDLDRDFR 来判断自己是否接收该中断消息,不是则忽略

  4. 如果该中断是 SMI/NMI/INIT/ExtINT/SIPI,直接送 CPU 执行,因为这些中断都是负责特殊的系统管理任务。否则的话将 IRR 相应的位置 1,等待 CPU 来处理。

  5. IRR 中挑选优先级最大的中断,相应位置 0,ISR 相应位置 1,然后送 CPU 执行。

  6. CPU 执行 OS 中的中断服务程序来处理中断。

  7. 中断处理完成后写 EOI 表示中断处理已经完成,写 EOI 导致 ISR 相应位置 0,对于 level 触发的中断,还会向所有的 IOAPIC 发送 EOI 消息,通知中断处理已经完成。

CPU AND OS 处理中断

上述为中断控制器部分,主要功能就是接收外设的中断信号,然后交由 CPU 来处理。最开始我把这部分只归结到了 CPU 部分,后面想想处理中断是个软硬件协作的一个过程,有 CPU 硬件部分,也有 OS 软件部分,但总归是真正处理中断的过程,我就把它们归结到一起了。

在实际谈处理中断前先来了解与中断相关的数据结构

中断描述符表&门描述符&中断向量

中断描述符表 IDT 里面存放的是门描述符,有三种门描述符,任务门,中断门,陷阱门:

[站外图片上传中...(image-147c04-1704199812111)]

任务门和任务状态段是 intel 最开始提供的一种任务切换机制,可以使用任务门来切换任务,但因效率低下,现已经不使用,这部分在进程一节中还会提及。

中断门和陷阱门几乎一模一样,从描述符的结构来看就只有 bit8-bit11TYPE 字段不一样,实际上从运行过程上说两者的唯一区别是中断门会影响 EFLAGSIF 位,而陷阱门不会

通过中断门访问中断服务程序时,CPU 会对 EFLAGSIF 位清 0,即不允许其他中断打扰当前中断的执行,也就是中断的执行过程中关中断,在通过 iret 指令从中断返回时恢复 IF 位。而这里的恢复是指弹出栈中保存的 eflags 值到 EFLAGS 寄存器,这在后面中断流程再详述。

中断门和陷阱门的格式与 GDT 中的段描述符很相像,段描述符描述符了一个段的位置和属性,同样的门描述符也描述了一个段的位置和属性。段的意思很灵活,就是指内存的一段数据信息,不是说只有代码段数据段才叫段,这里门描述符指向的段就是中断服务程序。

[站外图片上传中...(image-26d8f5-1704199812111)]

intel 手册里面这个图画的不是很全,我重画了一张,跟段描述符差不多:

  • DPL,描述符特权级,与 RPL CPL 一起作特权级检查
  • P,该段在内存中是否存在,0 不存在,1 存在
  • S,0 表示系统段,1 表示非系统段,门结构都是系统段
  • TYPE,类型字段
  • 段选择子:段内偏移 指示中断服务程序的地址

定位中断服务程序

所以 IDT 里面就存放着这些门描述符,门描述符又指向一个中断服务程序。在 GDT 中有段选择子来充当索引指向一个段描述符,在 IDT 中也有类似的结构,那就是中断向量 vector。它也是上述中断控制器 APIC 中多次出现的那个东西。

所以这里我们对中断控制器应有一个更清晰的认识,中断控制器就是接收中断然后将该中断的 vector 传给 CPUCPU 就会去 IDT 中索引门描述符,根据其中记录的 段选择子:段内偏移 获取中断服务程序,然后执行处理中断

至于如果从逻辑地址 段选择子:段内偏移 经过段级转换到线性地址,线性地址又经过页级转换到物理地址,这个过程现在大家应该很熟悉了,就不再赘述,如果不是很清楚,可以看看前面启动部分的前导理论。

来看张定位中断服务程序的示意图:

[站外图片上传中...(image-92cbf7-1704199812111)]

IDT&IDTR

GDT 有个 GDTR 指示 GDT 的位置,IDT 也有个 IDTR 指示 IDT 的位置

[站外图片上传中...(image-b7c820-1704199812111)]

IDTR 里面存放着 IDT 的地址和界限,很简单,一眼过去应该就能明白,不多说。同 GDTR 有 lgdt 指令来加载 GDT 的位置信息,IDTR 也有 lidt 来加载 IDT 的位置信息,指令格式为 LIDT m16&32

CPU AND OS部分的中断流程

CPU 和 OS 部分处理中断从 CPU 获取到中断控制器发来的 vector 开始,这部分的中断流程可以分为三个步骤:

  • 保存上下文
  • 执行中断处理程序
  • 恢复上下文

保存上下文

保存上下文又分为两部分:

  • 硬件 CPU 部分
  • 软件 OS 部分
CPU 部分

CPU 根据 vector 索引门描述符,它会根据描述符的 DPL,描述符选择子中的 RPLCS 不可见部分的 CPL 进行特权级检查,这里特权级检查很复杂,不是之前说的一般情况下只需要判断 DPL \ge CPL\ \ \ \&\&\ \ \ DPL \ge RPL 即可,但主要一点就是判断是否有特权级转移,我们一般就是用两种特权级,内核态 0,用户态 3。所以这里就是判断发生中断的前一刻处于哪个特权级,如果处于用户态那么就要从用户态进入内核态,否则就不需要

保存上下文是保存在栈里面,如果没有特权级转移,发生中断前本身就在内核,那么就使用当前的内核栈保存上下文。如果有特权级转移,发生中断前处于用户态,那么就要进入内核态,进入用户态的一个重要标识就是换栈,换成内核栈

什么叫做换栈,换栈就是更改 SSESP 寄存器的值,换成内核栈就是将这两个寄存器换成内核栈的 ssesp,关键问题是:内核栈的 ssesp 在哪?在 TSS 里面,TSSTask Segment State,任务状态段,这个结构我们后面进程会详细讲述,这里就只需要知道,TSS 里面有用户态进入内核时需要的内核栈位置信息

好了现在栈已经换成中断要使用的栈了,CPU 需要在里面保存上下文,有哪些呢?如下图所示:

[站外图片上传中...(image-7c479c-1704199812111)]

上下文也分两种,一种是发生了特权级转移的情况,一种是未发生的情况,发生特权级转移的情况多了 ss\_oldesp\_old,也很好理解,换了栈,当然要把旧栈信息保存到新栈对吧。

eflags 表示中断前的一些标识信息,cs\_oldeip\_old 表示中断点,中断退出后任务就会从这儿继续执行。

error\_code表示错误码,有些中断会产生错误码

[站外图片上传中...(image-b91750-1704199812111)]

其主要部分就是选择子,所以它用来指明中断发生在哪个段上,其他字段 IDT 表示是否指向 IDT,为 1 表示指向 IDT,0 表示指向 GDT/LDTTI 为 0 表示 GDTTI 为 1 表示 LDTEXT 表示中断源是否来自外部。

需要了解的是 Page Fault,缺页异常有错误码,它的错误码最后三位有不同的意思:

  1. bit0 U/S :
  2. bit1 W/R:
  3. bit2 P:

?????????????

我们关注错误码不是因为它有什么作用,当然有用也是有用的,只是这里主要关注它引起的格式问题,虽然有错误码的话 CPU 会自动压入,但是 iretCPU 不会自动弹出,iretESP 应指向 eip\_old,所以错误码需要我们手动弹出。那没有错误码的怎么办呢?为此,我们手动地也压入一个值 0,那么栈里面的结构就统一了,如上图所示。栈中结构同意了,我们的操作也就统一了,iret 前先把 error\_code/0 给弹出去,再执行 iret 指令。

OS 部分

中断服务程序我也分为了三部分,中断入口程序,中断处理程序,中断退出程序。而中断入口程序主要就是用来保存上下文的,这里所谓的上下文就是各类寄存器的值,通常要高效的话,可以选择性的保存,但是省事简单的话直接一股脑儿地全保存了也没什么问题另外通常这部分也把向量号 vector 也压进去,比如 xv6 里就是保存完上下文之后就是这样:

<img src="https://cdn.jsdelivr.net/gh/Rand312/Rand_Picture_System@main/xv6/trapframe.15txa70wgum8.png" style="zoom:50%;" />

执行中断处理程序

这部分其实没啥说的,就是 CPU 来执行一个程序来处理中断,不同的中断处理程序肯定是不同的,实际的中断处理程序分布在各个章节详细讲述,这里就这样,了解流程就行。

恢复上下文

这部分其实也没啥说的,因为就是保存上下文的逆操作,我同样的分为两部分

先是 OS 软件部分,执行中断退出程序,弹出通用寄存器,段寄存器等上下文

接着就是硬件 CPU 部分,执行 iret 弹出 cs\_old, eip\_old, eflags, (ss\_old, esp\_old,有特权级转移的话)。

开关中断

这里再说说开关中断的问题,CPU 是能够屏蔽可屏蔽中断的,就是通过 EFLAGSIF 位,IF 位为 1 表示允许中断,IF 为 0 表示屏蔽中断。

所以开关中断就是修改 EFLAGSIF 位,有这么几种方式修改 EFLAGS 寄存器值:

  • sti 指令将 EFLAGS IF 位置 1,cli 指令将 EFLAGS IF 位清 0,这两个指令有使用条件:CPL \le IOPL,IOPL 也是 ELFAGS 里面的位域,指的是 IO 特权级,这里不深入展开,到进程一块讲述 TSS 的时候还会提及
  • pushf 指令将 EFLAGS 压栈,还有中断时 EFLAGS 也会压栈,压入栈中后我们就可以修改栈里面的 EFLAGS 的值,待到后续 popf 或者 iret 中断退出将修改后的 EFLAGS 弹出后,就相当于修改了 EFLAGS 的值。
  • 通过中断门进入中断时会将 EFLAGSIF 位清 0.

通过更改 EFLAGSIF 位来开关中断就只有这三种方法,所以通常我们处理中断时并不需要额外地做开关中断处理,为什么呢?因为硬件 CPU 已经帮我们做了,通过中断门进入中断时自动地关闭中断,然后 iret 后又恢复中断。

中断流程总结

私以为上述说的中断流程应是很清楚的,只不过像对什么中断处理的分类,中断服务程序分类是我自己杜撰的,可能与您平时看到的不甚一样,不过我认为这样来看是要清楚些,这里将上述说的总结一番:

  1. 根据 vectorIDT 中索引相应的门描述符
  2. 判断特权级是否发生变化,如果中断发生在用户态,则需要换成内核栈,切换到内核态
  3. 若发生特权级变化,需要保存用户态的 ss\_oldesp\_old 到内核栈,否则不需要保存,然后再保存 eflagscs\_oldip\_old 到内核栈中,如果有错误码,还要将错误码压进栈中
  4. 根据门描述符中的段选择子再去 GDT 中索引相应的段描述符得到段基址,与中断描述符里的段偏移量结合起来找到中断服务程序
  5. 中断入口程序保存上下文,中断处理程序实际处理中断,中断退出程序恢复一部分上下文
  6. 最后执行 iret 恢复 CPU 保存的上下文,如果有特权级转移,则换到用户栈回到用户态。
  7. 中断完成,接着原任务执行

软件中断

最后这一部分简单说说软件中断(Software-Generated Interrupts),注意软件中断和 Linux 里面上下半部分中的软中断机制是两个不同的概念。这里软件中断指的就是 INT n 指令来模拟一个 n 号中断。以前 Linux 中系统调用就是使用 int 0x80 来实现的。

软件中断源来自内部,所以它的处理流程没有中断控制器这个部分,而剩下的 CPU&OS 部分可以说是一模一样,int n,这个 n 就是 vector,后面的如何操作不赘述了,见前。只是说如果使用 int n 指令来实现系统调用的话,总归是有点特殊的,留待系统调用再详述。

好了本节就这样吧,有什么问题还请批评指正,也欢迎大家来同我讨论交流学习进步。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355

推荐阅读更多精彩内容

  • 启动理论部分 本节来说说捋清启动需要知道的一些东西,因知识点的确很多,涉及了各个方面,我就不像其他章节一样各个部分...
    Rand_cs1阅读 160评论 0 1
  • 启动理论部分 本节来说说捋清启动需要知道的一些东西,因知识点的确很多,涉及了各个方面,我就不像其他章节一样各个部分...
    Rand_CS阅读 73评论 0 0
  • 说几句废fu之言,前几天没有接着写进程调度记录的文章,当然现在也不会写,如题,从现在开始记录linux内核基础知识...
    Gitlusen阅读 1,581评论 0 0
  • 启动代码部分 本文来说码,实打实地来看看计算机到底是如何启动的,先来看看 启动的整体流程图,好有个大概认识: 不...
    Rand_CS阅读 99评论 0 0
  • 在ARM64和MIPS这些精简指令集计算机体系结构中,中断、系统调用和其他打断程序正常执行流的事件统称为异常,这是...
    CHCD阅读 2,595评论 0 0