首页 / 资讯中心 / 文章详情

在KeyarchOS上源码编译集成mdevctl,实现GPU mdev设备管理

在KeyarchOS上源码编译集成mdevctl,实现GPU mdev设备管理 ★ FEATURED ARTICLE
每次接手新的服务器操作系统环境都需要重新评估一大堆基础工具链的兼容性。这次要在 KeyarchOS 上把 mdevctl-0.61-3 从源码跑起来起因很实际机房里有几台物理机装了支持 SR-IOV 和 vGPU 的加速卡虚拟化平台想把计算资源切成小块分给不同虚拟机。mdevmediated device中介设备正好是这类场景的核心机制但上游发行版没有预置可用的 mdevctl 二进制包直接拷贝 RPM 又因为 glibc 和 udev 版本差异跑不起来。所以就有了这篇完整适配记录从源码编译、系统集成到实际管理 mdev 实例覆盖了谁适合参考虚拟化平台维护、GPU 资源池化、内核设备子系统相关的工程师以及所有需要在自定义 Linux 系统上部署 mdev 设备管理能力的同学。1. 项目背景与整体思路拆解1.1 mdev 到底是什么先花点篇幅把 mdev 说清楚。mdev 全称 mediated device它的思路是把一张物理 PCIe 设备典型场景是 GPU在驱动层虚拟成多个逻辑设备。每个逻辑设备有独立的地址空间、中断和 DMA 能力可以单独挂载给虚拟机。和传统的 PCIe passthrough把整张卡交给一个虚机相比mdev 的优势是“切分”而不是“分配”一块物理卡服务多个虚机硬件资源利用率能翻几倍。这种设备在 Linux 内核侧通过 VFIO-MDEV 框架实现。用户态看到的路径大体是这样物理设备在 sysfs 下有一个 mdev_supported_types 目录里面每个子目录代表一种可创建的设备类型往里写 UUID 就会触发内核创建对应的 mdev 实例。听起来不复杂但如果全靠手工操作 sysfs问题很快就来了实例一多没人记得哪个 UUID 对应哪个物理设备宿主机重启之后所有实例全部丢失也没办法声明“某类设备应该自动恢复”。1.2 为什么 KeyarchOS 需要适配一把 mdevctlmdevctl 就是为了补齐上面这些管理缺口而存在的。它做的事情可以理解成把“内核 sysfs 里零散的设备节点”翻译成“可持久化定义、可查询、可启动停止的服务”。管理员先描述一个 mdev 实例应该挂在哪个 parent 设备上、用什么类型、UUID 是多少然后 mdevctl 负责在系统里创建、维护、恢复这个实例。KeyarchOS 作为一个面向数据中心场景的操作系统内核版本、systemd 版本、目录规范都和常见的 CentOS/Fedora 环境不完全一致。直接使用别的发行版编好的 mdevctl 二进制大概率会遇到系统库版本不匹配、服务路径不对、udev rule 不触发这类毛病。更稳妥的做法是从源码单独构建一遍这样能确保它跟你系统的 glibc、json-glib、systemd 版本真正兼容也方便在编译时按需裁剪功能。1.3 整体技术路线我给这次适配定的路线分四段走。第一段确认内核能力和系统基础状态特别是 VFIO 相关模块和 IOMMU 开启情况第二段把编译依赖拉齐从源码构建 mdevctl-0.61-3 并安装到系统路径第三段完成 systemd 服务、udev 规则、运行时目录这些系统集成第四段拿真实物理设备做一轮建/删/查/停/起全生命周期测试顺便验证与 libvirt 的对接。整个过程不需要修改内核源码mdevctl 完全是个用户态工具所以适配难度其实不算高主要坑集中在依赖和路径规划上。2. 准备阶段怎么做2.1 先确认基础条件动手之前先摸清家底。在 KeyarchOS 上执行下面几条看看当前状态# 确认系统版本 cat /etc/os-release # 确认内核版本 uname -r # 检查 VFIO 相关模块 lsmod | grep -E vfio|mdev正常情况下应该能看到 vfio、vfio_iommu_type1、vfio_mdev、mdev 这些模块至少也要保证它们可以被动态加载。如果 vfio_mdev 没加载先手动加载并且写入开机模块列表modprobe mdev modprobe vfio_mdev modprobe vfio_iommu_type1另外要确认物理设备本身支持 mdev。可以到 sysfs 里检查对应 PCI 设备的 vendor 目录下是否存在 mdev_supported_typesls /sys/bus/pci/devices/0000:1b:00.0/如果有 mdev_supported_types 目录说明硬件和驱动都支持后面的 work 才有意义。我这次测试用的物理加速卡型号不方便透露但方法对任何支持 VFIO-MDEV 的设备都通用。2.2 依赖包准备清单mdevctl 的构建依赖不算重但少一个都会在编译或运行阶段让你折腾半天。下面是本次使用到的依赖清单依赖项用途缺失时的典型表现gcc / g编译源码直接报 cc: command not foundmeson / ninja构建系统meson: command not foundglib2-devel提供 GLib 类型与主循环找不到 glib.hjson-glib-devel处理设备定义 JSON 配置找不到 json-glib.hsystemd-devel提供 libudev 接口找不到 libudev.hpkg-config发现依赖路径一堆 Unknown dependency在 KeyarchOS 上用系统自带的包管理器安装即可。装完之后可以用 pkg-config 快速验证关键依赖是否可见pkg-config --exists json-glib-1.0 echo ok pkg-config --exists libudev echo ok pkg-config --exists glib-2.0 echo ok2.3 源码获取与构建方式选择mdevctl-0.61-3 这个版本源码从上游 Git 仓库拉取或者解压官方 tar 包都一样。值得注意的一点是这个版本已经全面转向 meson 构建系统不再建议用 autotools 那套 ./configure make 的路径。你解压完之后看一眼顶层目录有 meson.build 文件就说明构建系统是 meson。目录结构大体是mdevctl-0.61-3/ ├── meson.build ├── src/ │ ├── mdevctl.c │ └── ... ├── mdevctl.d/ ├── systemd/ └── udev/systemd 和 udev 两个子目录里放着服务文件和规则模板安装阶段会由 meson 自动处理。建议构建时明确指定 --prefix/usr这样二进制落在 /usr/sbinsystemd 单元落在 /usr/lib/systemd/system符合 KeyarchOS 的 FHS 习惯后面写服务引用时也省事。3. 源码编译与系统集成3.1 meson 构建完整流程依赖就绪后进入源码目录执行构建。推荐用独立 build 目录方便以后清理和重新配置cd mdevctl-0.61-3 meson setup build --prefix/usr --sysconfdir/etc ninja -C build ninja -C build installmeson 在配置阶段会检查 glib、json-glib、libudev 等依赖任何一项找不到它会在 summary 里用醒目的标记告诉你。我这次第一次配置时就因为没装 json-glib-devel导致配置中断。安装好对应包后重新跑 meson setup build它默认不会覆盖已有配置可以先清掉 build 目录再跑避免残留误判。ninja 编译过程一般不会超过一分钟。编译完成后最直观的文件验证是ls -l /usr/sbin/mdevctl file /usr/sbin/mdevctl3.2 常见编译错误与处理编译阶段我实际碰到的两个问题挺有代表性。第一个是依赖头文件找不到。报错信息类似“fatal error: json-glib/json-glib.h: No such file or directory”。这个纯粹是 json-glib-devel 没安装或者安装后 pkg-config 路径没刷新。处理方式是补包装包然后清理 build 目录重新执行 meson setup。第二个是 libudev 版本符号不一致。0.61-3 里链接 libudev 时会引用某些较新的接口如果 KeyarchOS 自带的 libudev 偏旧ninja 链接阶段会报 undefined reference。我当时看了下系统里的 systemd 版本确实偏保守。解决办法是确认 systemd-devel 已装到匹配版本而不是绕开 udev因为 mdevctl 的设备管理依赖 udev 事件砍掉这块功能就失去了一半意义。这类问题的排查思路其实很简单先看报错里缺少的符号或头文件属于哪个库然后用 pkg-config --modversion 确认版本最后判断是补包还是升级包。依赖问题顺手就解决了。3.3 systemd 与 udev 集成编译安装只是第一步真正让 mdevctl 跑起来的是系统集成。安装完成后先确认服务文件存在并启用systemctl daemon-reload systemctl enable --now mdevctl systemctl status mdevctlmdevctl.service 的主要作用是系统启动时恢复所有标记为 auto 的 mdev 实例。它实际干的事情是调用 mdevctl 的 --system 参数逻辑上等价于把所有需要自动恢复的定义重新 start 一遍。这里有个容易踩的坑默认配置文件目录是 /etc/mdevctl.d但如果服务运行时的数据目录 /run/mdevctl 不存在所有查询命令都可能报错。安装脚本通常会处理好但如果你手动换过 --runtime-dir要记得创建相应目录。udev 集成同样关键。mdevctl 安装会在 /usr/lib/udev/rules.d 下放一个规则文件功能是当 parent 设备出现或消失时自动对相关 mdev 实例做对应处理。验证规则是否生效可以用udevadm info --attribute-walk --path/sys/bus/pci/devices/0000:1b:00.0 | grep mdevctl如果没看到相关规则手动 reloadudevadm control --reload-rules udevadm trigger4. 实战操作动态管理 mdev 设备4.1 定义设备类型系统集成完成后先看硬件暴露了哪些可用类型mdevctl types输出会按 parent 设备分组每个类别下列出可创建的设备类型名。比如某型号加速卡下会有类似 1cba-001 这种厂商定义的类型代表不同显存/算力切分配比。确认类型之后查询某物理设备下全部的可用类型细节mdevctl types --parent 0000:1b:00.0这一步能验证驱动与 VFIO-MDEV 框架是否正常。如果 types 输出为空先不要怀疑 mdevctl回到内核模块和硬件支持方向排查。4.2 创建实例有了类型定义并启动一个 mdev 实例的标准命令组合是这样# 先定义一个 UUID UUID$(uuidgen) # 定义实例指定 parent、type、uuid mdevctl define --parent 0000:1b:00.0 --type 你的设备类型 --uuid $UUID # 启动实例 mdevctl start --parent 0000:1b:00.0 --uuid $UUIDdefine 和 start 是两个阶段。define 是把“这个实例存在”这件事记到 /etc/mdevctl.d 下的配置文件里属于持久化元数据行为start 才是真正往 sysfs 写 UUID触发内核创建实例。用生活化的类比讲define 像是给一台服务器在 CMDB 里建档start 才是按配置实际开机器。启动后验证实例确实出现在系统中mdevctl listlist 会列出当前所有已实例化的 mdev 设备包含 parent、UUID、类型和状态。这一条命令平时用得最频繁建议养成习惯。4.3 生命周期管理命令动态控制能力最终体现在一套完整生命周期命令上。除了上面说的 define/start常用操作还有# 查看单个实例详细配置 mdevctl get --parent 0000:1b:00.0 --uuid $UUID # 停止实例 mdevctl stop --parent 0000:1b:00.0 --uuid $UUID # 取消定义删除配置记录 mdevctl undefine --parent 0000:1b:00.0 --uuid $UUID几个关键细节要特别说明。第一stop 会把实例从系统中移除但不会删除 define 留下的配置所以 stop 之后再 start实例能恢复原状。想彻底清理就执行 undefine。第二define 可以选择是否标记为自动恢复# 标记为 auto 的实例宿主机重启后会自动重建 mdevctl define --parent 0000:1b:00.0 --type 设备类型 --uuid $UUID --auto # 标记为 manual 的实例开机不会自动启动需要手动 start mdevctl define --parent 0000:1b:00.0 --type 设备类型 --uuid $UUID --manual这个设计很符合生产需求有的设备是常驻服务想让它随系统启动有的设备是临时分配给某台虚机的不需要自动恢复。定义时不要搞混。配置文件的存储路径也要知道方便出问题时候手工检查ls /etc/mdevctl.d/每个定义对应目录下一个 JSON 文件内容包含 parent、type、uuid、启动策略。这个文件完全可以手工编辑改完再 start 也一样能生效。4.4 与 libvirt 虚拟机对接mdevctl 管理好的实例最终要交付给虚拟机使用。libvirt 对接时域 XML 里是这么写的hostdev modesubsystem typemdev managedno modelvfio-pci source address uuid插入你的uuid/ /source /hostdev把上面创建出来的 UUID 填进去虚拟机的 guest 就能看到这个 mdev 设备。值得注意的是如果实例尚未 startlibvirt 启动虚机时不会自动拉起它。通常的做法有两种一种是不管它全靠 mdevctl define --manual libvirt 之外的上层编排系统管理实例另一种是把实例标记为 auto开机由 mdevctl.service 自动拉起。我个人更推荐按“谁使用谁管理”的原则分层处理避免 libvirt 和 mdevctl 对设备状态的认知出现冲突。实际环境中我见过不少人直接把 vGPU 实例写成 auto确实省事但一旦某次错误启动导致设备冲突排查时就得花不少时间在两边找原因。5. 问题排查与运维经验速查5.1 运行期故障排查实际部署中遇到过几次 start 失败的情况。最常见的报错是Error: could not create mdev instance这种问题几乎都不是 mdevctl 本身导致的而是底层内核创建失败。排查顺序如下# 1. 确认 sysfs 中设备类型还在 ls /sys/bus/pci/devices/0000:1b:00.0/mdev_supported_types/ # 2. 用 dmesg 看内核侧报错 dmesg | tail -n 50常见原因包括IOMMU 没有开启、vfio_mdev 模块未加载、同一 parent 设备上可用资源不足比如显存切分已经用完、UUID 格式不对。mdevctl 只是个“传话筒”内核不接受就是失败。这个思路想清楚排查速度会快很多。另一个容易忽略的问题是权限。mdevctl 正常需要 root 权限运行如果你用普通用户调用即使只是 list也会因为无法读 /run/mdevctl 下的运行时数据而报错。生产环境如果希望部分运维账号能查看设备状态可以单独做 sudo 或 sudoers 配置不建议放开目录写权限。5.2 配置与管理细节配置细节里最容易出问题的是 parent 设备地址变化。同一台宿主机如果换了 PCIe 插槽0000:1b:00.0 这类 BDF 地址可能会变导致 define 时写入的旧 parent 找不到设备。mdevctl 本身不会自动更新配置里的 parent 地址。解决办法是在定义之前先确认设备 BDF 稳定或者利用 udev 规则做软链映射。不过多数服务器设备的 BDF 在 BIOS 不调整插槽顺序时是稳定的。我可以给一个建议在机房里把加速卡固定在指定 PCIe 插槽并在文档里记录 BDF 与设备序列号的对应关系这样即使系统重建也能快速恢复 mdev 定义。还有一个细节是 json-glib 对配置文件的解析比较严格如果手工编辑 JSON 时漏了逗号或者引号mdevctl list/get 都可能直接报解析错误。遇到这种情况用 python 或 jq 先校验一下 JSONjq . /etc/mdevctl.d/uuid.json比肉眼盯着看靠谱得多。5.3 回归测试与注意事项上线之前务必做一轮回归测试。我的标准流程是操作预期结果验证命令mdevctl types能看到设备类型mdevctl typesmdevctl define --auto配置文件生成ls /etc/mdevctl.dmdevctl start实例启动mdevctl listrebootauto 实例自动恢复mdevctl listmdevctl stop实例消失mdevctl list dmesgmdevctl undefine配置文件删除ls /etc/mdevctl.d这六个步骤走通适配基本就算完成。补充一点reboot 测试一定要做因为很多环境里模块加载顺序和 /run 目录初始化时机有问题只做手动 start 根本发现不了。还有个小坑是内核模块加载顺序。如果 vfio_mdev 模块在系统启动早期被加载但 mdev 模块没有先加载服务恢复实例时会报错。稳妥的做法是在 /etc/modules-load.d/ 里写模块加载列表保证顺序。6. 写在最后的个人体会这次适配做完我对 mdevctl 的价值有了更实际的认识。单看命令数量一个系统维护小工具而已但放在大集群里它决定了你能否用“配置即代码”的思路去管理 GPU 这类稀缺资源。宿主机重启后能不能自动恢复设备是衡量虚拟化平台可用性的一个硬指标mdevctl 正好补上了这一环。如果你也想在类似的自研系统或新版本发行版上部署这套能力我的建议是从编译依赖到 udev 集成整条链路都自己跑一遍别直接拿二进制包凑数。跑通之后后续维护省心很多。最后提醒一句生产环境里所有变更走变更记录mdev 配置文件也是资产别只靠脑子记 UUID。
阅读完成 · 觉得有帮助?
咨询建站