IA架构
接下来想写一系列关于Linux和Intel架构的文章,所以先在本篇引入一点。一般说到Intel会想到它的CPU,可能它是Intel架构(可认为与IA/x86架构同义,之后处于习惯会用x86架构指代)的核心,但是计算机要正常工作还需要很多硬件配合,及芯片集与外设,而Intel架构就是由处理器与相关芯片组成,在以前伙伴芯片集被分为北桥与南桥,之后分别升级为MCH(Memory Controller Hub)与ICH(I/O Controller Hub),再之后MCH集成进处理器了,ICH演变为PCH(Platform Control Hub)并通过DMI(Direct Media Interface)与处理器相连,如下图为某系列的架构:
本篇的中断就会涉及处理器与PCH里的LCP(Low Pin Count)。在正式描述中断前,先简单提一下地址空间,在CPU视角,它有两个地址空间,64K的IO空间和与处理器位数相关(如在32位下为4GB)的MEM空间,我们一般所说的物理地址也是指这个MEM空间的地址,除此之外每个外设有它们自己的地址空间(如PCI设备有配置空间和自己的寄存器/RAM),内存也有它的地址空间。我们是从CPU视角使用内存,在访存时并不是所有空间都能用,也并不是所有地址映射到内存条的地址空间,它的映射关系一般由BIOS在启动时建立,并通过UMA(Unified Memory Architecture)管理,如下是一种典型的内存映射图:
如上可见并不是所有空间都映射到内存了,而且内存也不是完全连续映射的,有些空间映射到了设备上,比如当CPU访问0xA0000到0xBFFFF之间的地址时,当地址写入总线上时,VGA设备判断是访问它,于是会将该地址转换为它内部的地址并响应CPU。在之后的PCI里还会仔细描述,此处只需有这种概念:当CPU在访问某些地址时,并不是访问内存,而是访问到了某些设备的RAM/REG,因此对这些地址进行读写就能操纵设备。
中断
x86架构的处理器是中断驱动的,它在每执行完一条指令,就会判断是否有新的中断需要处理,若需要再调用对应的中断服务例程去处理该任务,这样就不必通过一直轮询这种低效的方式获取结果。中断在底层中占据极重的地位,当前可能涉及三种中断实现:可编程中断控制器(PIC),高级可编程中断控制器(APIC)与PCIe中的消息信号中断(MSI/MSI-x),本文将详细描述前两种中断,第三种其实依赖于APIC,这将在PCI相关的文章中叙述。
CPU把中断分为两类:interrupt与exception,前者由外部硬件产生,产生时间是随机的,后者是软件执行时出现异常或执行了特殊指令产生的,它的产生是可确定的,如下图:
在谈及设备驱动时主要关注的是可屏蔽中断,它是由外设产生的,并由可编程中断控制器(PIC)传递给处理器,本文的中断硬件主要关注这一部分。另外Int可以发出任何中断,但是有些硬件产生的中断会在栈上放入错误码,中断服务例程会在返回前先弹出错误码,而INT永远不会放入错误码,这会导致程序执行错误,此处要小心。
另外中断中经常出现边沿触发(edge triggered)与电平触发(level triggered),它和其他地方的概念类似:
- 边沿触发:在一个计时周期内电信号出现变换,如低电平变高电平是上升沿变换,反之为下降沿变换,这种变换是在一个周期内比较两次电平来检测的,因此若中断被屏蔽,则这个中断信号就会被丢弃。
- 电平触发:在一段时间内电信号保持低电平或高电平状态,它会一直有效,即使中断被屏蔽,之后开中断后依然能识别并处理它。
三种模式的中断
实地址模式
实模式下的中断朴实无华,它使用从地址0x00开始的中断向量表(IVT)里的中断服务程序处理中断,IVT是4*256大小的数组,它的每一项是一个地址,包括两字节的段地址与两字节的偏移,中断到来时它根据中断向量号索引IVT获取程序地址并切换到该地址执行。
保护模式
在保护模式处理器不再使用固定地址的中断服务例程,它使用中断描述符表寄存器(IDTR)指向中断描述符表(IDT),再通过它里面的中断描述符信息取得中断服务例程,如下:
与实模式不同,尽管IDT也是长度为256的数组,但是它存放的不再是4字节的地址,而是一个更复杂的结构-中断描述符,X86把中断描述符分为三种类型,如下图:
- TaskGate:任务门中存放的是TSS段选择子
- InterruptGate:中断门和陷阱门存放的都是中断服务例程的代码段选择子与入口偏移位置,区别是中断门会清除IF标志
- TrapGate:陷阱门不会清除IF标志
注意,这三种描述符都有个DPL用于做权限校验,而段选择子的RPL可用于判断是否切换到了内核态,这种切换意味着需要切换栈。任务门本身就会切换栈,而中断与陷阱门只有在用户态切换到内核态时才会切换栈,切换栈时它根据TSS获取内核栈,切换后将原始SS/ESP压入栈,并将EFLAGS/CS/EIP压入栈,若硬件需要产生错误码也将其压入栈。原来是内核态的就不必再切换栈,它们后续过程一致,如下图:
这些步骤都是由硬件完成的,中断返回使用IRET指令,它做相反的操作(除了错误码需要手动弹出),恢复PC与栈。如下处理器将前32个中断向量分配到固定的异常与不可屏蔽中断,其他的异常可由用户定义,如0x80用于系统调用,其他向量号分配给可屏蔽中断:
注意:0x08 Double Fault尽管名字有Fault,但是它的类型与Abort,表示要串行处理不支持串行处理的异常时,只能终止该过程。正常情况下linux内核只产生缺页异常,缺页异常不会再产生异常,用户态产生异常后进入内核,在内核可能会产生缺页异常,因此异常最多叠一次。但有时因为某些错误会导致异常堆叠,当堆叠的异常类别不兼容时就无法串行执行了,详见Intel-SDM-Vol. 3A 6-33。
当处理中断时,CPU会自动将部分寄存器压入栈,而剩下的寄存器需要按需保存:
如图上图为32位时硬件执行的入栈情况,若不存在特权级切换,它会把EFLAGS,CS/EIP,Error Code入栈保存。若存在特权级切换,它会在切换栈后将之前的SS/ESP先压入栈,随后和不切换栈一样。
虚拟8086模式
没见过,暂时略。
中断硬件
下面描述的参考7系PCH手册,该PCH上集成了两个级联的8259 PIC以及一个IOAPIC。
PIC
如下图,这是在8086时期出现的8259A中断控制器,所谓可编程就是传入的是硬连线对端设备的中断请求,输出的是解码后的中断向量,它们之间的映射关系是随时可修改的。它有8个中断输入引脚IR0-IR7可最多连接8个中断输入线,或将IR2连接到另外的次级8259A通过级联增加可支持的中断数。右侧是两种不同封装形式,学校遇到的是右上角的DIP封装,左图是内部电路,还有个EOI不在图中,它用于接收中断完成信号:
先看Figure1的右下角那三个寄存器,它们都是大小为8位的Bit Map REG,每一位与IRQ对应,如下:
寄存器 | 含义 | 说明 |
---|---|---|
Interrupt Request Register | IRR中断请求寄存器 | 当外设有新的中断产生时,相应位置位,对于边沿触发,若出现上升沿的变化则置位;对于电平出发,若为高电平则置位。它记录外设原始的中断状态,不受IMR的影响。 |
Interrupt Service Register | ISR中断服务寄存器 | 它记录CPU正在服务的中断号,一般来说它的最高位表示正在处理的中断,若有新的中断产生,PIC可通过与ISR比较查看是否需要先处理新中断,若是则ISR就可能有多位被置位。 |
Interrupt Mask Register | IMR中断屏蔽寄存器 | 当它对应位被置位时表示屏蔽对应的中断请求,此时PIC不会把被屏蔽的中断提交给ISR。(注:将IMR全部置位可表示禁用PIC。) |
一种常见(Fully Nested and Non-specific EOI模式)的处理流程是:
- 外设与8259某引脚相连,外设发生中断会被8259接收,8259将中断请求引脚对应的位送入中断请求寄存器IRR(若IRR对应位已经被置位则相当于上次的中断请求被丢弃了,这一般发生在边沿触发方式下,电平触发只有完成一个请求后才会再次触发新的中断)。
- IRR中可能存在多个请求,此时优先级解析器会通过
IRR&~IMR
获取需要处理的中断,并通过优先级策略选择优先级最高的位(其实也就是看新中断是否被IMR屏蔽以及是否是当前请求中优先级最高的中断),再将该位与ISR的最低位进行比较,若小于ISR(或者ISR无数据代表没有正在处理的中断)则表示新中断未被屏蔽。 - 当新中断未被屏蔽,8259A的INT引脚连接到处理器的INT#引脚用于告知CPU有新的中断发生,若CPU的中断使能位(IF)置位则通过INTA脉冲返回响应。(IF位可使用STI使能,使用CTI禁用。)
- 8259在收到第一个INTA脉冲时,会将新中断对应位放入ISR并将IRR对应位清除。
- 之后CPU发出第二个INTA脉冲,此时8259就将新的IRQ与基址进行编码获得中断向量,再将中断向量放入数据总线传递给CPU。
- CPU在处理完中断后会发送EOI命令告知8259A,8259就会把ISR优先级最高位清空(电平触发时,还需要告知外设中断已被处理,这一般是通过写外设特定寄存器实现),之后会进行下一个循环。
8259支持多种操作模式与屏蔽模式,在不同的模式下可能有不同的过程,下面会介绍常见的情况,在此之前先说明它的命令,它的寄存器通过PIO的方式映射到CPU地址空间,并且地址是固定的,一共有6个相关的地址,如下:
IO端口 | 含义 | 支持的指令 | 说明 |
---|---|---|---|
0x20/0xA0 | 主/从控的1号寄存器,根据osdev将其命名为CMDR。 | ICW1 | 当写入的bit4为1时表示为ICW1指令,之后会从DATAR接收后续指令。 |
OCW2 | 当写入的bit4为0,bit3为0时表示这是个OCW2指令。 | ||
OCW3 | 当写入的bit4为0,bit3为1时表示这是个OCW3指令。 | ||
0x21/0xA1 | 主/从控的2号寄存器,根据osdev将其命名为DATAR。 | ICW2 | 当向CMDR写入ICW1时,会顺序写入ICW2,ICW3,ICW4。 | ICW3 | ICW4 | OCW1 | 当直接对DATAR进行读写时,是执行的OCW1。 |
0x4D0/0x4D1 | 主/从控的触发模式位寄存器(Edge/Level Triggered Register),表示相应的IRQ的触发方式,对应位为0时表示边沿触发,反之为电平触发。 | ELCR(R/W) | - |
其中所涉及的指令解释如下:
操作 | 含义 | 指令 | 说明 |
---|---|---|---|
Initialization Command Words | 主/从控的初始化命令字,在操作PIC前需要先设置它,设置时需要连续向它写入4个8bit的字 | ICW1(WO) | bit4为1表示这是一个初始化序列,bit1为0表示级联了两个8259,bit0为1时表示有ICW4,其他位置0。 | ICW2(WO) | bit7:3表示该控制器的中断向量号基地址,即8位对齐的,如BIOS通常将主控的IRQ0-IRQ7映射到中断向量的0x08-0x0f,此处的基地址就是0x08。bit2:0在此处写0,而在第二个INTA脉冲时会被填充为IRQ。此处可见PIC的中断向量号是连续的。 | ICW3(WO) | bit2置位表示使用了级联方式,其他位为0。 | ICW4(WO) | 在ICW1指定时可设置ICW4,ICW4用于指定8259的工作模式,bit4置位表示处于SFNM,一般它是0。bit1置位表示为AEOI模式,相比于上面提到的中断处理过程,AEOI模式在收到第二个INTA脉冲时就清空ISR对应位,而不必等待处理器的EOI命令。bit0置位表示用于IA架构。其他位置0。 |
Operation Command Words | 主/从控的操作命令字 | OCW1(R/W) | 读写IMR。 | OCW2(WO) | 不像ICW,OCW2和OCW3不是顺序写的,它通过bit4:3指示,00表示OCW2,01表示OCW3。bit7:5用于表示要设置操作模式或要执行的命令类型,如001表示普通EOI模式下的EOI命令,100表示设置为Rotate in Auto EOI模式,关于模式和命令相见PCH手册5.8.4节。bit2:0根据bit7:5的命令类型所需,来表示命令的目标IRQ号,不需要时设为0。其他详见手册。 | OCW3(WO) | bit6表示SMM,用于执行特殊的屏蔽操作,正常情况下中断处理时会屏蔽优先级小于或等于自己的中断,但是该模式下可以指定。bit5为ESMM用于启用SMM。bit1为1时,bit0将生效,bit0为1表示之后读CMDR时获取的是ISR里的值,反之为读IRR(默认)。 |
表中提到Rotate模式,它是中断优先级设置的一种方式,默认是固定优先级,此时IRQ号越小优先级越高,如IRQ0的优先级最高。而rotate表示轮换的方式,当一个IRQ中断被处理后,它的优先级降为最低,它的下一位就会变为最高,依次排列,如IRQ0刚被处理,那么它的优先级将会成为最低,IRQ1为最高,IRQ2次之,以此类推,此时若同时出现IRQ0和IRQ2,那么会先处理IRQ2,之后IRQ2的优先级最低,IRQ3最高。
下图是一种典型的PIC与外设连接情况,其中Internal的输入源是固定的,如IRQ0是内部的计时器,而IRQ2是连接从PIC的:
APIC
8259A只支持最多15个IRQ,并且在多处理器系统上只能绑定到一个固定的核上,这导致所有外设的中断只能由固定的核心处理,现在已经很少见到它的身影了,取而代之的是高级可编程中断控制器APIC,如下图,它由两部分组成,位于每个core里的Local APIC和位于芯片集中的I/O APIC,外部设备一般连接到I/O APIC,I/O APIC可起路由作用,并通过系统总线将中断请求传递给LAPIC,LAPIC会根据LAPIC ID来决定是否处理:
上图有两种数据,I/O APIC传递给LAPIC的中断消息Interrupt Message与LAPIC之间传递的处理器间中断IPI,它们都通过系统总线传输,后面会详细介绍它们。
注:APIC分为多个版本,最初出现在P6家族的是APIC,它其实使用的是使用APIC串行总线,这种专用总线虽然不会争用系统总线但增加了布线难度,后来出现的xAPIC以及其后的x2APIC使用了系统总线,xAPIC与x2APIC有个显著的不同是前者使用MMIO进行控制而后者使用MSRs,本文采用xAPIC的结构,但是不对这三种版本作区分。
Local APIC
LAPIC位于处理器之上,每个核拥有一个,如下它由多个寄存器,引脚,传感器与硬件控制逻辑等部件组成,并连接到系统总线上:
在引脚上主要关注LINT0/1,它是可编程的,但一般用于实现以前处理INTR/NMI引脚的功能,若外部还使用旧的8259A PIC,则它可以连接到这个引脚,向该引脚发送中断请求时,可以指定0-255中所有的中断向量(注意:它使用的是在INTA脉冲传递中断向量,和LAPIC使用的方式不同,LAPIC本身只能收发16-255的中断向量),这些中断向量是从系统总线上获取的,详见上一节。另外和软中断一样,LINT与INTR也不会产生错误码。 而这些寄存器会被映射到物理地址空间,软件直接通过MOV等指令读写寄存器来使用APIC,它们默认被映射到0xFEE0000开始的4K空间,可通过MSR进行修改,如下图:
下面是默认情况下寄存器的映射位置,注意部分寄存器有256比特宽占了多行,但是不要想着使用MMX等指令访问: 1.Local APIC ID Register:存放了LAPIC的ID,IPI与I/O APIC使用该值指示期望那个核处理信息,一般系统也用它作为CPU的ID。尽管它有32位长,但是APIC只使用了4位,因为有一种代表广播,因此APIC只支持最多15个逻辑核,后来xAPIC扩展使用到了8位,x2APIC才完全使用了32位。
2.Local APIC Version Register:存放了LAPIC的版本,本地向量表个数等信息,不同版本支持的LVT是不一样的,本文采用现在常见的Nehalem架构,有7个
如下是的本地向量表定义,它有七项,对应着七个本地中断源:
上图已描述了每一位的作用,其他位都比较清晰易懂,只有Remote IRR表示电平触发时的中断请求,另外涉及的几个模式如下:
Bit位 | 值 | 含义 |
---|---|---|
Delivery Mode | 000 Fixed | 固定模式,只有它会使用Vector域,将它作为中断向量号投递给CPU。 |
010 SMI | 通过处理器的SMI信号路径投递SMI中断。 | |
100 NMI | 投递NMI中断,使CPU执行2号里的不可屏蔽中断处理程序。 | |
101 INIT | 投递INIT信号,使CPU执行初始化操作。 | |
110 Reserved | - | |
111 ExtINT | 连接8259A时使用,因此CPU需要INTA脉冲去获取获取中断向量。 | |
Delivery Status | 0 Idle | 当前无中断请求,或中断正在被处理。 |
1 SendPending | 中断正被发送给CPU但是CPU还未接受。 | |
Timer Mode | 00 | 一次性记时模式,记时完成发送中断后,需要软件重新设置开启新的一次记时。 |
01 | 周期性记时模式,记时完成后会自动重新开始记时。 | |
10 | 使用IA_TSC_DEADLINE MSR的值作为。 | |
11 | - |
3.Task Priority Register:任务优先级寄存器,它可用于暂时屏蔽一些高优先级中断,中断根据中断向量号决定优先级,它的前4位是优先级类,后4位是该级里的优先级排序,它们都是数字越大优先级越高。当前正在处理的中断位于ISR的最高位,通过设置TPR可屏蔽更高优先级的中断,阻止它中断正在处理的低优先级中断:
4.Processor Priority Register:处理器优先级寄存器,它真正决定能处理哪些中断,它由TPR与ISR共同决定,取它们的最大值:
5.Interrupt Command Register:中断命令寄存器用于发送处理器间中断IPI,它由两部分组成,定义如下:
使用时先写高双字再写低双字,此处指说明Destination Mode,它分为物理模式和逻辑模式,前者使用APIC ID作为目的地址,后者依赖于处理器自己设置的逻辑编号,见LDR&DFR。
6.Logic Destination Register/Destination Format Register:它们两个用于决定处理器的逻辑地址,LDR保存地址值,DFR表示地址类型,如平坦模式时,处理器收到IPI里的目的地址(消息目的地址MDA)后会与LDR进行与操作,若结果不为0则要处理它,除了平坦模式它还支持集群模式:
7.Arbitration Priority Register:仲裁优先级寄存器用于最低优先级分发模式,该模式不显式指定目的地址,而是由系统选出最低优先级的处理器去接受IPI,它只存在于P6系列,在奔腾上芯片集会通过一个特殊的总线周期获取每个逻辑核的优先级:
8.IRR/ISR/TMR:前两者和PIC一致不赘述,触发模式寄存器TMR(Trigger Mode Register )用于记录IRR对应的中断的触发模式,边沿触发为0而电平触发为1,这样就可以在电平触发时,给I/O APIC发送EOI信息。
9.Spurious-Interrupt Vector Register:伪造中断向量寄存器,用于在发起INTR时TPR被设为更高级,此时需要发送一个伪造的中断向量,其格式如下:
最后康康中断接收流图爬:
由于2xAPIC也常见,这里贴一下它的寄存器地址:
I/O APIC
它位于芯片集里(现在处理器上也有单独的IO APIC,如集显等会使用它),更准确的它和PIC一样位于LPC下,它主要做路由功能,连接外设并将外设的中断请求转换为中断消息发送给对应的LAPIC,对它也只需要看它的寄存器,它的寄存器分为直接寄存器与间接寄存器,直接寄存器采用MMIO的方式映射到MEM地址空间,而间接寄存器未映射到CPU的地址空间无法直接通过地址访问,它需要配合直接寄存器访问(类似PCI的配置空间),直接寄存器如下图:
如图下划线表示这部分地址是可变的,通过写CCR 0x31FE-0x31FF偏移处的OIC寄存器的7:0位指定(CCR默认起始为0x00),另外OIC的bit8可用于使能IOAPIC。先看EOIR,它只用到低8位,向它写入的数据表示中断处理完成的中断向量号,它将清除对应重向位表的IRR位。IND为索引寄存器,DAT为数据寄存器,IND里面存放要访问的间接寄存器的索引,向IND里写入它们的索引后就可以DAT访问间接寄存器里的内容了,间接寄存器索引信息如下图:
此处可见有三类寄存器,含义如下
-
ID:bit27:24表示IOAPIC的ID,在使用IOAPIC前必须先设置该值。
-
VER:存放IOAPIC的版本信息,bit23:16表示最大支持的重定向条目数,即支持的中断引脚数,此处它存放的是最大值索引,即0x17表示0x17+1=24个中断引脚,该位可被修改为更小的值来限制可用的条目数。bit7:0表示版本号。
-
REDIR:一共有24个重定向表寄存器,每个和一个中断引脚对应,它的详细结构如下:
Bit位 | 含义 |
---|---|
63:56 | 目标处理器的地址,若是物理地址则使用bit63:59表示,逻辑地址用bit63:56。 |
55:48 | 扩展目标ID。 |
47:17 | - |
16 | IMR位,若该位置位中断将直接不被处理。 |
15 | 置位时表示电平触发,否则为边沿触发。 |
14 | IRR位,当出现边沿触发中断请求是该位置位。 |
13 | 置位表示低电位有效,否则为高电位。 |
12 | 表示当前中断提交的状态,当有中断正在被处理时置位。 |
11 | 目标模式,0表示物理地址,1表示逻辑地址,它的含义同LAPIC。 |
10:8 | 分发模式,类似LAPIC。 |
7:0 | 该项对应的中断向量。 |
注:1.还经常听到一个和中断相关的词MSI/MSI-x,它是PCIe规范里提出的一种产生中断方式,它在硬件上还是依赖了APIC,可以认为MSI把IOAPIC的功能集成到设备里了,在之后的PCI里会介绍。
2.在高性能场景经常听到CPU亲和性,也是通过将某中断固定交给某逻辑核处理来实现的。
小节
本文大部分内容都是手册上的,但是并没有完全把手册抄上来,因此若有没提到看手册就好了,若手册没提到可看虚拟机实现的代码,如KVM,尽管它们不会百分百一致地实现每个功能,但是这是软件兼容的,不会妨碍我们理解。
参考
- 《WhitePaper Introduction to Intel® Architecture》
- 《8259A PROGRAMMABLE INTERRUPT CONTROLLER》
- 《Intel® 7 Series / C216 Chipset Family Platform Controller Hub (PCH)》
- 《Intel® 64 and IA-32 Architectures Software Developer’s Manual》
- https://wiki.osdev.org/PIC,https://wiki.osdev.org/APIC