中断、轮询与 MSI-X:工控扩展卡事件通知机制如何影响延迟与 CPU 占用

  2026-11-07   hermes     3   0  

为什么值得把“中断”当成一个概念来理解

采集卡、网卡这类工控扩展卡在工作时,数据是设备主动搬进主机内存的,但主机怎么知道“数据到了”,取决于一件很容易被忽略的事:事件通知机制。同一个采集通道,用引脚中断通知和用 MSI-X 消息通知,代码可能一行都不用改,实际表现却可能差在延迟抖动、单核占用,甚至多核能不能真正并行上。

现场讨论这个问题时,常常把“中断”“向量”“队列”“轮询”混着用。下面先讲清三条事件通知路径的机制差异,再说明向量数、掩码粒度、CPU 亲和性、中断合并这几项指标为什么直接影响工控场景的性能判断。文中的规格与接口行为,取自 Microsoft 的 Windows 驱动文档、公开的 PCIe IP 用户手册,以及 Linux 内核关于 NAPI、SMP IRQ 亲和性与 MSI 的文档。

三条路径:轮询、引脚中断、消息中断

主机感知设备事件,本质上只有两种方式:主动去问,或者等设备来报。等设备来报这一条,又分“拉一根专用信号线”和“往内存写一条消息”两种实现。三种方式的能力边界差别很大:

机制事件如何到达 CPU向量粒度共享与掩码
轮询没有通知路径,驱动按自己的节奏读设备状态或队列不涉及向量不涉及中断共享
INTx 引脚中断设备拉中断引脚,经中断控制器路由给 CPU常规 PCI 每张卡最多 4 个中断多个设备可共用一条 IRQ 线
MSI设备向约定地址写一条消息,芯片组据此产生中断每条功能最多 32 个向量多个向量共用一个消息地址,靠消息数据区分
MSI-X同样是内存写,但每个向量有独立的地址与数据每条功能最多 2048 个向量每个向量可独立掩码,也支持整功能掩码

这张表只列了能力上限,实际能用多少,还要看设备实现、驱动分配和平台配置。下面逐条展开。

轮询:不通知,主动去看

轮询是最简单的一类:驱动在一个循环里读设备的完成标志或接收队列,有数据就处理,没有就继续读。它没有中断路径,因此不存在“中断丢了”“中断被别的设备抢走”这类问题,延迟也相对可预测——最坏情况基本由轮询间隔决定,而不是由中断响应时间决定。

代价是 CPU。轮询在空闲时同样消耗处理器时间,而且轮询越密、延迟越低、占用越高,这个折中是绕不开的。Linux 网络栈的 NAPI 是一种混合形态:先由一次中断把处理流程唤醒,随后把后续报文的收取切换成轮询,避免高包速率下每个报文都触发一次中断。内核文档还给出两种更极端的做法:一种是忙轮询(busy polling),在套接字路径上主动轮询而不等中断到来;另一种是线程化 NAPI,把 NAPI 处理搬进独立的内核线程,并可以把这个线程绑定到某个 CPU 核上。内核文档明确写到,忙轮询会让被轮询的那个核保持 100% 占用。

这个取舍对工控场景并不陌生:需要确定性响应的运动控制、触发响应类应用,宁可牺牲一个核也要换来稳定的响应时间;而多通道长时间采集更在意总吞吐和 CPU 余量,通常会选择中断加适度合并。

INTx:一根线,大家共用

传统 PCI 设备用中断引脚发信号,引脚电平被拉高或拉低,经中断控制器转换成处理器中断。常规 PCI 每张卡最多只有 4 个中断可用,这个数量在单功能设备上够用,在多端口、多通道设备上很快就不够了。

更麻烦的是共享。一条 IRQ 线可以被多个设备共用,中断到来时,挂在这条线上的每个处理程序都要被调用一次,各自判断“是不是我发出的”。这种判断本身有开销,而且当共享设备变多、事件变密时,无效调用会明显拉高 CPU 占用和响应抖动。PCIe 里已经不再有独立的引脚中断,INTx 是通过带内消息模拟引脚置位与复位来实现的,但共享语义沿用了下来。

MSI:把中断变成一次内存写

