多处理器下的中断机制
扫描二维码
随时随地手机看文章
INTERRUPT
中断是硬件和软件交互的一种机制,可以说整个操作系统,整个架构都是由中断来驱动的。中断的机制分为两种,中断和异常,中断通常为 设备触发的异步事件,而异常是 执行指令时发生的同步事件。本文主要来说明 外设触发的中断,总的来说一个中断的起末会经历设备,中断控制器,CPU 三个阶段:设备产生中断信号,中断控制器翻译信号,CPU 来实际处理信号。本文用 的实例来讲解多处理器下的中断机制,从头至尾的来看一看,中断经历的三个过程。其中第一个阶段设备如何产生信号不讲,超过了操作系统的范围,也超过了我的能力范围。各种硬件外设有着自己的执行逻辑,有各种形式的中断触发机制,比如边沿触发,电平触发等等。总的来说就是向中断控制器发送一个中断信号,中断控制器再作翻译发送给 , 再执行中断服务程序对中断进行处理。中断控制器
说到中断控制器,是个什么东西?中断控制器可以看作是中断的代理,外设是很多的,如果没有一个中断代理,外设想要给 发送中断信号来处理中断,那只能是外设连接在 的管脚上, 的管脚是很宝贵的,不可能拿出那么多管脚去连接外设。所以就有了中断控制器这个中断代言人,所有的 外设连接其上,发送中断请求时就向中断控制器发送信号,中断控制器再通知 CPU,如此便解决了上述问题。中断控制器有很多,前文讲过PIC
,PIC
只用于单处理器,对于如今的多核多处理器时代,PIC
无能为力,所以出现了更高级的中断控制器 APIC
,APIC
() 高级可编程中断控制器,APIC
分成两部分 LAPIC
和 IOAPIC
,前者 LAPIC
位于 内部,每个 都有一个 LAPIC
,后者 IOAPIC
与外设相连。外设发出的中断信号经过 IOAPIC
处理之后发送给 LAPIC
,再由 LAPIC
决定是否交由 进行实际的中断处理。可以看出每个 上有一个 LAPIC
,IOAPIC
是系统芯片组一部分,各个中断消息通过总线发送接收。关于 APIC
的内容很多也很复杂,详细描述的可以参考 开发手册卷三,本文不探讨其中的细节,只在上层较为抽象的层面讲述,理清 APIC
模式下中断的过程。计算机启动的时候要先对 APIC
进行初始化,后续才能正确使用,前面说过初始化就是设置一些寄存器,这部分我在再谈中断(APIC)有所讲解,本文关于寄存器这一块不会再详述,可以先看一看。下面来看看 APIC
在一种较为简单的工作模式下的初始化过程:IOAPIC
初始化IOAPIC
就是设置 IOAPIC
的寄存器,IOAPIC
寄存器一览:所以有了以下定义:#define REG_ID 0x00 // Register index: ID
#define REG_VER 0x01 // Register index: version
#define REG_TABLE 0x10 // Redirection table base 重定向表
但是这些寄存器是不能直接访问的,需要通过另外两个映射到内存的寄存器来读写上述的寄存器。内存映射的两个寄存器
这两个寄存器是内存映射的,IOREGSEL
,地址为 ;IOWIN
,地址为 。IOREGSEL
用来指定要读写的寄存器,然后从 IOWIN
中读写。也就是常说的 index/data
访问方式,或者说 ,用 index
端口指定寄存器,从 data
端口读写寄存器,data
端口就像是所有寄存器的窗口。而所谓内存映射
,就是把这些寄存器看作内存的一部分,读写内存,就是读写寄存器,可以用访问内存的指令比如 mov
来访问寄存器。还有一种是 IO端口映射
,这种映射方式是将外设的 IO端口(外设的一些寄存器)
看成一个独立的地址空间,访问这片空间不能用访问内存的指令,而需要专门的 in/out
指令来访问。通过 IOREGSEL
和 IOWIN
既可以访问到 IOAPIC
所有的寄存器,所以结构体 如下定义:struct ioapic {
uint reg; //IOREGSEL
uint pad[3]; //填充12字节
uint data; //IOWIN
};
填充 字节是因为 IOREGSEL
在 ,长度为 4 字节,IOWIN
在 ,两者中间差了 2 字节,所以填充 字节补上空位方便操作。通过 IOREGSEL
选定寄存器,然后从IOWIN
中读写相应寄存器,因此也能明白下面两个读写函数:static uint ioapicread(int reg)
{
ioapic->reg = reg; //选定寄存器reg
return ioapic->data; //从窗口寄存器中读出寄存器reg数据
}
static void ioapicwrite(int reg, uint data)
{
ioapic->reg = reg; //选定寄存器reg
ioapic->data = data; //向窗口寄存器写就相当于向寄存器reg写
}
这两个函数就是根据 来读写 IOAPIC
的寄存器。下面来看看 IOAPIC
寄存器分别有些什么意义,了解了之后自然就知道为什么要这样那样的初始化了。下面只说 中涉及到的寄存器,其他的有兴趣见文末链接。IOAPIC 寄存器
ID Register- 索引为 0
- :ID
- 索引为 1
- 表示版本,
- 表示重定向表项最多有几个,这里就是 23(从 0 开始计数)
IOAPIC
有 24 个管脚,每个管脚都对应着一个 64 位的重定向表项(也相当于 64 位的寄存器),保存在 ,重定向表项的格式如下所示:这是 大佬在他的 中总结出来的,很全面也很复杂,这里有所了解就好,配合着下面的初始化代码对部分字段作相应的解释。IOAPIC 初始化
#define IOAPIC 0xFEC00000 // Default physical address of IO APIC
void ioapicinit(void)
{
int i, id, maxintr;
ioapic = (volatile struct ioapic*)IOAPIC; //IOREGSEL的地址
maxintr = (ioapicread(REG_VER) >> 16)