ESXi直通SATA控制器的群晖虚拟机偶尔整池掉盘:irqbalance把直通控制器的MSI中断挪走了

家里的NAS是ESXi上跑的群晖虚拟机(黑裙),主板的Intel SATA控制器直通给它,挂了四块硬盘。这一年里它莫名其妙掉盘了六次:直通控制器上的四块盘同一秒一起掉盘,存储池直接crashed,重启一下又全好了。有时候隔3天,有时候隔半年多,完全摸不着规律。

之前每次都是重启了事,这次忍住没重启,登进去看了内核日志。结果硬盘和线都没毛病,是DSM自带的irqbalance想把直通控制器的中断挪到别的CPU上。真机上设备会照办,可ESXi直通的时候根本不理会这个改动,中断还是往开机时的位置送。内核这边一直留着旧位置所以没事,直到某一天把它释放了(do_IRQ: 0.145 No irq handler for vector),驱动再也收不到中断,四块盘的命令全部超时,最后被内核一个个禁用。

解决办法是启动irqbalance的时候加个--banirq,不让它动这个中断。改完重启,两分钟后NAS回来,存储池自己就组好了。下面记一下过程。

直通SATA控制器的MSI中断路径:平时中断送到CPU0;irqbalance迁移后ESXi仍然送到内核已释放的CPU0向量145,AHCI驱动收不到完成中断
左边是平时,右边是出事的时候。CPU和向量的编号是这次日志里的。

环境

部分配置
宿主机ESXi 8.0 U3(8.0.3 build 24859861),Xeon Silver 4210R,Intel C620芯片组的主板
虚拟机DSM 7.4.1-90080 Update 1,型号配置RS1221+,内核4.4.302+,8 vCPU
硬盘1、2VMware虚拟磁盘,挂在虚拟SATA控制器上。存储池1(RAID 1),放Docker和套件
硬盘3~6主板的Intel C620 SATA控制器(AHCI,8086:a182)直通进来。三块12TB组的存储池2(SHR),还有一块1TB没进池

DSM的系统分区在每块盘上都有一份,所以直通的盘全掉了系统也还活着,网页能开,/var/log也还在。

问题现象

  • 存储管理器里存储池2显示已损毁,HDD/SSD里只剩两块虚拟盘。
  • 通知里有一条Drive slot has been disabled.
  • 存储池1上的Docker容器没受影响,存储池2上的共享文件夹全挂了。

DSM的日志(系统日志和硬盘日志按时间放一起了,都是原文):

四块盘是同一秒掉盘的。

往前翻了下日志,同样的掉盘一共六次:

掉盘时间(UTC)距上一次怎么恢复的
2025-11-06 12:16—重启
2025-12-27 23:0551天重启
2026-03-02 04:1665天重启
2026-03-29 21:0927天重启
2026-04-01 17:133天重启
2026-11-10 19:46223天这次

四块盘是两个牌子四个型号,不可能同一秒一起坏,两块虚拟盘又从来没事,所以肯定不是硬盘本身的问题,得往控制器、直通或者供电上找。

SSH也登不上了

想登进去看日志,结果密钥登录直接被拒:

