CPU中断机制与硬件交互详解
[toc]
# 引言
在现代计算机系统中,CPU作为核心处理单元,需要与各种硬件设备进行高效协调工作。从键盘输入到网络数据传输,从磁盘读写到图形渲染,CPU必须能够及时响应各种硬件设备的请求,同时又不能因为等待这些相对较慢的设备而降低整体系统性能。这就引出了计算机系统中至关重要的机制——中断处理。
中断机制使得CPU可以在执行当前任务的同时,及时响应来自各种硬件设备的紧急请求。随着计算机技术的发展,从单核到多核,从简单设备到复杂外设,中断处理机制也在不断演进和优化。本文将深入探讨CPU与硬件设备交互的演进历程,从最早的中断控制器到现代APIC架构,以及如何通过CPU亲和力等技术优化多核系统的性能。
我是 SharkChili,Java 开发者,Java Guide 开源项目维护者。欢迎关注我的公众号:写代码的SharkChili,也欢迎您了解我的开源项目 mini-redis:https://github.com/shark-ctrl/mini-redis。
# 详解CPU与其他硬件交互的演进
# 中断控制器
计算机发展初期,CPU除了执行必要的指令以外,还需要和键盘、网卡、鼠标、硬盘等各种设备进行交互。鉴于这些设备执行速度与CPU不对等,为了避免CPU为了响应这些中断而进行等待,便提出了中断响应机制。实现方式也很简单,所有硬件设备都和中断控制器绑定,当其他事件需要CPU调度执行时,通过中断驱动程序发送信号,CPU在完成手头的指令后都会查看中断信号,若看到信号则直接响应中断:

因为响应中断时需要暂时放下手里正在执行的指令,所以为了能够在完成响应后继续执行线程的后续工作,CPU在响应中断前会将线程在各个寄存器中的数值保存到线程栈中,等待完成中断响应后取出恢复现场继续工作。

在特定情况下CPU不会响应可屏蔽中断,即当EFLAGS寄存器中的IF标志位为0时(正在执行优先级较高的事件或处于中断禁用状态)。但是一旦遇到NMI(Non-Maskable Interrupt)中断,这意味着系统发生致命错误或者硬件异常(如笔记本温度过高、电源断电)等异常情况,NMI中断是不可屏蔽的,CPU必须响应。
# 传统中断的缺陷和PIC的引入
随着计算机的演进,需要交互的硬件设备越来越多,于是设计者们引入了一个芯片专门处理不同的硬件中断控制——PIC(Programmable Interrupt Controller)即8259A芯片。PIC针对每个硬件都进行编号,也就是中断向量,同时针对每个硬件中断都配备对应的中断处理函数,最后将中断向量和处理函数地址进行映射,构建出一张IDT(Interrupt Descriptor Table)表存放于内存中(CPU缓存空间有限),并通过IDTR(Interrupt Descriptor Table Register)指针进行管理:

因为IDT这一设计理念非常出色,所以设计者后续也将一些异常响应中断(如除数为0、内存地址错误)及其处理函数也放到IDT表中。
# APIC的优化
但随着CPU核心数的增加,传统的PIC对于中断响应也表现得有些力不从心,于是整个架构演进为:
- 将PIC撤除
- 每个CPU内置一个APIC,对应名为Local APIC
- 外部配备一个I/O APIC
基于这个新架构,由I/O APIC处理外部硬件中断请求并根据分发策略交给多核CPU中的某个Local APIC,让Local APIC通知CPU处理中断。 同时因为Local APIC的存在,各个CPU之间也能进行相互的中断请求,这也就是IPI(Inter-Processor Interrupt),进一步提升了CPU之间各自通知中断响应的效率:

# 网卡中断的性能瓶颈与CPU亲和力的理念
互联网发展后在并发网络连接下,网卡单位时间内需要处理的网络数据越来越多。按照原有的BSP(Bootstrap Processor)即第一个CPU处理网络请求方案会导致CPU使用率偏斜,进而导致单核CPU过热的情况,于是提出了负载均衡的设计理念。但是新的问题又来了,网络消息一般都是来自特定的几个网络连接,每次CPU处理特定连接消息时就会将每个socket连接信息缓存下来,如果采用随机负载均衡策略,就会出现当前连接此刻在当前CPU处理,下一刻又在别的CPU处理,多核CPU缓存大概率失效,每次处理消息都必须从相对低效的内存中加载数据,使得执行性能大大降低。
所以考虑到负载均衡且提升网络消息包的处理效率,设计者们提出了CPU亲和力的设计理念:
- 操作系统提供API,进程或中断可以选择是否需要保证CPU亲和力
- 操作系统引入smp_affinity,使用者可以通过掩码决定当前中断绑定到哪个CPU
- APIC引入CPU亲和寄存器,用于实现第二点的适配
由此网卡对应的连接消息既可以按照分发策略交给不同CPU响应中断,并通过亲和力寄存器保证和CPU绑定避免缓存失效,高效处理连接:

对应的我们也可以键入cat /proc/interrupts查看系统如何分配中断给CPU,以MCP机器异常检查轮训来看,整体是负载均衡的:
CPU0 CPU1 CPU2 CPU3 CPU4 CPU5
......
NMI: 0 0 0 0 0 0 Non-maskable interrupts
LOC: 0 0 0 0 0 0 Local timer interrupts
SPU: 0 0 0 0 0 0 Spurious interrupts
PMI: 0 0 0 0 0 0 Performance monitoring interrupts
IWI: 1 0 0 0 0 0 IRQ work interrupts
RTR: 0 0 0 0 0 0 APIC ICR read retries
RES: 719671 736844 710157 718313 723883 724818 Rescheduling interrupts
CAL: 2770801 2519025 2333672 2228790 2230070 2189432 Function call interrupts
MCP: 1366 1366 1366 1366 1366 1366 Machine check polls
......
2
3
4
5
6
7
8
9
10
11
12
# 小结
CPU与硬件设备的交互机制经历了从简单到复杂、从单核到多核的演进过程:
中断机制的基础:中断机制是CPU与硬件设备交互的核心,通过中断控制器协调CPU与各种设备的通信,避免CPU在等待设备响应时浪费计算资源。
从PIC到APIC的演进:随着多核处理器的发展,传统的PIC中断控制器已无法满足需求,APIC架构通过Local APIC和I/O APIC的分工合作,实现了更高效的中断处理和CPU间通信。
性能优化策略:CPU亲和力机制通过将特定中断绑定到特定CPU核心,减少了缓存失效问题,提升了多核系统处理高并发网络请求的性能。
现代中断处理:现代系统还引入了MSI/MSI-X等更先进的中断机制,提供了更灵活的中断分发和处理能力。
理解这些机制有助于我们更好地进行系统调优和故障排查,特别是在处理高并发、低延迟的应用场景时。
我是 SharkChili,Java 开发者,Java Guide 开源项目维护者。欢迎关注我的公众号:写代码的SharkChili,也欢迎您了解我的开源项目 mini-redis:https://github.com/shark-ctrl/mini-redis。
# 参考
- Intel® 64 and IA-32 Architectures Software Developer's Manual
- Advanced Programmable Interrupt Controller (APIC) Documentation
- Understanding Linux Interrupts and CPU Affinity
- Linux Kernel Development by Robert Love
- Understanding the Linux Kernel by Daniel P. Bovet and Marco Cesati
- CPU中的寄存器是什么以及它的工作原理是什么?:https://zhuanlan.zhihu.com/p/865277160
- NMI中断概要 https://blog.csdn.net/weixin_45264425/article/details/128494605
- 中断描述符表(Interrupt Descriptor Table,IDT) https://www.cnblogs.com/qintangtao/p/3325985.html
- Linux 多核下绑定硬件中断到不同 CPU(IRQ Affinity)https://developer.aliyun.com/article/64868
- Linux系统中栈在进程、线程与中断处理中的核心作用 https://edu.51cto.com/article/note/14154.html