MSI 从 PCI 2.2 开始引入。设备不再拉引脚,而是往一个约定的内存地址写一个特定值,芯片组收到这次写操作后,向对应的处理器投递中断。中断从“带外信号”变成了“带内消息”,走的是和普通数据一样的路径。

MSI 有两个容易被误解的特性。第一,它是边沿语义:设备发出消息后,不会收到任何硬件层面的确认,中断是否被处理要靠驱动通过设备寄存器或队列状态去确认,而不能指望硬件回执。第二,消息里携带的那个值不是给驱动传数据用的,它只用于让芯片组判断该触发哪个中断、投递给哪个处理器,驱动拿不到这段内容。

在向量数量上,MSI 每条功能最多支持 32 个向量,而且这些向量共享同一个消息地址,靠消息数据里的编码来区分。对多队列设备来说,这个上限很快就成为瓶颈。

MSI-X:每个向量都有自己的地址和数据

MSI-X 由 PCI 3.0 定义,把 MSI 的“一个地址、多个编码”改成了“每个向量一套地址和数据”,每条功能最多可支持 2048 个向量。这个改动带来三个实际差别:

  • 向量可以真正对应到事件源。多队列网卡可以为每个接收队列分配一个向量,多通道采集卡可以为每个通道或每组通道分配一个向量,驱动不必在一个中断里遍历所有队列去找“是谁”。
  • 掩码粒度到单个向量。MSI-X 的向量表里每一项都有独立的掩码位,另有整功能的掩码位。需要临时屏蔽某一路事件、又不影响其他通道时,逐向量掩码是必要的。INTx 没有逐向量掩码的概念;MSI 的逐向量掩码属于可选能力,是否具备要看设备实现;MSI-X 则把这一项做进了标准结构。
  • 向量表放在内存里,由主机软件填写。MSI-X 能力结构给出向量表与 PBA 的定位信息(表所在 BAR 的编号与偏移、表大小),表本身位于设备 BAR 所指的内存空间,主机启动时按设备声明的大小写入每个向量的地址、数据和掩码。按公开的 PCIe IP 手册,表项大小为 16 字节,表大小由能力寄存器读出值加一得到,上限 2048 项,表基址需要 4 KB 对齐;待处理但尚未投递的中断记录在挂起位数组(PBA)里。

还有一点常被忽略:MSI-X 的每条消息本质上是一次单双字内存写,因此它和普通数据写遵循同一套内存写排序规则。这意味着设备侧必须先让数据写入可见、再发出中断消息,驱动在中断处理里读到的数据才是新的。这个顺序不是协议自动保证的“业务语义”,而是设备与驱动需要共同遵守的工程约定,也是自研板卡或自写驱动时最容易出错的地方。

四个指标,决定了机制能不能变成性能

有了上面三条路径,就可以把“中断机制好不好”拆成四个可检查的指标:

  • 向量数与队列数是否匹配。多队列的前提是每个队列能被独立通知。如果设备有 8 个接收队列却只分配了 1 个向量,驱动仍然要在一次中断里轮询 8 个队列,多核并行就无从谈起。反过来,向量数远超实际队列数也不会带来收益,只是多占资源。
  • 掩码粒度。能否只屏蔽某一路事件,决定了异常处理时会不会被迫停掉整条采集流。
  • CPU 亲和性。Linux 用 /proc/irq/编号/smp_affinity(位掩码)和 smp_affinity_list(CPU 列表)指定某条中断允许投递到哪些核;亲和性掩码不能把所有 CPU 都关掉,如果中断控制器本身不支持亲和性,这个值也不会从默认的全核掩码发生变化。把不同队列的向量分到不同核,才能真正把负载摊开。
  • 中断合并与节流。把多次事件攒成一次中断,能显著降低 CPU 占用,但会引入额外延迟。合并不是“关掉更好”,它取决于事件频率:低速小数据量场景下合并几乎没有收益,高包速率场景下不合并则会把系统拖进中断风暴。

为什么工控场景对这件事更敏感

通用服务器更关心平均吞吐,工控现场更关心两件事:确定性和资源余量。