密钥没问题,ssh -v能看到公钥发过去了,就是服务器不认。想了一下才反应过来:homes共享文件夹在存储池2上,authorized_keys跟着一起没了(

root的家目录在系统分区,不受影响。用密码登上去sudo -i,把公钥加到root下面就能用密钥登root了:

这时候先别重启。重启存储池是能回来,但/proc/interrupts这些现场就没了,这次能查出来全靠它。另外存储池挂了以后btrfs会疯狂往kern.log里刷报错,我这一小时刷了二十多万行,日志只留9份,出事那几分钟的记录过几个小时就被冲掉了,最好先另存一份。

背景知识

看日志之前先说几个东西,熟悉的可以直接跳过。

中断(IRQ)是硬件有事通知CPU的方式。比如硬盘控制器把一个读写命令做完了,就发一个中断,内核里的驱动(这里是AHCI驱动)收到了才知道命令完成。驱动发完命令就在等这个中断,等不到就只能超时。

/proc/interrupts里每一行是一个中断号,后面是每个CPU处理过多少次,最后是哪个设备的。

以前PCI设备的中断是走专门的中断线的(INTx),几个设备可能共用一根线,再经过IOAPIC转给CPU。现在的PCI设备基本都改用MSI(Message Signaled Interrupts)了,不用中断线,设备要发中断的时候就往一个特定的地址写一个数,这次写操作本身就是中断。

这个地址和数就存在设备的配置空间里,是驱动加载的时候内核给设的。x86上地址是0xfeeXXXXX这种,里面带着目标CPU的APIC ID,数据里是向量号(vector)。每个CPU有一张向量表,内核在里面登记哪个向量归哪个中断处理。所以一个MSI中断送到哪个CPU的哪个向量,就是设备里这两个值说了算,lspci -vv能看到。后面日志里的0.145就是CPU 0的145号向量。

一个中断由哪个CPU处理叫affinity,是可以改的。irqbalance是个后台程序,隔一会儿看一下各个CPU的中断负载,把中断在CPU之间挪来挪去,让负载平均一点。挪一个MSI中断的时候,内核要在新的CPU上分一个新向量,再把设备里的地址和数据改掉。

地址和数据是两个寄存器,要分两次写。有的设备支持MSI屏蔽(MSI-X是每个中断都自带屏蔽位),改之前可以先把中断挡住,改完再放开。不支持的(lspci里显示Maskable-)只能直接改,改到一半设备正好发中断的话,就可能送错地方。

直通是ESXi把物理设备直接交给虚拟机用,虚拟机里的驱动直接操作这个设备。不过中断怎么送进虚拟机是ESXi管的,它按虚拟机给设备设的目标(哪个CPU、哪个向量)来送。所以虚拟机里改了MSI的目标,ESXi那边也得跟着改。

内核日志

四块盘一起掉盘,能想到的大概三种情况,日志里的样子不一样:

可能的原因内核日志里会看到什么
直通控制器的中断丢了命令超时,复位后链路能正常up,但接着的IDENTIFY又超时
控制器从PCI总线上掉了(ESXi那边的问题)controller reset failed,寄存器读回来全是1
四块盘共用的供电、线或者背板所有端口一起SATA link down

然后看/var/log/kern.log里出事那几分钟。去掉了主机名和内核时间戳,箭头后面是我加的说明,ata35~ata38是直通控制器上的硬盘3~6:

可以看到:

  • SErr是0,每次复位链路都能6.0 Gbps重新up,不是供电和线的问题。后来看SMART,四块盘的UDMA_CRC_Error_Count也都是0。
  • 没有controller reset failed,控制器一直都在。
  • 命令能发出去,就是等不到命令完成的中断。这几分钟里No irq handler for vector一共36条,全是0.145,每次复位完都会出现两条。

0.145是CPU 0的145号向量,意思是有中断送到了这里,但内核在这个位置没有登记处理函数。

中断去哪了

因为没重启,现场都还在。先确认直通控制器用的是哪个中断。lspci里能看到两个SATA控制器,13:00.0是直通进来的Intel控制器(DeviceName是pciPassthru0),中断号在sysfs里:

所以是IRQ 25。中断号是开机的时候分配的,不是固定的,后面的脚本也是这样现查。

再看/proc/interrupts(中间几列省略了),25那一行最后一列也是这个PCI地址:

开机20天,直通控制器(IRQ 25)一亿多次中断全在CPU0上,CPU2上有1次,其他都是0。再看几个地方:

  • MSI的Address是fee04000,目标APIC ID是4,这台虚拟机里对应CPU2;Data是0x4054,向量是0x54也就是84。就是说内核已经把这个设备的MSI改成了送到CPU2的84号向量。
  • CPU2上确实收到过1次,但之后的中断全跑到CPU0的145号向量去了,和设备里写的对不上。
  • affinity是6,是irqbalance后来又想把它挪到CPU6。这种MSI改affinity要等下一次中断来的时候才真正执行,结果中断再也没来。
  • Maskable-,这个控制器的MSI不支持屏蔽。
  • 改affinity的就是irqbalance,DSM自带的,开机就在跑。

到这里能确定的是:设备里写的目标是CPU2 / 84,实际中断送到的是CPU0 / 145,而CPU0的145号向量内核已经不认了。但还有两件事说不通:群晖真机上irqbalance天天这么挪中断,为什么没事?IRQ 25在CPU0上好好跑了20天,又是什么时候出的问题?

为什么真机没事,ESXi上就不行

先去看了ESXi那边。虚拟机的vmware.log和主机的vmkernel.log在19:42那会儿关于这个设备一行都没有,只有虚拟机重启的时候才有几行直通设备的记录。也就是说虚拟机改MSI这件事,ESXi根本没当回事。

再看ESXi是怎么送这个中断的:

ESXi开了posted interrupt,直通设备的中断不经过ESXi的软件,是硬件按一张表直接送到虚拟机的某个CPU和向量上,这张表是ESXi填的。那就要看虚拟机改写MSI以后,ESXi会不会跟着改这张表。

直接做了个实验(这时候已经重启过,IRQ 25在CPU0的145号向量上):一边用dd读盘制造中断,一边在虚拟机里把直通控制器的MSI数据寄存器从向量145改成231,保持50毫秒再改回去,看这50毫秒里中断还到不到CPU0。脚本的输出:

寄存器确实改掉了(读回来是e7,也就是231),但这50毫秒里1489次中断照样送到了CPU0,一次都没丢。把地址寄存器(目标CPU)从CPU0改成CPU7也一样(只留了相关的行):

所以ESXi完全不理会虚拟机对MSI的改写,中断一直送到虚拟机开MSI的时候(也就是开机时)的那个CPU和向量。真机上没有这一层,设备自己的寄存器改了马上就生效,irqbalance怎么挪都没事。

这个实验有风险。如果ESXi认了改写,这50毫秒里的中断就全丢了,轻则卡几十秒,重则再掉一次盘,别在有数据的机器上随便试。

那为什么还能跑20天

4.4内核挪中断的时候不会马上丢掉旧向量,要等新位置上收到第一个中断,才认为挪完了,再把旧向量释放掉。ESXi一直往旧位置送,旧向量一直有效,所以一切正常,只是smp_affinity里显示的CPU和实际收中断的CPU对不上而已。这个状态可以一直持续下去。

直到新位置上真的来了一个中断,/proc/interrupts里CPU2上那孤零零的1次就是它。这个中断多半不是直通控制器发的,而是虚拟SATA控制器(IRQ 24)的:它的MSI也不能屏蔽,irqbalance大概每30秒就挪它一次,挪的时候内核先写地址再写数据,中间要是正好来了一个中断,就会送到新的CPU、旧的向量上。平时日志里那些零星的No irq handler就是这么来的。

写了个脚本记录IRQ 24的MSI寄存器每次变化的时间(开机后的秒数),和内核日志对上了两条:

这种送错的中断落在没人用的向量上,就是一条No irq handler,没什么影响。可要是正好落在IRQ 25预留的新位置(CPU2的84号向量)上,内核就以为IRQ 25挪完了,把CPU0上的旧向量释放掉,而ESXi还在往旧位置送,后面真正的中断就全丢了。

虚拟SATA控制器在irqbalance挪动中断时送错的一个中断,落在直通控制器预留的CPU2向量84上,内核释放了旧向量,直通控制器掉盘
虚拟控制器的一个中断送错了地方,正好落在直通控制器预留的位置上。这一步是推出来的。

最后这一下我没抓到现场,是推出来的。不过数量上对得上:上次开机的20天里这种记录一共502条,分布在182个不同的向量上,算下来大概一千条里中一次,一天25条左右就是40天上下一次,和实际的间隔(3天到223天,平均74天)是一个量级。

所以完整的经过应该是:

  1. 开机后irqbalance把IRQ 25从CPU0挪到CPU2,内核改了设备的MSI地址和数据。具体什么时候挪的不知道,可能开机没多久就挪了。
  2. ESXi不理会这个改动,中断照旧送到CPU0的145号向量。内核的旧向量还留着,所以一直正常。
  3. 20天后,一个送错的中断正好落在CPU2的84号向量上,内核以为挪完了,把CPU0上的旧向量释放掉。
  4. 真正的中断还是送到CPU0的145号向量,那里已经没有处理函数,全丢了。
  5. AHCI驱动等不到中断,命令超时,复位,再超时,最后把端口禁用。四块盘在同一个控制器上共用这一个中断,所以一起掉盘。

间隔这么随机,是因为要等一个送错的中断正好落在那个位置上。

又看了下DSM是怎么启动irqbalance的。启动脚本/usr/syno/etc/rc.sysv/irqbalance.sh会从synoinfo.conf读syno_10g_irq_bind_cpus和syno_ahci_irq_bind_cpus两个值,有的话加上群晖自己的绑核参数。我这个型号的配置里两个都没有,所以就是不带任何参数的irqbalance,所有中断都会被它挪来挪去。

No irq handler这条平时每天都有几十条,每次的CPU和向量都不一样,出现一次就没了;出事那次是同一个0.145一直重复。所以不能光看条数,要看是不是同一个向量在重复:

解决办法

既然ESXi不认改写,那就别让irqbalance去挪这个中断,内核和ESXi认的就一直是开机时的同一个位置,其他中断照常。DSM带的irqbalance是1.6.0,支持--banirq。

中断号写死不太保险,所以写了个脚本启动的时候现查:找出所有不是VMware虚拟设备的AHCI控制器,把它们的中断都ban掉。保存成/usr/local/bin/irqbalance-passthrough-ban.sh,记得chmod 755:

然后用systemd drop-in把原来的启动命令换掉,/etc/systemd/system/irqbalance.service.d/10-passthrough-ban.conf:

想确认irqbalance真的认这个参数,可以单独跑一轮调试模式看看:

DSM升级有可能把drop-in清掉,所以又加了个开机检查。DSM开机会执行/usr/local/etc/rc.d/*.sh start,在里面放一个irqbalance-passthrough-ban.sh(也要chmod 755),发现drop-in没了就补回去:

其他几个办法没用:

  • --banmod=ahci看着更省事,但这个内核给中断起的名字是PCI地址(0000:13:00.0)不是ahci,估计匹配不上,我没试。
  • 群晖自己的--synoahciirq是把AHCI中断绑到指定CPU,等于主动挪一次,更不行。
  • 直接停掉irqbalance也可以,不过网卡那8个队列的中断就没人管了,只ban一个影响最小。

重启

这是DSM自带的命令,和网页上点重启一样。22:15执行,22:18就能登录了,连不上的时间不到两分钟。起来之后存储池2自己组好了:

应该是因为三块盘是一起掉盘的,md想往盘上写谁掉了的时候盘都已经没反应了(DSM日志里那几条write error就是这个),三块盘上的元数据都停在出事之前,是一致的,所以重启直接就能组起来,不用重建。

顺便检查了下:

  • btrfs device stats /volume2全是0
  • 四块盘SMART都是PASSED,重映射、待映射、CRC错误都是0
  • 原来的账号又能用密钥登了
  • 容器都自己起来了

还有一个问题,直通的三块盘离线了两个多小时,开机时DSM系统分区的镜像把它们当成过期的踢掉了(md: kicking non-fresh sata3p1 from array!),存储管理器会提示系统分区故障。数据不受影响,在存储管理器里点修复,几分钟就同步完了。

开机17分钟后再看中断:

虚拟控制器的中断已经被irqbalance挪了好几个CPU,直通控制器一直在CPU0。No irq handler还是偶尔有(这17分钟里1条,3.120),就是前面说的那种,IRQ 25一直正常,也没有ATA报错。

还不确定的

  • kern.log只留9份,前五次的内核日志早没了,只能说DSM这边的现象是一样的,不敢保证是同一个原因。
  • 正好落在预留位置上的那一个中断没抓到现场,是按数量推出来的。
  • 最长一次隔了7个月才出现,到底修没修好只能等时间验证。
  • 还有一个没试过的办法:在ESXi这边给虚拟机的.vmx加一行pciPassthru0.msiEnabled = "FALSE",让直通设备不用MSI,退回INTx。ban了以后应该用不上。
  • DSM升级以后要看一下irqbalance的命令行里--banirq还在不在。

如果你也遇到了

ESXi里的Linux虚拟机直通了SATA控制器,偶尔控制器上所有盘一起掉盘、重启就好的,可以这样查:

  1. 先别重启。密钥登不上的话按上面的办法给root加公钥。
  2. 看内核日志里是不是链路能up但命令超时,有没有同一个向量反复出现的No irq handler for vector:
  3. 看/proc/interrupts里直通控制器的中断是不是都在一个CPU上,而/proc/irq/N/smp_affinity_list指向另一个CPU,再看有没有irqbalance在跑。
  4. lspci -vv看控制器的MSI是不是Maskable-。
  5. 都对得上的话,把这个中断从irqbalance里ban掉再重启。

如果日志里是所有端口一起SATA link down,那是另一回事,该查供电和线。

参考

  • x86/apic/msi: Plug non-maskable MSI affinity race(Linux提交6f1a4891a592,2020年):不可屏蔽的MSI改affinity的时候,地址和数据不是一次写完的,中间来的中断会送错地方。前面那些零星的No irq handler就是这种情况。
  • genirq/msi: Make sure PCI MSIs are activated early(Linux提交f3b0946d629c,2016年):提交说明里写了设备可以在MSI使能的时候就把MSI寄存器的内容锁存下来,还提到这个改动顺带修好了VMware直通的一个问题,和这次看到的ESXi的行为对得上。
  • irqbalance:--banirq这些参数的说明。
0 评论
    留言