
简介dmar.rar_直接存储是一份围绕 DMA Remapping直接存储访问重映射技术的 C 语言源码包适合驱动开发人员、系统底层原理学习者以及虚拟化安全方向的研究者阅读。压缩包内共有 1 个文件即 dmar.c资源整体仅 9KB体积小巧但代码具有代表性浓缩了 DMA 重映射单元在地址转换、设备内存隔离与硬件安全保护方面的核心实现思路。通过阅读这份源码可以依次学习到 DMA 翻译表的创建与动态更新、重映射异常中断的处理流程以及设备注册与初始化。文件还覆盖了地址空间隔离等安全机制、缓存一致性管理与预读取等性能优化策略以及地址对齐、越界访问等常见错误的检测与报告方法。整体来看该资源既适合作为学习直接存储机制的案例参考也可作为底层驱动开发的辅助模板帮助读者快速理解设备物理地址与系统内存地址之间的映射关系。目前该资源已有 173 人浏览学习是一份轻量但信息密度较高的底层代码样本。1. 拿到 dmar.rar_直接存储先看清它解决什么问题搜索框里敲下“dmar.rar_直接存储”的人通常分两种一种是从网上下载了一个命名含糊的压缩包对着里面一堆 DMAR、IOMMU、vfio 开头的文件发懵另一种是在做虚拟机直通存储想用 DMA Remapping 把一块 NVMe 直接丢给虚拟机摆脱文件模拟层的性能和延迟损耗。这两种诉求其实是同一件事的两端理解 DMARDMA Remapping表让存储设备绕过宿主机 CPU 拷贝直达客户机内存。这篇文章会带你走完从解包、验证环境、配置直通到用 fio 验证性能和排查 IOMMU 坑的完整链路。适合虚拟化性能调优、嵌入式数据采集和存储相关研发也适合刚接触 vfio 直通但被 BIOS 和内核参数卡住的人。2. 直接存储为什么需要 DMARDMA 重映射的账与内核里的三张表2.1 直接存储 vs 间接存储CPU 拷贝到底贵在哪直接存储的概念并不复杂设备通过 DMA 把数据直接写到最终的内存或存储介质上中间不需要 CPU 参与逐字节拷贝。反过来看间接存储虚拟化环境里最常见的路径是 QEMU 进程把镜像文件内容读到用户态缓冲区再通过 KVM 接口写入客户机物理内存。一块 4KB 的数据页要经历页缓存、用户态缓冲、KVM 的 mmap 区域至少两次 memcpyCPU 全程在场。延迟高、CPU 占用高吞吐还容易被单进程瓶颈卡住。DMA 直接存储的价值在于把“搬运”这件体力活交给设备本身。NVMe 控制器拿到源地址和目的地址后自己通过 PCIe 总线写数据CPU 只需要在 I/O 请求开始时准备地址映射在中断到来时处理完成通知。同样是顺序读直通后带宽普遍比 qcow2 虚拟盘高出百分之几十延迟也能接近物理机水平。这不是夸张而是直通把虚拟化层最重的那段拷贝路径整个删掉了。那什么时候不适合直通如果你需要快照、热迁移、多台虚拟机共享同一块盘或者你的存储压力根本没到 CPU 成为瓶颈的程度那么直通的收益不明显反而会把运维灵活性赔进去。选型理由说到底是三条延迟敏感、单机部署、能接受设备级绑定不迁移。2.2 DMAR 与 IOMMUACPI 表、DMA 重映射、vfio-pci 三者的关系DMAR 是 ACPI 里的 DMA Remapping TableBIOS 在启动时把它暴露给操作系统里面描述 IOMMU 硬件能力、设备作用域和中断重映射信息。Linux 的 intel-iommu 驱动解析 DMAR 表后初始化 IOMMU 硬件开始为设备的 DMA 请求做地址翻译。IOMMU 在这里的角色类似于 CPU 的 MMU只不过 MMU 管的是 CPU 访问内存IOMMU 管的是 PCIe 设备访问内存。vfio-pci 是用户态设备驱动框架。设备一旦被 vfio-pci 接管普通内核驱动就不再碰它用户态程序比如 QEMU通过 VFIO 文件描述符直接操作设备。QEMU 把直通设备的 BAR 空间和中断映射给虚拟机同时通过 IOMMU 为虚拟机物理内存建立 DMA 映射设备发出的 DMA 请求经过 IOMMU 翻译后落在正确位置。检查这套链路是否就绪第一件事看内核日志。常见的检查命令如下# 查看内核是否识别 DMAR 表和 IOMMU 硬件 dmesg | grep -iE DMAR|IOMMU # 如果看到 DMAR: IOMMU enabled 说明 IOMMU 已经工作 # 如果看到 DMAR: DRHD 或 DMAR: ATSR 相关行说明 ACPI 表被正常解析 # 查看 IOMMU 分组每个目录对应一个可独立直通的 DMA 隔离域 ls -l /sys/kernel/iommu_groups/ | head -30看日志时注意和“直接存储”相关的关键信息dmesg 里出现DMAR: IOMMU enabled是最基本的如果只有DMAR: DRHD: handling fault status reg却没有 enabled说明 BIOS 暴露了表但内核没能启用。后面这种情况八成是内核参数没加或者 GRUB 没重新生成。2.3 SWIOTLB 与 iommupt为什么直通存储还要关心 DMA 路径IOMMU 开启后设备 DMA 默认走“地址翻译”路径内核为设备建立 I/O 页表设备的 DMA 地址被翻译成物理地址。翻译过程本身有开销但对现代 IOMMU 来说可以忽略真正影响性能的是另一种情况——当 DMA 请求无法直接映射时内核会退回 SWIOTLB软件 IO TLB把数据先复制到一块连续的 bounce buffer再让设备从那里搬运。一次 DMA 变成了“设备写 bounce buffer CPU 复制到目标地址”直接存储就名存实亡了。常见触发 SWIOTLB 的场景是设备 DMA 寻址范围受限或者 IOMMU 被配置成强制翻译模式。为了解决这个问题内核提供了 iommuptpassthrough模式。在这种模式下IOMMU 保持直通映射设备 DMA 尽量使用物理地址绕过页表翻译和 bounce buffer。grub 配置里加参数时intel_iommuon负责打开 DMA Remappingiommupt负责让性能敏感的直通设备走短路径。两种模式的取舍可以用下面这张表概括模式内核参数DMA 路径隔离性适用场景翻译模式intel_iommuon设备地址经 IOMMU 页表翻译强设备只能访问被映射内存需要安全隔离的虚拟化环境直通模式intel_iommuon iommupt设备 DMA 尽量走物理地址弱依赖设备自身隔离直通 NVMe、GPU 等性能敏感设备我一般会在启用直通前先定下这台宿主机的主要用途只做设备直通和虚拟化用 pt 模式更省心如果同一个平台上跑多租户虚拟机安全隔离优先保留翻译模式。3. 把 dmar.rar 变成可用资产解包校验与直通环境体检3.1 下载站压缩包第一步隔离解压、列清单、看类型“dmar.rar”这种命名方式在下载站很常见功能标签用下划线加在文件名后面但包里的东西到底是从哪台机器抄出来的配置、脚本还是日志备份完全看不出来。我拿到这种包的第一步从来不是直接解压到当前目录而是先隔离到一个临时目录做三件事测试压缩包完整性、列出文件清单、鉴别文件类型。mkdir -p /opt/src/dmar cd /opt/src/dmar # 测试压缩包完整性避免解到一半发现文件头损坏 unrar t /path/to/dmar.rar # 列出内容确认里面是什么类型脚本、文档、还是固件 unrar l /path/to/dmar.rar | head -40 # 解压到独立目录防止未知脚本混进 PATH unrar x /path/to/dmar.rar /opt/src/dmar/ find . -maxdepth 2 -type f | head -50unrar t会把整个压缩包跑一遍 CRC 校验但不写盘这一步不能省。下载站给的包经常因为传输中断出现 CRC 错误这时候硬解出来的文件很可能缺字节配置模板还好如果是二进制固件或内核模块缺字节就直接废了。unrar l只列清单不落盘用来快速判断包的内容方向有 README、.sh 脚本多半是某位工程师的配置笔记有 .c 或 .ko 文件则是内核模块相关的源码或编译产物有 .bin 或 .img可能是固件备份。解压到独立目录还有一个原因网上拿到的压缩包可能带着绝对路径或者脚本里的危险操作直接解开再手动 review 比盲目执行安全得多。包内的 README 或脚本开头注释能快速看出作者的部署意图但别急着跑先往下继续做环境体检。3.2 BIOS 与内核打开 VT-d 的三个开关直通存储能不能成第一道坎是 BIOS。Intel 平台要在 BIOS 里打开 VT-dI/O 虚拟化AMD 平台对应的是 AMD IOMMU也叫 SVM。很多服务器默认关闭这个选项而且不同厂商的叫法不一样有的叫“Intel Virtualization Technology for Directed I/O”有的直接叫“DMA Remapping”。BIOS 打开之后内核参数还差一脚。常见发行版的 grub 配置文件位置不同但改法一致# 编辑内核启动参数三项按需添加 # intel_iommuon 确保 Intel 平台的 DMA Remapping 真正启用 # iommupt 让设备 DMA 尽量走物理地址减小翻译开销 # pcie_acs_overridedownstream 仅当确认 ACS 缺失且需要拆分分组时才用 vim /etc/default/grub # 在 GRUB_CMDLINE_LINUX 中追加参数后更新引导 grub2-mkconfig -o /boot/grub2/grub.cfg # 多数 RHEL/CentOS 系 # 或 update-grub # Debian/Ubuntu 系重启后重新看 dmesg确认 IOMMU 状态。这里特别提一句pcie_acs_override它是在 IOMMU 分组不合预期时用来强制拆分分组的补丁开关能让原本共享一个 DMA 隔离域的设备拆开直通。但代价是削弱隔离性如果组内设备被 DMA 攻击整个系统都不安全。生产环境慎用实验室环境用之前也要想清楚。3.3 确认 IOMMU 分组哪块存储能不能独立直通IOMMU 分组决定了一块 PCIe 设备能否被单独直通。分组本质上是 DMA 隔离域同一个组里的设备共享同一个 IOMMU 翻译上下文不能只把其中一块交给虚拟机、另一块留在宿主机。消费级主板经常把两个 M.2 插槽和一个 PCIe x16 分成一组导致你想直通的那块盘拖家带口。# 列出所有 NVMe 设备的 BDFBus:Device.Function lspci -Dnn | grep -i Non-Volatile # 假设输出 01:00.0 0108 [NVM Express] # 找到这个 BDF 所属的 IOMMU 分组 for g in /sys/kernel/iommu_groups/*/devices/*; do if [ $(basename $(readlink $g)) 0000:01:00.0 ]; then echo 设备属于分组: $(dirname $(dirname $g)) ls -l $(dirname $(dirname $g))/devices/ fi done这段脚本把 PCI 地址反过来查它所在的 IOMMU 分组目录然后列出组内所有设备链接。如果组里只有目标 NVMe 一个设备祝贺可以直接进入下一步如果组里还有别的设备先把它们也列为直通对象否则只能想办法调整插槽位置或者开启 ACS override。这里补充一点PCIe 交换器和平台芯片组会影响分组同一颗 CPU 下的直连插槽往往分组更干净。4. 让 NVMe 直达虚拟机vfio-pci 直通的两条路径与参数4.1 把设备从内核驱动手上拿过来vfio-pci 绑定环境体检通过后开始真正动刀。核心操作是把 NVMe 设备的驱动从 nvme 换成 vfio-pci。先看设备编号再动态加载驱动绑定验证可用后再写成持久化配置这个顺序能让你在出问题时快速回滚。# 查 NVMe 的 vendor:device 号例如 144d:a808 lspci -n -s 01:00.0 # 加载 vfio-pci 框架 modprobe vfio_pci # 动态把设备注册到 vfio-pci重启后失效适合验证阶段 echo 144d a808 /sys/bus/pci/drivers/vfio-pci/new_id # 如果设备已在 nvme 驱动下先解绑再绑定到 vfio-pci echo 0000:01:00.0 /sys/bus/pci/drivers/nvme/unbind echo 0000:01:00.0 /sys/bus/pci/drivers/vfio-pci/bind # 验证驱动归属 lspci -ks 01:00.0注意顺序不能乱先 new_id 让 vfio-pci 认识这个设备再去 unbind 旧驱动最后 bind。很多人第一次直接 unbind nvme 然后发现 vfio-pci bind 不回来就是因为 vfio-pci 的设备 ID 列表里还没有这个设备。验证时lspci -ks输出Kernel driver in use: vfio-pci才算成功。上面这套是动态验证重启后失效。要永久生效写入 /etc/modprobe.d/vfio.conf# 让 vfio-pci 在开机时就抢占这块 NVMe options vfio-pci ids144d:a808 # 加载 nvme 驱动之前先加载 vfio-pci softdep nvme pre: vfio-pci改完配置后需要重新生成 initramfsRHEL 系用dracut -fDebian/Ubuntu 系用update-initramfs -u。如果忘记这一步重启后 initramfs 阶段先加载了 nvme 驱动设备被 nvme 接管vfio.conf 里的配置自然落空。4.2 QEMU 命令行直通最直接的一条路驱动绑定完成启动虚拟机时把设备挂进去。一条最小可验证的 QEMU 命令行比图形界面更直观qemu-system-x86_64 \ -machine q35,accelkvm \ -cpu host \ -smp 8 \ -m 16G \ -drive fileubuntu.qcow2,ifvirtio \ -device vfio-pci,host0000:01:00.0 \ -netdev user,idnet0 \ -device virtio-net-pci,netdevnet0关键参数是-device vfio-pci,host0000:01:00.0它告诉 QEMU 把宿主机上的这块 NVMe 以 PCI 设备形式直通给客户机。启动后虚拟机内会出现一块裸的 NVMe 设备看到/dev/nvme0n1就说明直通成功。这里强调一点直通后宿主机上对应的 /dev/nvme0n1 会消失因为设备已经被 vfio-pci 接管不再是 nvme 驱动管理。千万别把系统盘这样直通出去否则宿主机直接丢盘。4.3 libvirt XML 方式适合生产维护命令行适合验证生产环境还是建议用 libvirt 管理热插拔和配置管理都方便。在虚拟机 XML 里加一段 hostdev 配置hostdev modesubsystem typepci managedyes source address domain0x0000 bus0x01 slot0x00 function0x0/ /source /hostdevmanagedyes表示让 libvirt 自动处理设备驱动接管宿主机上不需要预先绑定 vfio-pcilibvirt 会在虚拟机启动时把设备从 nvme 驱动切到 vfio-pci关闭虚拟机后再切回来。这个特性在测试阶段很好用但你如果已经通过 modprobe.conf 写死了绑定managed 与否都不影响结果。添加配置后重启虚拟机或者用 virsh attach-device 动态挂载。直通的代价同样明显整块物理盘被虚拟机独占虚拟机内做的分区、文件系统、数据都在物理盘上宿主机完全不可见。快照、克隆、热迁移这些虚拟化便利一概没有。所以生产上直接存储只适合明确要性能、能接受设备级绑定的工作负载。5. 直接存储的常见问题排查fio 验证与 5 个坑5.1 用 fio 量化性能直通与虚拟盘的差距直通到底值不值别靠感觉跑一轮 fio 就知道。建议在虚拟机内对直通盘跑一组测试再和同样环境下 qcow2 虚拟盘对比。注意 fio 写操作会覆盖盘上既有数据测试前先确认这块盘是空的数据做好备份。# 在 VM 内对直通盘跑 60 秒顺序读4M 块队列深度 32 fio --nameseqread --filename/dev/nvme0n1 --rwread \ --bs4M --iodepth32 --direct1 --ioenginelibaio \ --size8G --runtime60 --numjobs1 --group_reporting # 随机写重点看 IOPS 和延迟分布 fio --namerandwrite --filename/dev/nvme0n1 --rwrandwrite \ --bs4k --iodepth32 --direct1 --ioenginelibaio \ --size4G --runtime60 --time_based参数里--direct1绕过页缓存测的就是设备真实能力--ioenginelibaio是 Linux 下最常用的异步引擎--iodepth32决定队列深度NVMe 盘对队列深度很敏感。同一台机器跑对比测试时唯一变量应该是磁盘后端其余参数保持一致对比结果才有说服力。直通盘顺序读和随机 IOPS 明显高于虚拟盘说明这条 DMA 路径是通的如果差距不大往下查坑。5.2 坑 1IOMMU 分组把多设备绑一起虚拟机起不来现象绑定 vfio-pci 后启动虚拟机QEMU 直接报Device is not behind an IOMMU或者类似 vtd 错误。原因目标设备所在的 IOMMU 分组含多个 PCIe 设备没有独立隔离域。常见于消费级主板把 M.2 和 PCIe 插槽共享一组。解决回到第 3.3 节的脚本确认组内成员。如果组里确实有别的设备把它们一起直通给同一个虚拟机或者换一个独立的插槽位。非要拆组就只能上pcie_acs_overridedownstream但要清楚它会让组内设备之间失去 DMA 隔离多租户生产环境不建议。5.3 坑 2vfio-pci IDs 写太宽开机后系统盘消失现象配置好 modprobe.d 并重建 initramfs 后重启宿主机系统进不去或者系统盘设备名变了引导失败。原因options vfio-pci ids里用了过宽的 vendor 通配比如直接把整个144d系列写进去操作系统盘也被 vfio-pci 接管了。解决重新进入恢复模式改回精确的 vendor:device 号只对应一块设备。配置持久化时用lspci -n -s 01:00.0查到的完整编号不要只写 vendor。如果用的是 softdep 方案更要核对softdep nvme pre: vfio-pci的写法它只会影响加载顺序不会接管所有 nvme 设备。5.4 坑 3直通盘性能不升反降延迟偶尔暴涨现象直通后吞吐上去了但延迟不平滑p99 很高dmesg 里有中断相关的报错。原因直通设备的中断重映射没开或者没对齐。MSI-X 中断通过 IOMMU 做重映射BIOS 里只开了 VT-d 没开 Interrupt Remapping有些主板叫 Interrupt Remapping 或 VT-d Interrupt。解决回 BIOS 确认 Interrupt Remapping 是 Enabled宿主机 dmesg 找DMAR-IR相关行确认IR-REMAP状态如果 BIOS 没有这个选项尝试加intremapon内核参数并重新生成 grub。还有一个容易忽略的细节虚拟机内中断 CPU 亲和性没绑NVMe 的多队列会瘫痪成单队列性能自然不稳定。5.5 坑 4升级内核后直通失效设备悄悄回到 nvme 驱动现象yum/apt 升级内核后重启直通盘没了lspci -k显示驱动变回 nvme。原因initramfs 重新生成时没有包含 vfio-pci 模块和 modprobe.d 里的配置。很多发行版升级内核会自动跑dracut -f或update-initramfs -u但有些定制环境不会。解决升级后主动重新生成 initramfs。更稳妥的做法是把 vfio-pci 配置文件纳入发行版的标准配置管理。检查 initramfs 是否包含对应配置可以用lsinitrd | grep vfioRHEL 系或initramfs-tools的列表命令。5.6 坑 5宿主机日志大量 DMAR Fault 报错现象虚拟机和宿主机的存储看起来都正常但 dmesg 里反复刷DMAR: [DMA Read] Request device [0000:01:00.0] fault addr。原因I/O 页表映射失效通常是 QEMU 退出或 vfio-pci 解绑时设备 DMA 请求还在飞指向一块已经被回收的内存。解决这类报错大多是伴随虚拟机开关产生的不是持续故障。如果只在开关机瞬间出现可以通过升级 QEMU 和内核版本缓解。持续刷错则要检查直通盘是否出现了硬件错误或者 BAR 资源冲突用lspci -vvv看设备 Capabilities 和 Region 分配。6. 再往深处走一步用 io_uring 把直接存储压到极限直通跑通、fio 验证完事情并没有结束。虚拟机内用上 io_uring 引擎往往还能再挤出一截性能。io_uring 通过共享环形队列减少每次 I/O 的系统调用开销对 NVMe 这类高队列深度设备尤其适合。同样在虚拟机里跑一轮对比# 用 io_uring 引擎跑随机读对比 libaio fio --nameuring --filename/dev/nvme0n1 --rwrandread \ --bs4k --iodepth64 --direct1 --ioengineio_uring \ --sqthread_poll1 --runtime60 --time_based--ioengineio_uring指定内核的 io_uring 接口--sqthread_poll1开启 SQPOLL 模式让内核线程代替用户态主动轮询提交队列进一步减少系统调用。这个参数对内核版本有要求虚拟机里用之前先确认内核开启了CONFIG_IO_URING和IORING_SETUP_SQPOLL。验证多队列有没有真正激活进虚拟机执行ls /sys/block/nvme0n1/mq/看到多个队列目录说明 NVMe 多队列正常工作。再配合lspci -k确认宿主机侧驱动是 vfio-pci、虚拟机内驱动是 nvme整条链路就闭环了。我自己最早做直通时也翻过车BIOS 里 VT-d 没开dmesg 看半天找不到原因后来养成了习惯拿到任何一台机器先跑一遍环境体检确认 DMAR、IOMMU、分组三步都过了才碰设备。直接存储这个方向值不值得投入关键看你的存储是不是真瓶颈——如果延迟敏感、愿意接受设备独占它带来的收益是虚拟化其他路径很难追上的如果只是跑常规业务虚拟盘加上足够的缓存也够用。希望帮到你。本文还有配套的精品资源点击获取