中断
中断机制的本质:来一个中断信号,调用相应的中断处理程序
中断分类
中断分为内部中断和外部中断,内部中断分为异常和软中断,外部中断分为可屏蔽中断和不可屏蔽中断。每个中断都有对应的中断向量号,根据中断向量号调用相应的中断处理函数,理想情况下为每一个可屏蔽的外部中断在CPU上都接一个引脚,但是理论上外设是无上限的,必然扩大了CPU的大小,所以只在CPU上接两个引脚用于外部中断(可屏蔽、不可屏蔽),中断代理连接可屏蔽引脚,它在外设和CPU之间,用于仲裁外设中断并且分配中断向量号。
1. 外部中断(中断源必须是硬件)
可屏蔽中断(可通过eflag寄存器的IF位设置中断,中断号由中断代理提供)
不可屏蔽中断(中断向量号由CPU提供,统一为2)
2.内部中断(不受eflag寄存器的IF位影响)
软中断(中断向量号由软件提供):Int 8位立即数:系统调用命令,8位可以表示256个中断
异常(中断向量号由CPU提供)
针对外部中断,CPU提供了两个引脚,根据中断的紧急程度从不同的引脚进

Intel支持256个中断,019是已经定义的异常,2031是保留,所以自定义创建中断向量,中断向量号要从32开始

中断使用的相关寄存器
EFLAGS:X86架构CPU中一个重要的状态寄存器,主要用于存储处理器状态和控制信息:IF位(Interrupt Enable Flag):控制CPU允许响应【可屏蔽中断】。IF=0屏蔽所有的可屏蔽中断,IF=1,通过中断代理选择性屏蔽外设
SS,ESP :栈寄存器,因为中断涉及特权级变化,处理器在不同的特权级下使用不同的特权级栈
CS,EIP:中断是段间远转移,中断返回需要找到入口
中断描述符表
中断向量表是实模式下的表,中断描述符表是保护模式下的表;
中断向量表位置固定大小固定,中断描述符表位置不固定,需要使用硬件寄存器存储中断描述符表的存储地址
中断描述符表的存放地址不受限制,操作系统需要知道中断描述符表的存放地址,使用中断描述符表寄存器IDT(Interrupt Descriptor Table Register)存储中断描述表的地址。中断描述符表寄存器是存放在CPU中的寄存器。
一个中断描述符用8B表示,16位的表界限可以表示8192个中断描述符,但是处理器只支持256个中断,中断号 0~255,其余中断描述符不可用。
.png)

注意:中断描述符中的DPL既不直接指向中断描述符所在的8字节内存特权级,也不直接只向中断程序所在的内存的特权级。它的作用是控制出发该中断或异常所需的特权级下限。在可屏蔽中断中,DPL=0,在系统调用时,DPL=3,因为系统调用是用户程序通过int 8位立即数触发的,访问0x80中断向量号对应的中断处理函数时,处理器处于CPL=3下。
中断处理过程
(注意:CPU外部中断由中断代理接收,经中断代理处理后将中断向量号发送给CPU,异常和软中断以及灾难性错误是直接可以获取中断向量号的,不需要中断代理发送)。
step1:定位中断描述符
当CPU收到中断向量号后,在中断描述符表中索引,中断描述符的地址是IDTR的基址加上中断向量号*8。
step2:特权级检查
引用通过中断门描述符可以调用中断处理函数,进入内核态。当前特权级必须在中断门描述符的DPL和目标代码段的DPL特权级之间。因为中断是提供中断向量号的,是个整数,它没有RPL
step3:执行中断处理函数
先保存程序的上下文环境,将中断门描述符代码段选择子加载到代码段寄存器CS中,把中断门描述符中的中断处理程序的偏移地址加载到EIP寄存器中。这里存在一个非常重要的压栈过程!!!
step4:从中断返回
从中断返回指令是iret,从栈中弹出相应的数据到寄存器(cs,eip,eflags等),并根据特权级是否变化判断是否需要恢复旧栈,也就是说是否需要将栈中位于SS_old和ESP_old位置弹出到寄存器ss和esp中
中断执行的压栈过程
通过中断向量号在中断描述符表中找到中断描述,会进行特权检查,然后将中断描述符中的代码段选择子和中断处理程序在目标代码段中的偏移量分别加载到CS 和 EIP中。不同特权级下处理器要使用不用的特权级的栈。
如果需要向更高特权级转移:在TSS结构中找到同中断处理程序相同的特权级的栈,在这个新栈下保存旧栈的SS和ESP,分别记作SS_old和ESP_old;然后压入EFLAGS寄存器,再压入CS_old和EIP_old,对于有错误码的还要压入错误码ERROR_CODE(****一般有错误码的是在0~32之间的异常,外部中断和int软中断不会产生错误码****)


