简介:dmar.rar_直接存储是一份围绕 DMA Remapping(直接存储访问重映射)技术的 C 语言源码包,适合驱动开发人员、系统底层原理学习者以及虚拟化安全方向的研究者阅读。压缩包内共有 1 个文件,即 dmar.c,资源整体仅 9KB,体积小巧但代码具有代表性,浓缩了 DMA 重映射单元在地址转换、设备内存隔离与硬件安全保护方面的核心实现思路。通过阅读这份源码,可以依次学习到 DMA 翻译表的创建与动态更新、重映射异常中断的处理流程,以及设备注册与初始化。文件还覆盖了地址空间隔离等安全机制、缓存一致性管理与预读取等性能优化策略,以及地址对齐、越界访问等常见错误的检测与报告方法。整体来看,该资源既适合作为学习直接存储机制的案例参考,也可作为底层驱动开发的辅助模板,帮助读者快速理解设备物理地址与系统内存地址之间的映射关系。目前该资源已有 173 人浏览学习,是一份轻量但信息密度较高的底层代码样本。
1. 拿到 dmar.rar_直接存储:先看清它解决什么问题
搜索框里敲下“dmar.rar_直接存储”的人,通常分两种:一种是从网上下载了一个命名含糊的压缩包,对着里面一堆 DMAR、IOMMU、vfio 开头的文件发懵;另一种是在做虚拟机直通存储,想用 DMA Remapping 把一块 NVMe 直接丢给虚拟机,摆脱文件模拟层的性能和延迟损耗。这两种诉求其实是同一件事的两端:理解 DMAR(DMA Remapping)表,让存储设备绕过宿主机 CPU 拷贝,直达客户机内存。这篇文章会带你走完从解包、验证环境、配置直通,到用 fio 验证性能和排查 IOMMU 坑的完整链路。适合虚拟化性能调优、嵌入式数据采集和存储相关研发,也适合刚接触 vfio 直通但被 BIOS 和内核参数卡住的人。
2. 直接存储为什么需要 DMAR:DMA 重映射的账与内核里的三张表
2.1 直接存储 vs 间接存储:CPU 拷贝到底贵在哪
直接存储的概念并不复杂:设备通过 DMA 把数据直接写到最终的内存或存储介质上,中间不需要 CPU 参与逐字节拷贝。反过来看间接存储,虚拟化环境里最常见的路径是 QEMU 进程把镜像文件内容读到用户态缓冲区,再通过 KVM 接口写入客户机物理内存。一块 4KB 的数据页要经历页缓存、用户态缓冲、KVM 的 mmap 区域,至少两次 memcpy,CPU 全程在场。延迟高、CPU 占用高,吞吐还容易被单进程瓶颈卡住。
DMA 直接存储的价值在于把“搬运”这件体力活交给设备本身。NVMe 控制器拿到源地址和目的地址后,自己通过 PCIe 总线写数据,CPU 只需要在 I/O 请求开始时准备地址映射,在中断到来时处理完成通知。同样是顺序读,直通后带宽普遍比 qcow2 虚拟盘高出百分之几十,延迟也能接近物理机水平。这不是夸张,而是直通把虚拟化层最重的那段拷贝路径整个删掉了。
那什么时候不适合直通?如果你需要快照、热迁移、多台虚拟机共享同一块盘,或者你的存储压力根本没到 CPU 成为瓶颈的程度,那么直通的收益不明显,反而会把运维灵活性赔进去。选型理由说到底是三条:延迟敏感、单机部署、能接受设备级绑定不迁移。
2.2 DMAR 与 IOMMU:ACPI 表、DMA 重映射、vfio-pci 三者的关系
DMAR 是 ACPI 里的 DMA Remapping Table,BIOS 在启动时把它暴露给操作系统,里面描述 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 与 iommu=pt:为什么直通存储还要关心 DMA 路径
IOMMU 开启后,设备 DMA 默认走“地址翻译”路径:内核为设备建立 I/O 页表,设备的 DMA 地址被翻译成物理地址。翻译过程本身有开销,但对现代 IOMMU 来说可以忽略;真正影响性能的是另一种情况——当 DMA 请求无法直接映射时,内核会退回 SWIOTLB(软件 IO TLB),把数据先复制到一块连续的 bounce buffer,再让设备从那里搬运。一次 DMA 变成了“设备写 bounce buffer + CPU 复制到目标地址”,直接存储就名存实亡了。
常见触发 SWIOTLB 的场景是设备 DMA 寻址范围受限,或者 IOMMU 被配置成强制翻译模式。为了解决这个问题,内核提供了 iommu=pt(passthrough)模式。在这种模式下,IOMMU 保持直通映射,设备 DMA 尽量使用物理地址,绕过页表翻译和 bounce buffer。grub 配置里加参数时,intel_iommu=on负责打开 DMA Remapping,iommu=pt负责让性能敏感的直通设备走短路径。
两种模式的取舍可以用下面这张表概括:
| 模式 | 内核参数 | DMA 路径 | 隔离性 | 适用场景 |
|---|---|---|---|---|
| 翻译模式 | intel_iommu=on | 设备地址经 IOMMU 页表翻译 | 强,设备只能访问被映射内存 | 需要安全隔离的虚拟化环境 |
| 直通模式 | intel_iommu=on iommu=pt | 设备 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-d(I/O 虚拟化),AMD 平台对应的是 AMD IOMMU(也叫 SVM)。很多服务器默认关闭这个选项,而且不同厂商的叫法不一样,有的叫“Intel Virtualization Technology for Directed I/O”,有的直接叫“DMA Remapping”。
BIOS 打开之后,内核参数还差一脚。常见发行版的 grub 配置文件位置不同,但改法一致:
# 编辑内核启动参数,三项按需添加: # intel_iommu=on 确保 Intel 平台的 DMA Remapping 真正启用 # iommu=pt 让设备 DMA 尽量走物理地址,减小翻译开销 # pcie_acs_override=downstream 仅当确认 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 设备的 BDF(Bus: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 ids=144d:a808 # 加载 nvme 驱动之前先加载 vfio-pci softdep nvme pre: vfio-pci改完配置后需要重新生成 initramfs:RHEL 系用dracut -f,Debian/Ubuntu 系用update-initramfs -u。如果忘记这一步,重启后 initramfs 阶段先加载了 nvme 驱动,设备被 nvme 接管,vfio.conf 里的配置自然落空。
4.2 QEMU 命令行直通:最直接的一条路
驱动绑定完成,启动虚拟机时把设备挂进去。一条最小可验证的 QEMU 命令行比图形界面更直观:
qemu-system-x86_64 \ -machine q35,accel=kvm \ -cpu host \ -smp 8 \ -m 16G \ -drive file=ubuntu.qcow2,if=virtio \ -device vfio-pci,host=0000:01:00.0 \ -netdev user,id=net0 \ -device virtio-net-pci,netdev=net0关键参数是-device vfio-pci,host=0000:01:00.0,它告诉 QEMU 把宿主机上的这块 NVMe 以 PCI 设备形式直通给客户机。启动后虚拟机内会出现一块裸的 NVMe 设备,看到/dev/nvme0n1就说明直通成功。这里强调一点:直通后宿主机上对应的 /dev/nvme0n1 会消失,因为设备已经被 vfio-pci 接管,不再是 nvme 驱动管理。千万别把系统盘这样直通出去,否则宿主机直接丢盘。
4.3 libvirt XML 方式:适合生产维护
命令行适合验证,生产环境还是建议用 libvirt 管理,热插拔和配置管理都方便。在虚拟机 XML 里加一段 hostdev 配置:
<hostdev mode='subsystem' type='pci' managed='yes'> <source> <address domain='0x0000' bus='0x01' slot='0x00' function='0x0'/> </source> </hostdev>managed='yes'表示让 libvirt 自动处理设备驱动接管,宿主机上不需要预先绑定 vfio-pci,libvirt 会在虚拟机启动时把设备从 nvme 驱动切到 vfio-pci,关闭虚拟机后再切回来。这个特性在测试阶段很好用,但你如果已经通过 modprobe.conf 写死了绑定,managed 与否都不影响结果。添加配置后重启虚拟机,或者用 virsh attach-device 动态挂载。
直通的代价同样明显:整块物理盘被虚拟机独占,虚拟机内做的分区、文件系统、数据都在物理盘上,宿主机完全不可见。快照、克隆、热迁移这些虚拟化便利一概没有。所以生产上直接存储只适合明确要性能、能接受设备级绑定的工作负载。
5. 直接存储的常见问题排查:fio 验证与 5 个坑
5.1 用 fio 量化性能:直通与虚拟盘的差距
直通到底值不值,别靠感觉,跑一轮 fio 就知道。建议在虚拟机内对直通盘跑一组测试,再和同样环境下 qcow2 虚拟盘对比。注意 fio 写操作会覆盖盘上既有数据,测试前先确认这块盘是空的,数据做好备份。
# 在 VM 内对直通盘跑 60 秒顺序读(4M 块,队列深度 32) fio --name=seqread --filename=/dev/nvme0n1 --rw=read \ --bs=4M --iodepth=32 --direct=1 --ioengine=libaio \ --size=8G --runtime=60 --numjobs=1 --group_reporting # 随机写,重点看 IOPS 和延迟分布 fio --name=randwrite --filename=/dev/nvme0n1 --rw=randwrite \ --bs=4k --iodepth=32 --direct=1 --ioengine=libaio \ --size=4G --runtime=60 --time_based参数里--direct=1绕过页缓存,测的就是设备真实能力;--ioengine=libaio是 Linux 下最常用的异步引擎;--iodepth=32决定队列深度,NVMe 盘对队列深度很敏感。同一台机器跑对比测试时,唯一变量应该是磁盘后端,其余参数保持一致,对比结果才有说服力。直通盘顺序读和随机 IOPS 明显高于虚拟盘,说明这条 DMA 路径是通的;如果差距不大,往下查坑。
5.2 坑 1:IOMMU 分组把多设备绑一起,虚拟机起不来
现象:绑定 vfio-pci 后启动虚拟机,QEMU 直接报Device is not behind an IOMMU或者类似 vtd 错误。
原因:目标设备所在的 IOMMU 分组含多个 PCIe 设备,没有独立隔离域。常见于消费级主板把 M.2 和 PCIe 插槽共享一组。
解决:回到第 3.3 节的脚本,确认组内成员。如果组里确实有别的设备,把它们一起直通给同一个虚拟机,或者换一个独立的插槽位。非要拆组就只能上pcie_acs_override=downstream,但要清楚它会让组内设备之间失去 DMA 隔离,多租户生产环境不建议。
5.3 坑 2:vfio-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 没有这个选项,尝试加intremap=on内核参数并重新生成 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 vfio(RHEL 系)或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 --name=uring --filename=/dev/nvme0n1 --rw=randread \ --bs=4k --iodepth=64 --direct=1 --ioengine=io_uring \ --sqthread_poll=1 --runtime=60 --time_based--ioengine=io_uring指定内核的 io_uring 接口,--sqthread_poll=1开启 SQPOLL 模式,让内核线程代替用户态主动轮询提交队列,进一步减少系统调用。这个参数对内核版本有要求,虚拟机里用之前先确认内核开启了CONFIG_IO_URING和IORING_SETUP_SQPOLL。
验证多队列有没有真正激活,进虚拟机执行ls /sys/block/nvme0n1/mq/,看到多个队列目录说明 NVMe 多队列正常工作。再配合lspci -k确认宿主机侧驱动是 vfio-pci、虚拟机内驱动是 nvme,整条链路就闭环了。
我自己最早做直通时也翻过车:BIOS 里 VT-d 没开,dmesg 看半天找不到原因;后来养成了习惯,拿到任何一台机器先跑一遍环境体检,确认 DMAR、IOMMU、分组三步都过了才碰设备。直接存储这个方向值不值得投入,关键看你的存储是不是真瓶颈——如果延迟敏感、愿意接受设备独占,它带来的收益是虚拟化其他路径很难追上的;如果只是跑常规业务,虚拟盘加上足够的缓存也够用。希望帮到你。
本文还有配套的精品资源,点击获取