ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

深入理解PCIe ACS:IOMMU分组与设备直通的关键机制

深入理解PCIe ACS:IOMMU分组与设备直通的关键机制 1. 先搞清楚ACS到底解决什么问题1.1 一次直通失败引发的深入排查ACSAccess Control Services在PCIe体系里是个看起来不起眼、但关键时刻能卡住你整个虚拟化方案的能力。我之前调试一台服务器时想把PCIe switch下面挂的一块网卡直通给虚拟机结果IOMMU group死活拆不开折腾了一整天最后发现根因就是switch的downstream port没有实现ACS能力。这个经历让我重新认识了ACS。当时的情况是这样的服务器有一路PCIe switch下面挂了NVMe控制器、两块网卡和一个RAID卡。我想把其中一张网卡单独直通给虚拟机做高性能网络但是每次用VFIO绑定设备时系统都提示这块网卡和其他设备在同一个IOMMU group里无法安全隔离。查了lspci输出发现那个switch的downstream port在ACS能力上全部是减号也就是说它根本不支持ACS。在虚拟化和设备直通场景里ACS几乎是决定你能不能把设备安全拆分给虚拟机的关键开关。它不影响设备跑不跑得起来但直接影响设备能不能安全地独立使用。如果你做PCIe驱动、虚拟化平台、硬件虚拟化直通passthrough相关工作或者在做PCIe switch、FPGA加速卡这类硬件方案这篇内容值得花几分钟认真看一遍。1.2 PCIe P2P如何绕开IOMMU要理解ACS得先搞清楚PCIe里一个容易被忽略的现象设备与设备之间可以不经过根复合体Root Complex直接通信这就是Peer-to-PeerP2P传输。正常情况下一个PCIe设备要读写内存DMA请求会从设备发出经过switch的上行端口Upstream Port到达Root Complex再由Root Complex经过IOMMU做地址翻译和权限检查最终落到内存里。IOMMU在这里像个门卫所有进出内存的数据都要过它这一关这也是虚拟化环境下设备直通能保持安全的核心机制。但P2P传输走的是另一条路。举个例子两块网卡同时挂在同一个PCIe switch下如果它们之间要交换数据可以在switch内部直接转发请求根本不需要经过Root Complex。这本来是个好的性能特性GPUDirect RDMA、NVMe之间的数据搬运都依赖这种能力。问题在于如果P2P请求完全不经过Root ComplexIOMMU就失去了对这笔事务的可见性门卫被跳过了。在虚拟化场景下这意味着一个直通给虚拟机的设备可能通过P2P路径去访问另一个直通设备或者宿主机管理的设备空间绕过IOMMU的权限管控产生安全漏洞。ACS就是PCIe规范为解决这个问题提供的标准机制。1.3 ACS在PCIe规范中的定位ACS定义在PCIe Base Specification的6.12章节属于可选的 capability不是每个设备都必须实现。它主要落在switch的downstream port和Root Complex的root port上因为这两个端口才是P2P流量真正经过的关口。你可以把ACS理解成在switch内部加的一道道门禁规则。当downstream port实现了ACS之后软件就可以通过配置ACS Control寄存器要求这个端口对P2P请求、完成Completion事务做额外检查或者干脆把所有事务都重定向到上游端口让IOMMU有机会参与判断。这样一来P2P快是还能快但被置于可控范围之内。在做IOMMU分组的时候内核会检查每个端口到Root Complex路径上所有switch端口是否支持ACS。只有路径上的每段链路都具备ACS能力设备才有可能被划分到独立的IOMMU group。这也是我在文章开头提到的排查场景里设备们被绑在同一个group里的根本原因。2. ACS能力结构与关键位逐个拆解2.1 ACS能力结构长什么样ACS在PCIe配置空间里以Extended Capability的形式存在它的Capability ID是0x000D。虽然名字里带“Services”但它本质上就是一组寄存器和位定义软件通过读写这些位来启用或查看ACS能力。我先把标准的寄存器布局整理一下。ACS Extended Capability结构从Capability Header开始紧跟着的寄存器分别是ACS Capability Register、ACS Control Register和可选的Egress Control Vector。其中ACS Capability Register偏移是0x06ACS Control Register偏移是0x08Egress Control Vector从0x0C开始。每个寄存器16位不同位分别对应不同的ACS功能。下面是ACS Capability Register和ACS Control Register里各个位的对应关系两张表可以对照着看。Bit位缩写功能名称作用说明0SrcValidSource Validation校验请求的Requester ID是否来自合法端口1TBTranslation Blocking阻止未经地址转换的事务通过端口2PRRP2P Request RedirectP2P请求被重定向到上游端口3PCRP2P Completion RedirectP2P完成事务被重定向到上游端口4UFUpstream Forwarding强制所有事务向上游端口转发5ECEgress Control基于向量表控制出口方向6DTPDirect Translated P2P允许经过地址转换后的直通P2P7VMVector Masking支持对Egress Control向量做掩码8P2PCP2P Completion是否支持P2P完成超时等扩展2.2 校验类Source Validation与Translation BlockingSource ValidationSrcValid是ACS里最基础的校验功能。它要解决的是请求来源伪造问题。在PCIe体系里每个设备都有自己的Requester ID由Bus号、Device号和Function号组成。正常情况下一个端口收到的请求其Requester ID应该和挂在它下面的设备一致。启用Source Validation后端口会核对每个请求的Requester ID如果请求声称的ID不在该端口下游允许的范围内这个请求就会被拒绝。这就像小区门禁系统业主刷脸进门时系统会确认这个人是否登记在册。没有Source Validation的话一个设备可以伪造另一个设备的ID发起请求这在多租户场景下是很危险的事情。Translation BlockingTB则和Address Translation ServicesATS有关。ATS允许设备缓存IOMMU的地址翻译结果设备自己拿着翻译后的地址发起DMA这样可以减少每次DMA都经过IOMMU翻译的开销。但问题是如果设备拿着一个未经翻译的地址直接发请求并且通过了switchIOMMU就拦不到了。Translation Blocking启用后端口会阻止那些没有经过地址转换的事务通过强制要求所有DMA必须经过IOMMU或受支持的转换路径。2.3 转发类P2P Request Redirect与Completion RedirectP2P Request RedirectPRR是ACS里最常用、也最直接的一个功能。启用后downstream port收到来自下游设备的P2P请求时不会在switch内部直接转发给另一个downstream port而是把请求重定向到上游端口交给Root Complex做地址翻译和权限检查然后再由Root Complex决定怎么路由。这个设计的巧妙之处在于P2P数据通路没有完全关闭但所有事务都被“上提”了一层让IOMMU能够看到并参与决策。如果你在做虚拟化直通这个位基本是必须要打开的。P2P Completion RedirectPCR和PRR是一对。PCIe事务分请求和完成两个方向一个读请求发出后目标设备要返回完成数据包。启用了PCR这个完成数据包也不会在switch内部直接走P2P路径返回而是先回到上游端口经过检查后再路由到请求方。这样整个事务生命周期都在IOMMU的视线范围内不会出现请求走了安全通道、应答却从另一个通道偷偷回来的情况。2.4 边界类Upstream Forwarding与Egress ControlUpstream ForwardingUF逻辑上更严格一旦启用downstream port收到的所有事务都必须转发到上游端口不允许任何形式的P2P内部转发。这和PRR的区别在于PRR只重定向P2P请求UF则是把所有事务都强制走上游甚至包括原本合法、已经在内部判定可以转发的流量。这个功能适合对安全性要求极高的环境代价是P2P性能完全丧失。Egress ControlEC在PCIe spec 4.0之后越来越重要。它允许软件通过一段位图向量Egress Control Vector来精确控制某个端口允许哪些方向的出口事务。每个bit对应一个可路由的ID范围置1表示允许该方向的请求从本端口离开置0则拒绝。这比单纯的重定向更精细适合在大型switch拓扑里做细粒度的东西向流量控制。需要特别注意不同的function有不同的ACS用途。对root port来说主要关注它对下游设备P2P请求的处理对downstream port来说重点是它如何管理自己挂载的子树而对多功能设备本身ACS还能决定各个function之间的流量是否要互相隔离这也是后面要讲的multifunction场景的基础。3. 使能ACS的三种途径与实操配置3.1 固件与BIOS侧的ACS开关在大多数服务器平台上ACS能力的使能是在BIOS/固件阶段完成的。很多平台会在内存初始化后期、PCIe设备枚举阶段由固件根据平台策略去设置ACS Control寄存器。服务器级平台通常默认打开ACS能力因为虚拟化、SR-IOV、设备直通都是这类平台的常见需求。但消费级主板和部分嵌入式平台就不一定了。很多桌面级主板虽然有PCIe switch固件却不会主动去配置ACS寄存器导致Linux内核在IOMMU分组时发现端口不支持ACS把整个switch下的设备绑成一个group。这时你在系统层做什么操作都是徒劳设备直通依然不完整。如果你在做平台选型我的建议是先把PCIe拓扑搞清楚确认所有关键switch芯片是否声明了ACS能力。多数企业级PCIe switch芯片比如Broadcom、Microchip的Plx系列都支持ACS但固件是否默认使能又另说。有些服务器BIOS设置里能看到类似“ACS Enable”或“PCIe P2P Control”的选项但更多时候固件已经默默配好了你只需要在系统里确认结果即可。3.2 运行时用setpci修改ACS控制寄存器如果固件没有帮你配置ACS而设备本身又声明了ACS能力你可以在系统运行时用setpci手动修改配置空间。这在开发和调试阶段非常有用我经常靠它快速验证一个设备在ACS开启/关闭时的行为差异。首先找到ACS capability在配置空间里的位置。可以通过lspci的详细输出查看也可以用setpci直接搜索ecap。假设设备在01:00.0先用这条命令确认ACS能力存在lspci -s 01:00.0 -vvv | grep -i acs如果设备支持ACS输出里会显示ACS capability相关的寄存器内容。接下来找到capability偏移setpci -s 01:00.0 ECAP_ACS这个命令会直接打印ACS Extended Capability在配置空间中的偏移地址一般是0x100、0x120这样的值。假设返回的是0x100那么ACS Control Register就在0x100 0x08 0x108。然后把需要使能的位置成16位值写进去# 使能 SrcValid(bit0)、TransBlk(bit1)、P2P Request Redirect(bit2)、P2P Completion Redirect(bit3) setpci -s 01:00.0 0x108.w0x000F这条命令把ACS Control寄存器的低4位全部置1相当于同时打开Source Validation、Translation Blocking、P2P Request Redirect和P2P Completion Redirect。需要提醒的是运行时修改配置空间有几个坑。第一个某些设备在驱动加载后配置空间寄存器被驱动锁住setpci写不进去。第二个即使写进去了硬件也不一定立刻按新配置生效很多设备要求功能级别的复位或者整条链路重新枚举后才能应用ACS配置。所以最稳妥的方式还是修改固件配置setpci更适合做临时验证。3.3 内核IOMMU分组与VFIO直通的关系Linux内核在做IOMMU分组时核心目标是保证同一个group内部的设备之间必须有可控的隔离手段。如果一个group里有两个设备而这两个设备之间可以通过P2P直接通信且不受IOMMU管控那这两个设备就只能一起被直通或者一起不直通不能拆分。内核的pci_device_group函数会沿着设备的PCIe拓扑一直向上走检查路径上每个桥和端口的ACS能力。只要路径上有任何一个端口不支持必要的ACS功能这些设备就可能被合并进同一个group。你可以通过下面命令快速查看当前平台IOMMU group的划分情况find /sys/kernel/iommu_groups/ -type l | sort也可以更直观地看某个指定设备属于哪个groupreadlink -f /sys/bus/pci/devices/0000:01:00.0/iommu_group如果输出显示01:00.0和01:00.1的iommu_group链接指向同一个目录说明这两个function在同一个group里。对于VFIO直通来说同一个group的设备要么全部绑定vfio-pci驱动要么全部不绑否则直通无法正常进行。3.4 pcie_acs_override参数到底能不能用如果你确认设备不支持ACS或者固件没有使能ACS还有一个颇具争议的兜底方案内核启动参数pcie_acs_override。这个参数可以强制内核忽略ACS能力的缺失强行把设备拆分到不同IOMMU group。参数格式是pcie_acs_overridedownstream,multifunctiondownstream含义是把所有downstream port和root port都视为支持ACS即使硬件没有声明。multifunction含义是把所有多功能设备视为每个function之间具备ACS隔离能力即使硬件没有支持。这个参数在某些老平台、消费级主板上确实非常有用很多做GPU直通、网卡直通的人都依赖它才能把设备拆开。但代价是安全性被削弱。因为你实际上是在告诉内核“虽然硬件没有隔离能力但我相信它是安全的。”这在隔离要求严格的生产环境里是不可接受的它只适合个人开发机、测试环境或者硬件已经明确淘汰但暂时无法更换的场景。我个人的经验是能用硬件ACS解决的问题不要碰override。它只是一个临时的创可贴不是长期方案。而且在有些设备上即使打开了override后续真正跑P2P流量时依然会出问题因为硬件层面的隔离确实不存在只是软件层面假装它存在。4. ACS相关的现实排查与避坑实录4.1 用lspci快速确认ACS支持情况排查ACS问题第一步永远是看设备实际的ACS能力而不是看芯片型号或者资料文档。用lspci可以快速确认lspci -s 03:00.0 -vvv在输出中搜索“ACS”关键字会看到类似这样的内容ACS (Access Control Services) Capability ACSCap: SrcValid TransBlk ReqRedir CmpRedir UpstrFwd EgressCtrl DirectTrans ACSCtl: SrcValid TransBlk ReqRedir CmpRedir UpstrFwd EgressCtrl- DirectTrans-这里我习惯把两行分开看。ACSCap那行表示设备支持哪些ACS功能加号表示硬件实现了这个功能减号表示没实现。ACSCtl那行表示当前使能状态加号是打开减号是关闭。很多时候你会发现一个很有意思的情况设备硬件上支持ACS但ACSCtl显示一堆减号这就说明固件没有默认使能可以尝试用setpci去打开。反过来如果ACSCap本身就是一堆减号那就算setpci写得再用力也没用硬件根本不吃这一套。4.2 典型场景switch不带ACS导致直通无门我之前碰到过一个实际的案例。客户有一张PCIe扩展卡上面集成了一个switch芯片分出三个下游端口分别接了NVMe、网卡和USB控制器。客户想把网卡直通给虚拟机但IOMMU group显示这三个设备全都绑在一起。用lspci查了switch的每个downstream portACSCap基本都是零。这时候无论怎么调整VFIO绑定顺序设备都无法拆开因为在内核看来这三个设备可以通过switch内部的P2P路径互相通信且中间没有任何隔离机制必须视为一个整体。当时给出了三个选项第一更换一张支持ACS的PCIe switch扩展卡这是最彻底的方案。第二接受现状不单独直通网卡把整棵设备树一起直通但明显不现实。第三在测试环境用pcie_acs_override强行拆分验证业务可行性但明确说明不能上生产。这就是ACS在现实硬件选型中的分量。你在选PCIe switch卡时光看带宽、通道数、功耗是不够的ACS支持情况直接决定了这张卡能不能用在虚拟化直通场景里。采购清单上必须加一条确认switch downstream port支持ACS并能在固件中使能。4.3 多功能设备的ACS坑除了switch端口多功能设备的ACS也有自己的坑。显卡、网卡很多都是多功能设备一个物理插槽实现了多个function。默认情况下Linux内核对多功能设备的分组有两个条件一是function之间确实有独立的PCIe路径二是这些function之间的P2P交互必须能被ACS隔离。如果设备是真正的多功能设备且硬件实现了ACS隔离那么每个function都可能被分到独立的IOMMU group这对SR-IOV和直通来说是理想情况。但问题在于不少设备虽然以多功能形式出现在配置空间里各个function之间却共享内部资源function A可以直接访问function B的寄存器空间。这种情况下硬件如果没有ACS能力内核同样会把它们绑在同一个group里。所以别看见一个网卡带两个function就觉得可以分开直通还是要看ACS。检查方法也是一样对每个function执行lspci -vvv确认它的ACS capability是否完整。有些设备在function 0上有ACS能力function 1上却没有这种情况也要特别小心。4.4 排查ACS问题的实操三板斧最后总结一下我排查ACS相关问题的固定套路希望对你有参考价值。第一步确认ACS能力。用lspci -vvv看ACSCap和ACSCtl把整个拓扑路径上的所有端口都查一遍包括root port、switch的upstream和downstream port、设备本身的ACS能力。这一步的目的是搞清楚硬件到底行不行。第二步分析IOMMU group。用find /sys/kernel/iommu_groups/ -type l查看分组情况对照PCIe拓扑确定是哪一段链路导致设备被绑在一起。如果路径上某个端口ACSCap全是减号那它基本就是罪魁祸首。第三步选方案。如果硬件支持ACS但固件没使能用setpci或修改固件配置去打开。如果硬件不支持要么换硬件要么在测试环境里用pcie_acs_override兜底。千万不能跳步不要直接上override然后感叹虚拟化直通不稳定根因一定要找到。再补充一个实用的小技巧。在确认ACS使能是否成功后很多人只看到lspci显示ACSCtl连续加号就觉得万事大吉了。但实际上你还需要确认IOMMU group是否真的发生了变化。修改配置后建议重新加载vfio-pci驱动或者重启系统再查看一次group。有些硬件虽然寄存器已经置位但内部逻辑在运行时没有完全切换需要一次干净的重新枚举才能真正生效。ACS这个东西单独看就是配置空间里几个bit但它背后连接的是PCIe事务路由、IOMMU隔离、虚拟化安全这一整个链条。我见过不少人做设备直通时被IOMMU group卡住第一反应是打override补丁结果问题暂时解决了后患却留下了。真正花点时间把ACS弄清楚反而是一劳永逸的事。
返回列表