注意:中断描述符表其实就是中断函数起始地址的数组,当中断信号来时,中断信号的本质就是中断描述符表中的索引,根据索引找到对应的中断函数处理,在代码中使用地址数组存储中断处理函数的起始地址,这涉及C程序调用,需要保护进程的上下文,所以在硬件自动保存SS_old、ESP_old、EFLAGS、CS_old、EIP_old、ERROR_CODE后还需要压入寄存器环境,包括4(ds,es,fs,gs)个段寄存器和8个通用寄存器(EAX, EXC,EDX,EBX,ESP,EBP,ESI,EDI)

1 | [bits 32] |
中断执行完后的弹栈过程
****iret****指令是中断执行的最后一个指令,负责把栈中的数据都弹出到对应的寄存器中,它是压栈过程的逆过程-。当执行iret指令时必须保证ss_old或者ss指向的是EIP,如果有错误码需要手动跳过
中断执行的特权级
中断处理都是在0特权级下执行的,因为中断的发生多半是因为外部硬件发生了某种状况或者发生了不可抗力的原因导致必须要通知CPU,在大多数情况下,CPU只有处于0特权级才能访问硬件。
可屏蔽中断代理—8259A
****注意:8259A中断代理位于主板上的南桥芯片中,不需要为它的引脚指定外部连接设备,比如IR0引脚就是时钟中断,这由内部电路实现,咱们只需要直接操作8259A就行,不用担心这些外设是否已经连接上8259A的引脚****
8259A用于管理和控制可屏蔽中断,表现在屏蔽外设中断,对它们实行优先级判决,向CPU提供中断向量号一个8259A 芯片只能管理 8 个中断,为了支持 Intel的256个中断,需要对8259A进行级联,个人PC中也只有2个8259A芯片
Intel支持256个中断,意味着中断向量号要256个,但是2个8259A芯片只能支持17个中断,如何支持256个?因为****中断向量号 = 起始中断向量号+IRQ接口向量号****。
本次项目中使用到的8259A中断是IRQ0时钟中断和IRQ1键盘中断,系统调用是通过软中断0x80和eax子功能号实现的。

8259A编程本质
8259A是可编程的中断代理,它的内部有两组寄存器,一组是初始化命令寄存器组,用来确定是否需要级联,设置起始中断向量号,设置中断结束模式;另一组寄存器是操作命令字,用来设置中断屏蔽和中断结束结束的工作模式
8259A芯片的内部结构