先是确定性。产线节拍、外部触发与图像曝光的配合,靠的是响应时间的稳定性,而不是平均值。中断路径上的排队、共享判断、跨核迁移都会放大尾延迟,表现为“平时很快,偶尔卡一下”。轮询和固定亲和性之所以在运动控制类应用里常见,就是因为它们把这种不确定性压了下去。

其次是资源余量。一台工控机往往同时跑采集、通信和人机界面,中断处理占掉的 CPU 时间越多,留给应用的时间就越少。一个千兆口在 1500 字节帧、线速收包时每秒约 8 万个报文;如果每个报文都触发一次中断,中断处理本身就会吃掉可观的 CPU 时间,这也是高包速率场景必须启用合并或多队列的原因。

还有平台差异。老平台、经过 PCIe 桥扩展的插槽、或者 BIOS 资源分配不当的机器上,MSI-X 可能无法启用,设备可能先回退到 MSI,或者进一步退到 INTx 共享线。这时系统通常仍能工作,但延迟抖动和 CPU 占用都会变差,很容易被误判为“卡的性能不行”。Linux 内核提供了全局关闭 MSI 的启动参数,可以用来做一次反向验证:关闭后现象消失,问题基本就落在消息中断路径上,而不是设备的数据通路。

判断顺序:先看事件频率,再看并行度,最后看确定性

选型或调优时,不必一上来就比参数表,可以按三个问题依次判断:

先问判断依据对应的机制选择
事件有多密?每秒事件数:千级以下、万级、十万级以上低频可直接中断;高频必须合并或改用轮询为主的收取方式
需要几个核参与?队列数、通道数,以及应用能否按核拆分多队列需要向量数不少于队列数,并配合亲和性绑定
延迟要多稳?能否接受偶发的毫秒级抖动要求高则减少合并、固定亲和性,必要时独占一个核做忙轮询

三个问题的答案会直接排除掉一部分方案。比如一台需要 8 通道同步采集、同时跑视觉算法的工控机,如果采集卡只提供一个中断向量,那么无论驱动怎么写,8 个通道的处理都只能在一个核上串行排队;这种情况下换主板、换 CPU 都没有用,问题在设备的中断能力上。

四个常见误解

  • “MSI 会把数据一起带给驱动”:不会。消息里的数据只用于路由,驱动要拿数据仍然得读设备内存或 DMA 缓冲区。
  • “向量越多性能越好”:向量多只提供了并行和隔离的可能。驱动不按队列分配、核数不够、合并参数不合适时,多出来的向量不会自动变成吞吐。
  • “关掉中断合并一定更低延迟”:低事件频率下成立;高事件频率下关掉合并会把 CPU 拖进中断处理,整体响应反而更差。
  • “中断一定比轮询快”:单次事件的响应,忙轮询可以比中断更短,因为省掉了中断投递与上下文切换;代价是一个核被持续占满,且只在能接受这种资源占用的场景下成立。

现场怎么确认实际用的是哪条路径

不必拆开机箱,系统里就有足够的信息:

  • 在 Linux 上,lspci 的详细输出会列出设备声明的中断能力,包括是否具备 MSI-X 能力、当前是否启用、分配了多少向量;对照驱动的队列数,就能看出向量是否被真正用起来。
  • /proc/interrupts 按向量列出各 CPU 上的中断计数。计数集中在某一个核,说明亲和性没有生效或者驱动没有做分散;各核分布均匀,才说明多队列确实在并行工作。
  • /proc/irq/编号/smp_affinity 可以直接读到当前允许的 CPU 掩码,和预期不符时先查这里,而不是先怀疑设备。
  • 在 Windows 上,是否启用消息中断受驱动的注册表配置(例如 MSISupported)、系统版本与平台固件共同影响,需要结合驱动配置与系统信息一起看。

小结

中断、轮询与 MSI-X 不是三个可以互相替代的名词,而是回答同一个问题的三种答案:设备有事件时,主机怎么用最小的代价知道。判断一套方案是否合适,可以简化为三句话——事件频率决定该用中断还是轮询,并行度决定向量数够不够,确定性要求决定合并与亲和性怎么设。把这三件事分开看,很多“性能不稳定”的现场问题就能落到具体的一层上,而不是停留在换卡、换机的循环里。

客服微信:18025338165点击复制并跳转微信