一个中断信号来时,8259A芯片做了什么?
****【step1】****8259A 首先检查IMR中是否屏蔽了来自该IRQ接口中断信号,1:屏蔽,就将该信号丢弃, 进step2
****【step2】****将该中断信号存入 IRR 寄存器中
*【step3】* PR 寄存器在IRR寄存器中取一个优先级最大的中断信号(判断方法:IRQ接口号越低,优先级越大)
*【step4】* 8259A 向CPU发送 INTR信号
****【step5】****CPU 接收到中断信号后,将当前指令执行完向8259A发送一个中断响应信号, 8259A收到中断响应信号后将该中断从 IRR 寄存器中去除
*【step6】* CPU通过INTA给8259A发送信号,获取中断对用的中断向量号, 8259A将由外设发出的中断信号分配一个向量号(注意: 外设只知道发送中断信号,但是不知道自己的中断向量号)
中断框架
中断中很多都是硬件支持的。中断描述符表寄存器IDTR是硬件支持的,TSS结构也是硬件支持的,进入中断时的上下文环境保护是需要压栈的,而要压入哪个栈中是由TSS提供的,TSS数据结构是由程序员构造由CPU直接使用的,进入中断找到正确的栈以及中断的压栈过程和弹栈过程都是硬件支持的。*对于操作系统开发人员来说,主要做的就是 ① 构造好IDT, ②提供中断向量号*
如何通过8259A实现中断处理,实现的是哪个外部可屏蔽中断处理?
Step1:用汇编语言实现中断处理程序
Step2:创建中断描述符表IDT
Step3:实现IO端口,8259A和任何硬件的控制都需要端口
Step4:设置8259A
Step5:加载IDT,开启中断
时钟中断
*时钟分为内部时钟和外部时钟*
内部时钟由位于主板上的晶体振荡器产生,经过分频后就是主板的外频,将外频×倍数=主频。处理器执行指令的时钟周期都是基于主频的。内部时钟是无法改变的。
外部时钟指处理器和外部设备之间通信采用的一种时序,外部时钟和内部时钟是两套独立运行的定时体系,但是要依据内部时钟设置外部时钟。
*设置外部时钟有两种方式*
*【方式一】*:软件实现,while空轮转,通过消耗时钟周期实现的
*【方式二】*:硬件实现,即用定时器设计(使用8253芯片实现)
定时器到达所计数的时间,发送一个定时信号,硬件定时器是不占用处理器时间的。计数器根据时钟脉冲信号计数
可编程计数器8253
可编程计数器8253芯片内部结构如下,它有3个独立的计数器,本操作系统中只使用了计数器0,计数器0专门用来产生时钟信号,这个时钟信号就是连接在8259A芯片的IR0引脚上
注意:代码并没有指定8253的输出引脚要接在8259A的IR0引脚上,因为8259A位于南桥芯片的主板上,它的引脚的连接已经直接由内部硬件电路写好了,不需要重新安装,不需要担心外设是否和它连上,也就是说8253发出信号的时候,一定会被8259A的IR0引脚接收到
下图是8253的内部结构图,对8253的编程主要是对其控制字寄存器进行编程,设定它的工作模式以及设置寄存器初值来设定其发出信号的频率

键盘中断
键盘中断由8259A 的IRQ1接收

*在键盘上按下一个键后,操作系统的工作流程是什么?*
键盘是一个独立的设备,在这个设备的内部,存在一个键盘编码器芯片8048,每当键盘上发生按键操作,它就向****键盘控制器8042****报告哪个按键被按下,按键是否被弹起。键盘控制器位于主机内的主板上(大体上应该在南桥芯片中),它接收来自键盘编码器的按键信息,解码后保存,向中断代理8259A发送中断信号。
*8048是键盘编码器芯片*,它监控键盘上的按键操作,通码:按键被按下,断码:按键被弹起;所以一个键的扫描码是通码+断码。8048、8042、8259A他们是独立的处理器,都有自己独立的寄存器和内存,关系如下图:

8042、前面的计数器,他们都是集成在主板内的南桥芯片中,8042是键盘控制器,也就是键盘的IO接口,它是键盘的代理,因为8042和处理器都位于主机的内部,所以可以通过端口直接通信
8048芯片做了什么工作?
8048监控键盘上什么键被按下,什么键被放开。键盘扫描码有三套,常用第二套,兼容第一套。键盘扫描码是对键盘的编码,每个一键都有对应的通码和断码,当某个键被按下时,8048向8042发送第一套按键扫描码的按键的通码,8048收到通码后将其转换成第一套键盘扫描码对应的通码,然后存放在自己的缓存区中,向中断代理8259A发送中断信号,随后处理器执行中断处理程序,中断处理程序从8042的缓存区中获取数据然后执行相应的操作,例如在显示器上展示;如果按键持续不松手,那么8048会持续向8042发送按键通码,8042每次将其转换成第二套扫描码的通码,然后向中断代理发送中断信号,然后执行相应的中断处理程序;当按键松开时,8048按照第二套扫描码给8042发送断码,8042接收到将其转换成第一套按键扫描码的断码并存储在自己的缓冲区中,然后向中断代理发送中断信号,断码执行的中断处理函数是忽略。所以每按一个键至少产生2次中断。
8042做了什么?
8042是集成在主机的南桥芯片中主机可以通过端口直接和它进行通信,8042又是独立的芯片,它有自己的寄存器和内存,他有4个8位寄存器(输出缓冲区寄存器、输入缓冲区寄存器、状态寄存器、控制寄存器),处理器可以通过它实现对8048的工作方式,然后让8048的工作成果通过8042回传给处理器。在本操作系统中,8042接收8048的数据,将这个数据按照第一扫描码进行解码放进自己的缓冲区中,然后发出中断信号给中断代理8259A,处理器获取8042缓冲区中的字符扫描码,执行中断处理函数