【NVIDIA】k8s-device-plugin v0.19.3 — CDI / MIG / vGPU 模块超深度代码分析之五

NVIDIA k8s-device-plugin v0.19.3 --- CDI / MIG / vGPU 模块超深度代码分析

分析对象:internal/cdiinternal/miginternal/vgpu 三大子模块

版本:v0.19.3

分析日期:2026-07-26


目录

  • [1. 模块定位总览](#1. 模块定位总览)
  • [2. CDI 模块深度分析](#2. CDI 模块深度分析)
    • [2.1 模块定位](#2.1 模块定位)
    • [2.2 模块整体结构](#2.2 模块整体结构)
    • [2.3 接口定义 --- api.go](#2.3 接口定义 — api.go)
    • [2.4 Options 模式 --- options.go](#2.4 Options 模式 — options.go)
    • [2.5 核心实现 --- cdi.go](#2.5 核心实现 — cdi.go)
    • [2.6 IMEX 通道 CDI 生成 --- imex.go](#2.6 IMEX 通道 CDI 生成 — imex.go)
    • [2.7 空实现 --- null.go](#2.7 空实现 — null.go)
    • [2.8 CDI 设备注入完整流程图](#2.8 CDI 设备注入完整流程图)
  • [3. MIG 模块深度分析](#3. MIG 模块深度分析)
    • [3.1 模块定位](#3.1 模块定位)
    • [3.2 模块整体结构](#3.2 模块整体结构)
    • [3.3 核心实现 --- mig.go](#3.3 核心实现 — mig.go)
    • [3.4 MIG 设备节点映射 --- mig-dp.go](#3.4 MIG 设备节点映射 — mig-dp.go)
    • [3.5 MIG 策略类图与流程图](#3.5 MIG 策略类图与流程图)
  • [4. vGPU 模块深度分析](#4. vGPU 模块深度分析)
    • [4.1 模块定位](#4.1 模块定位)
    • [4.2 模块整体结构](#4.2 模块整体结构)
    • [4.3 核心实现 --- vgpu.go](#4.3 核心实现 — vgpu.go)
    • [4.4 PCI 工具层 --- pciutil.go](#4.4 PCI 工具层 — pciutil.go)
    • [4.5 vGPU 设备发现与 PCI 流程图](#4.5 vGPU 设备发现与 PCI 流程图)
  • [5. 三模块协作关系](#5. 三模块协作关系)
  • [6. 关键设计总结](#6. 关键设计总结)

1. 模块定位总览

复制代码
k8s-device-plugin
├── internal/cdi    --- CDI (Container Device Interface) 规范文件生成与设备注入
├── internal/mig    --- MIG (Multi-Instance GPU) 设备信息管理与能力映射
├── internal/vgpu   --- vGPU (Virtual GPU) PCI 设备发现与驱动信息解析
└── internal/imex   --- IMEX (Import/Export) 通道管理 (被 CDI 依赖)
模块 职责 输入 输出
CDI 为容器生成 CDI Spec JSON 文件,提供 CDI Qualified Name NVML/设备信息、配置选项 CDI Spec 文件、QualifiedName
MIG 管理节点上 GPU 设备的 MIG 状态,提供按 MIG 启用/禁用分类的设备列表 resource.Manager 设备分类映射、MIG 能力路径映射
vGPU 在虚拟化环境中发现 vGPU PCI 设备,解析宿主机驱动版本信息 sysfs PCI 设备树 vGPU Device 列表、HostDriverInfo

2. CDI 模块深度分析

2.1 模块定位

CDI(Container Device Interface)是 CNCF 提出的容器设备接口规范,用于以声明式方式描述设备如何注入容器。k8s-device-plugin 的 CDI 模块负责:

  1. 生成 CDI Spec 文件 :将 NVIDIA GPU 设备信息转换为 CDI 规范的 JSON 文件,写入 /var/run/cdi/
  2. 提供 CDI Qualified Name :为 device plugin 的 Allocate 响应提供 vendor/class/deviceID 格式的设备引用名
  3. 管理附加设备:为 GDS、MOFED、GDRCopy、IMEX 通道等附加模式生成 CDI 设备

CDI 模块在系统中的位置处于 device plugin 的 Allocate 链路 上------当 Pod 请求 GPU 资源时,kubelet 调用 device plugin 的 Allocate,后者通过 CDI 模块生成设备引用名返回给容器运行时。

2.2 模块整体结构

#mermaid-svg-beEwqRZ02EgDLz6z{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-beEwqRZ02EgDLz6z .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-beEwqRZ02EgDLz6z .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-beEwqRZ02EgDLz6z .error-icon{fill:#552222;}#mermaid-svg-beEwqRZ02EgDLz6z .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-beEwqRZ02EgDLz6z .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-beEwqRZ02EgDLz6z .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-beEwqRZ02EgDLz6z .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-beEwqRZ02EgDLz6z .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-beEwqRZ02EgDLz6z .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-beEwqRZ02EgDLz6z .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-beEwqRZ02EgDLz6z .marker{fill:#333333;stroke:#333333;}#mermaid-svg-beEwqRZ02EgDLz6z .marker.cross{stroke:#333333;}#mermaid-svg-beEwqRZ02EgDLz6z svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-beEwqRZ02EgDLz6z p{margin:0;}#mermaid-svg-beEwqRZ02EgDLz6z g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-beEwqRZ02EgDLz6z g.classGroup text .title{font-weight:bolder;}#mermaid-svg-beEwqRZ02EgDLz6z .cluster-label text{fill:#333;}#mermaid-svg-beEwqRZ02EgDLz6z .cluster-label span{color:#333;}#mermaid-svg-beEwqRZ02EgDLz6z .cluster-label span p{background-color:transparent;}#mermaid-svg-beEwqRZ02EgDLz6z .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-beEwqRZ02EgDLz6z .cluster text{fill:#333;}#mermaid-svg-beEwqRZ02EgDLz6z .cluster span{color:#333;}#mermaid-svg-beEwqRZ02EgDLz6z .nodeLabel,#mermaid-svg-beEwqRZ02EgDLz6z .edgeLabel{color:#131300;}#mermaid-svg-beEwqRZ02EgDLz6z .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-beEwqRZ02EgDLz6z .label text{fill:#131300;}#mermaid-svg-beEwqRZ02EgDLz6z .labelBkg{background:#ECECFF;}#mermaid-svg-beEwqRZ02EgDLz6z .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-beEwqRZ02EgDLz6z .classTitle{font-weight:bolder;}#mermaid-svg-beEwqRZ02EgDLz6z .node rect,#mermaid-svg-beEwqRZ02EgDLz6z .node circle,#mermaid-svg-beEwqRZ02EgDLz6z .node ellipse,#mermaid-svg-beEwqRZ02EgDLz6z .node polygon,#mermaid-svg-beEwqRZ02EgDLz6z .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-beEwqRZ02EgDLz6z .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-beEwqRZ02EgDLz6z g.clickable{cursor:pointer;}#mermaid-svg-beEwqRZ02EgDLz6z g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-beEwqRZ02EgDLz6z g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-beEwqRZ02EgDLz6z .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-beEwqRZ02EgDLz6z .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-beEwqRZ02EgDLz6z .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-beEwqRZ02EgDLz6z .dashed-line{stroke-dasharray:3;}#mermaid-svg-beEwqRZ02EgDLz6z .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-beEwqRZ02EgDLz6z #compositionStart,#mermaid-svg-beEwqRZ02EgDLz6z .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-beEwqRZ02EgDLz6z #compositionEnd,#mermaid-svg-beEwqRZ02EgDLz6z .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-beEwqRZ02EgDLz6z #dependencyStart,#mermaid-svg-beEwqRZ02EgDLz6z .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-beEwqRZ02EgDLz6z #dependencyStart,#mermaid-svg-beEwqRZ02EgDLz6z .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-beEwqRZ02EgDLz6z #extensionStart,#mermaid-svg-beEwqRZ02EgDLz6z .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-beEwqRZ02EgDLz6z #extensionEnd,#mermaid-svg-beEwqRZ02EgDLz6z .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-beEwqRZ02EgDLz6z #aggregationStart,#mermaid-svg-beEwqRZ02EgDLz6z .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-beEwqRZ02EgDLz6z #aggregationEnd,#mermaid-svg-beEwqRZ02EgDLz6z .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-beEwqRZ02EgDLz6z #lollipopStart,#mermaid-svg-beEwqRZ02EgDLz6z .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-beEwqRZ02EgDLz6z #lollipopEnd,#mermaid-svg-beEwqRZ02EgDLz6z .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-beEwqRZ02EgDLz6z .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-beEwqRZ02EgDLz6z .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-beEwqRZ02EgDLz6z .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-beEwqRZ02EgDLz6z .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-beEwqRZ02EgDLz6z :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} implements
implements
creates
cdilibs map
<<interface>>
Interface
CreateSpecFile() : error
QualifiedName(class, id) : string
AdditionalDevices() : \[\]string
cdiHandler
-infolib info.Interface
-nvmllib nvml.Interface
-devicelib device.Interface
-logger *logrus.Logger
-driverRoot string
-devRoot string
-targetDriverRoot string
-targetDevRoot string
-nvidiaCTKPath string
-vendor string
-deviceIDStrategy string
-deviceListStrategies spec.DeviceListStrategies
-deviceDiscoveryStrategy string
-nvcdiFeatureFlags \[\]string
-gdsEnabled bool
-mofedEnabled bool
-gdrcopyEnabled bool
-imexChannels imex.Channels
-cdilibs mapstringnvcdi.SpecGenerator
-additionalModes \[\]string
+CreateSpecFile() : error
+QualifiedName(class, id) : string
+AdditionalDevices() : \[\]string
-getRootTransformer() : transform.Transformer
-newImexChannelSpecGenerator() : nvcdi.SpecGenerator
null
+AdditionalDevices() : \[\]string
+CreateSpecFile() : error
+QualifiedName(class, id) : string
imexChannelCDILib
-vendor string
-imexChannels imex.Channels
+GetSpec(...string)(spec.Interface, error)
nvcdi.SpecGenerator

2.3 接口定义 --- api.go

go 复制代码
type Interface interface {
    CreateSpecFile() error
    QualifiedName(string, string) string
    AdditionalDevices() []string
}

逐行解析:

方法 目的 调用时机
CreateSpecFile() 扫描所有设备,生成 CDI Spec JSON 并写入磁盘 device plugin 启动阶段
QualifiedName(class, id) 构造 vendor/class/id 格式的 CDI 设备引用名 Allocate 响应构建时
AdditionalDevices() 返回 GDS/MOFED/GDRCopy 等附加设备的 CDI 引用名 Allocate 响应构建时

//go:generate moq 注解指示使用 moq 工具自动生成 mock 文件 api_mock.go,用于单元测试。

2.4 Options 模式 --- options.go

options.go 采用 Go 的 Functional Options 模式,为 cdiHandler 的构造函数 New() 提供灵活的参数注入。

go 复制代码
type Option func(*cdiHandler)

完整 Option 清单:

Option 函数 设置字段 设计目的
WithFeatureFlags(...string) nvcdiFeatureFlags 控制 nvcdi Spec 生成的特性开关
WithDeviceListStrategies deviceListStrategies 决定是否启用 CDI(若不含任何 CDI 策略则返回 null handler)
WithDriverRoot(root) driverRoot 宿主机驱动根路径(容器化场景下可能不是 /
WithDevRoot(root) devRoot 设备文件根路径,可与 driverRoot 不同
WithTargetDriverRoot(root) targetDriverRoot 目标(容器内)驱动根路径
WithTargetDevRoot(root) targetDevRoot 目标(容器内)设备根路径
WithNvidiaCTKPath(path) nvidiaCTKPath nvidia-ctk 工具路径,CDI hook 使用
WithDeviceIDStrategy(strategy) deviceIDStrategy 设备命名策略(uuidindex
WithVendor(vendor) vendor CDI vendor 名称(通常 nvidia.com
WithGdrcopyEnabled(bool) gdrcopyEnabled 是否生成 GDRCopy CDI Spec
WithGdsEnabled(bool) gdsEnabled 是否生成 GDS CDI Spec
WithMofedEnabled(bool) mofedEnabled 是否生成 MOFED CDI Spec
WithImexChannels(Channels) imexChannels IMEX 通道列表,用于生成通道 CDI Spec
WithDeviceDiscoveryStrategy(strategy) deviceDiscoveryStrategy 设备发现策略(nvmltegra

设计意图: Options 模式使得新增配置项不需要修改 New() 的签名,调用方可按需传入选项,未传入的字段在 New() 中使用默认值。

2.5 核心实现 --- cdi.go

2.5.1 常量与结构体定义
go 复制代码
const (
    cdiRoot = "/var/run/cdi"
)

cdiRoot 是 CDI Spec JSON 文件写入的根目录,符合 CDI 规范约定。

go 复制代码
type cdiHandler struct {
    infolib   info.Interface       // go-nvlib 平台信息接口
    nvmllib   nvml.Interface       // NVML 库接口
    devicelib device.Interface     // go-nvlib 设备接口
    logger           *logrus.Logger
    driverRoot       string         // 宿主机驱动根路径
    devRoot          string         // 宿主机设备根路径
    targetDriverRoot string         // 容器内驱动根路径
    targetDevRoot    string         // 容器内设备根路径
    nvidiaCTKPath    string         // nvidia-ctk 二进制路径
    vendor           string         // CDI vendor 名
    deviceIDStrategy string         // 设备 ID 策略
    deviceListStrategies    spec.DeviceListStrategies  // 设备列表策略
    deviceDiscoveryStrategy string                     // 设备发现策略
    nvcdiFeatureFlags []string                         // nvcdi 特性标志
    gdsEnabled     bool
    mofedEnabled   bool
    gdrcopyEnabled bool
    imexChannels imex.Channels          // IMEX 通道
    cdilibs         map[string]nvcdi.SpecGenerator  // 各类的 Spec 生成器
    additionalModes []string                             // 附加模式列表
}

设计意图:

  • cdilibs 使用 map 按 class(gpuimex-channelgdsmofedgdrcopy)存储不同的 Spec 生成器实例
  • additionalModes 记录哪些附加模式被启用,供 AdditionalDevices() 使用
  • driverRoot / devRoot 分离设计,支持驱动和设备文件位于不同根路径的场景(如驱动安装在 /driver-root,设备文件在 /dev
2.5.2 New() 构造函数 --- 完整流程

#mermaid-svg-w3jIn8F2tiuNE883{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-w3jIn8F2tiuNE883 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-w3jIn8F2tiuNE883 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-w3jIn8F2tiuNE883 .error-icon{fill:#552222;}#mermaid-svg-w3jIn8F2tiuNE883 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-w3jIn8F2tiuNE883 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-w3jIn8F2tiuNE883 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-w3jIn8F2tiuNE883 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-w3jIn8F2tiuNE883 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-w3jIn8F2tiuNE883 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-w3jIn8F2tiuNE883 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-w3jIn8F2tiuNE883 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-w3jIn8F2tiuNE883 .marker.cross{stroke:#333333;}#mermaid-svg-w3jIn8F2tiuNE883 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-w3jIn8F2tiuNE883 p{margin:0;}#mermaid-svg-w3jIn8F2tiuNE883 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-w3jIn8F2tiuNE883 .cluster-label text{fill:#333;}#mermaid-svg-w3jIn8F2tiuNE883 .cluster-label span{color:#333;}#mermaid-svg-w3jIn8F2tiuNE883 .cluster-label span p{background-color:transparent;}#mermaid-svg-w3jIn8F2tiuNE883 .label text,#mermaid-svg-w3jIn8F2tiuNE883 span{fill:#333;color:#333;}#mermaid-svg-w3jIn8F2tiuNE883 .node rect,#mermaid-svg-w3jIn8F2tiuNE883 .node circle,#mermaid-svg-w3jIn8F2tiuNE883 .node ellipse,#mermaid-svg-w3jIn8F2tiuNE883 .node polygon,#mermaid-svg-w3jIn8F2tiuNE883 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-w3jIn8F2tiuNE883 .rough-node .label text,#mermaid-svg-w3jIn8F2tiuNE883 .node .label text,#mermaid-svg-w3jIn8F2tiuNE883 .image-shape .label,#mermaid-svg-w3jIn8F2tiuNE883 .icon-shape .label{text-anchor:middle;}#mermaid-svg-w3jIn8F2tiuNE883 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-w3jIn8F2tiuNE883 .rough-node .label,#mermaid-svg-w3jIn8F2tiuNE883 .node .label,#mermaid-svg-w3jIn8F2tiuNE883 .image-shape .label,#mermaid-svg-w3jIn8F2tiuNE883 .icon-shape .label{text-align:center;}#mermaid-svg-w3jIn8F2tiuNE883 .node.clickable{cursor:pointer;}#mermaid-svg-w3jIn8F2tiuNE883 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-w3jIn8F2tiuNE883 .arrowheadPath{fill:#333333;}#mermaid-svg-w3jIn8F2tiuNE883 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-w3jIn8F2tiuNE883 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-w3jIn8F2tiuNE883 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-w3jIn8F2tiuNE883 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-w3jIn8F2tiuNE883 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-w3jIn8F2tiuNE883 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-w3jIn8F2tiuNE883 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-w3jIn8F2tiuNE883 .cluster text{fill:#333;}#mermaid-svg-w3jIn8F2tiuNE883 .cluster span{color:#333;}#mermaid-svg-w3jIn8F2tiuNE883 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-w3jIn8F2tiuNE883 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-w3jIn8F2tiuNE883 rect.text{fill:none;stroke-width:0;}#mermaid-svg-w3jIn8F2tiuNE883 .icon-shape,#mermaid-svg-w3jIn8F2tiuNE883 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-w3jIn8F2tiuNE883 .icon-shape p,#mermaid-svg-w3jIn8F2tiuNE883 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-w3jIn8F2tiuNE883 .icon-shape .label rect,#mermaid-svg-w3jIn8F2tiuNE883 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-w3jIn8F2tiuNE883 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-w3jIn8F2tiuNE883 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-w3jIn8F2tiuNE883 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} No
Yes

已设置
无NVML且非tegra
有NVML或是tegra
Yes
No
Yes
No
Yes
No
Yes
No
Yes
No
New() 被调用
应用所有 Option
deviceListStrategies

是否含 CDI 策略?
返回 null{} (空实现)
deviceDiscoveryStrategy

是否设置?
返回 error
HasNvml() 且

strategy != tegra?
返回 null{}
设置默认值

logger, strategy, roots
创建 DeviceNamer
构建 commonOptions

传给 nvcdi.New()
strategy == tegra?
注入 CSV 文件路径

csvFilesForRoot()
跳过 CSV
创建 GPU SpecGenerator
imexChannels 非空?
创建 imex-channel SpecGenerator
跳过
gdrcopyEnabled?
additionalModes += gdrcopy
跳过
gdsEnabled?
additionalModes += gds
跳过
mofedEnabled?
additionalModes += mofed
跳过
为每个 additionalMode

创建 nvcdi SpecGenerator
返回 cdiHandler

关键代码逐段解析:

段1:CDI 策略检查

go 复制代码
if !c.deviceListStrategies.AnyCDIEnabled() {
    return &null{}, nil
}

DeviceListStrategies 配置不包含任何 CDI 策略(如仅使用 envvar 策略),则直接返回空实现 null{}。这是性能优化------避免不必要的 CDI Spec 生成。

段2:设备发现策略校验

go 复制代码
if c.deviceDiscoveryStrategy == "" {
    return nil, fmt.Errorf("device discovery strategy not set")
}

必须有明确的设备发现策略(nvmltegra),否则报错。

段3:NVML 可用性检查

go 复制代码
hasNVML, _ := infolib.HasNvml()
if !hasNVML && c.deviceDiscoveryStrategy != "tegra" {
    klog.Warning("No valid resources detected, creating a null CDI handler")
    return &null{}, nil
}

若无 NVML 且非 Tegra 平台,说明没有可用的 GPU 检测手段,返回 null handler。Tegra 平台使用 CSV 模式,不需要 NVML。

段4:默认值填充

go 复制代码
if c.deviceIDStrategy == "" { c.deviceIDStrategy = "uuid" }
if c.driverRoot == "" { c.driverRoot = "/" }
if c.devRoot == "" { c.devRoot = c.driverRoot }
if c.targetDriverRoot == "" { c.targetDriverRoot = c.driverRoot }
if c.targetDevRoot == "" { c.targetDevRoot = c.devRoot }

默认使用 UUID 作为设备 ID 策略,默认根路径为 /devRoot 默认等于 driverRoot,target 根默认等于源根------形成一致的默认路径映射。

段5:构建 nvcdi 公共选项

go 复制代码
commonOptions := []nvcdi.Option{
    nvcdi.WithDeviceLib(c.devicelib),
    nvcdi.WithDevRoot(c.devRoot),
    nvcdi.WithDriverRoot(c.driverRoot),
    nvcdi.WithFeatureFlags(c.nvcdiFeatureFlags...),
    nvcdi.WithInfoLib(c.infolib),
    nvcdi.WithLogger(c.logger),
    nvcdi.WithNVIDIACDIHookPath(c.nvidiaCTKPath),
    nvcdi.WithNvmlLib(c.nvmllib),
    nvcdi.WithVendor(c.vendor),
}

这些选项被所有 class 的 SpecGenerator 共享,确保设备库、根路径、日志器等一致。

段6:Tegra CSV 文件注入

go 复制代码
if c.deviceDiscoveryStrategy == "tegra" {
    commonOptions = append(commonOptions, nvcdi.WithCSVFiles(csvFilesForRoot(c.driverRoot)))
}

Tegra 平台使用 CSV 文件描述设备挂载,csvFilesForRoot() 函数在 driverRoot 下查找 CSV 目录。

段7:GPU SpecGenerator 创建

go 复制代码
c.cdilibs["gpu"], err = nvcdi.New(
    append(commonOptions,
        nvcdi.WithDeviceNamers(deviceNamer),
        nvcdi.WithClass("gpu"),
    )...,
)

GPU 是核心 class,使用 deviceNamer 按指定策略命名设备。

段8:IMEX 通道 SpecGenerator

go 复制代码
if len(c.imexChannels) > 0 {
    c.cdilibs["imex-channel"] = c.newImexChannelSpecGenerator()
}

若有配置 IMEX 通道,创建自定义的 SpecGenerator(imexChannelCDILib)。

段9:附加模式 SpecGenerator

go 复制代码
for _, mode := range c.additionalModes {
    lib, err := nvcdi.New(
        append(commonOptions, nvcdi.WithMode(mode))...,
    )
    c.cdilibs[mode] = lib
}

gdrcopygdsmofed 各创建一个 nvcdi SpecGenerator,使用不同的 Mode 参数。

2.5.3 CreateSpecFile() --- Spec 文件生成

#mermaid-svg-UcCXMmbajBRZnhYi{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UcCXMmbajBRZnhYi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UcCXMmbajBRZnhYi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UcCXMmbajBRZnhYi .error-icon{fill:#552222;}#mermaid-svg-UcCXMmbajBRZnhYi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UcCXMmbajBRZnhYi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UcCXMmbajBRZnhYi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UcCXMmbajBRZnhYi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UcCXMmbajBRZnhYi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UcCXMmbajBRZnhYi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UcCXMmbajBRZnhYi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UcCXMmbajBRZnhYi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UcCXMmbajBRZnhYi .marker.cross{stroke:#333333;}#mermaid-svg-UcCXMmbajBRZnhYi svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UcCXMmbajBRZnhYi p{margin:0;}#mermaid-svg-UcCXMmbajBRZnhYi .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-UcCXMmbajBRZnhYi .cluster-label text{fill:#333;}#mermaid-svg-UcCXMmbajBRZnhYi .cluster-label span{color:#333;}#mermaid-svg-UcCXMmbajBRZnhYi .cluster-label span p{background-color:transparent;}#mermaid-svg-UcCXMmbajBRZnhYi .label text,#mermaid-svg-UcCXMmbajBRZnhYi span{fill:#333;color:#333;}#mermaid-svg-UcCXMmbajBRZnhYi .node rect,#mermaid-svg-UcCXMmbajBRZnhYi .node circle,#mermaid-svg-UcCXMmbajBRZnhYi .node ellipse,#mermaid-svg-UcCXMmbajBRZnhYi .node polygon,#mermaid-svg-UcCXMmbajBRZnhYi .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UcCXMmbajBRZnhYi .rough-node .label text,#mermaid-svg-UcCXMmbajBRZnhYi .node .label text,#mermaid-svg-UcCXMmbajBRZnhYi .image-shape .label,#mermaid-svg-UcCXMmbajBRZnhYi .icon-shape .label{text-anchor:middle;}#mermaid-svg-UcCXMmbajBRZnhYi .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-UcCXMmbajBRZnhYi .rough-node .label,#mermaid-svg-UcCXMmbajBRZnhYi .node .label,#mermaid-svg-UcCXMmbajBRZnhYi .image-shape .label,#mermaid-svg-UcCXMmbajBRZnhYi .icon-shape .label{text-align:center;}#mermaid-svg-UcCXMmbajBRZnhYi .node.clickable{cursor:pointer;}#mermaid-svg-UcCXMmbajBRZnhYi .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-UcCXMmbajBRZnhYi .arrowheadPath{fill:#333333;}#mermaid-svg-UcCXMmbajBRZnhYi .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-UcCXMmbajBRZnhYi .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-UcCXMmbajBRZnhYi .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UcCXMmbajBRZnhYi .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-UcCXMmbajBRZnhYi .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UcCXMmbajBRZnhYi .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-UcCXMmbajBRZnhYi .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-UcCXMmbajBRZnhYi .cluster text{fill:#333;}#mermaid-svg-UcCXMmbajBRZnhYi .cluster span{color:#333;}#mermaid-svg-UcCXMmbajBRZnhYi div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-UcCXMmbajBRZnhYi .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-UcCXMmbajBRZnhYi rect.text{fill:none;stroke-width:0;}#mermaid-svg-UcCXMmbajBRZnhYi .icon-shape,#mermaid-svg-UcCXMmbajBRZnhYi .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UcCXMmbajBRZnhYi .icon-shape p,#mermaid-svg-UcCXMmbajBRZnhYi .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-UcCXMmbajBRZnhYi .icon-shape .label rect,#mermaid-svg-UcCXMmbajBRZnhYi .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UcCXMmbajBRZnhYi .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-UcCXMmbajBRZnhYi .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-UcCXMmbajBRZnhYi :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 成功
错误: empty device edits
其他错误
Yes
No
CreateSpecFile()
遍历 cdilibs map
对每个 class 调用

cdilib.GetSpec()
获取 Raw CDI Spec
应用 Root Transformer

getRootTransformer()
生成 Spec 名称

cdiapi.GenerateNameForSpec()
保存到 /var/run/cdi/{name}.json
保存成功?
继续下一个 class
记录日志,标记空 spec
返回 error
还有更多 class?
删除空 spec 的 class

从 cdilibs map
返回 nil

逐行解析关键段:

go 复制代码
var emptySpecs []string
for class, cdilib := range cdi.cdilibs {

遍历所有已注册的 Spec 生成器(gpuimex-channelgds 等)。

go 复制代码
    spec, err := cdilib.GetSpec()
    if err != nil {
        return fmt.Errorf("failed to get CDI spec: %v", err)
    }

调用各 class 的 SpecGenerator 获取 CDI Spec 对象。对于 GPU,这会通过 NVML 枚举所有 GPU 设备并生成设备节点、挂载等信息。

go 复制代码
    transformer := cdi.getRootTransformer()
    if err := transformer.Transform(spec.Raw()); err != nil {
        return fmt.Errorf("failed to transform driver root in CDI spec: %v", err)
    }

Root Transformer 的作用是将 CDI Spec 中的设备路径从宿主机根路径转换为容器内目标根路径。这是容器化部署的关键------宿主机驱动可能在 /driver-root,但容器内需要映射到 / 或其他路径。

go 复制代码
    specName, err := cdiapi.GenerateNameForSpec(spec.Raw())
    err = spec.Save(filepath.Join(cdiRoot, specName+".json"))

CDI Spec 名称由 spec 内容(vendor、class、设备列表)自动生成,保证唯一性。文件写入 /var/run/cdi/{specName}.json

go 复制代码
    if strings.Contains(err.Error(), "invalid device, empty device edits") {
        emptySpecs = append(emptySpecs, class)
        continue
    }

对于没有设备的 class(如未安装 GDS 但仍尝试生成 spec),跳过并记录。这是一种优雅降级策略。

2.5.4 getRootTransformer() --- 路径转换器
go 复制代码
func (cdi *cdiHandler) getRootTransformer() transform.Transformer {
    driverRootTransformer := transformroot.New(
        transformroot.WithRoot(cdi.driverRoot),
        transformroot.WithTargetRoot(cdi.targetDriverRoot),
        transformroot.WithRelativeTo("host"),
    )

创建驱动根路径转换器:将 Spec 中以 driverRoot 为根的路径转换为以 targetDriverRoot 为根。

go 复制代码
    if cdi.devRoot == cdi.driverRoot || cdi.devRoot == "" {
        return driverRootTransformer
    }

若 devRoot 与 driverRoot 相同,无需额外转换。

go 复制代码
    ensureDev := func(p string) string {
        return filepath.Join(strings.TrimSuffix(filepath.Clean(p), "/dev"), "/dev")
    }
    devRootTransformer := transformroot.New(
        transformroot.WithRoot(ensureDev(cdi.devRoot)),
        transformroot.WithTargetRoot(ensureDev(cdi.targetDevRoot)),
        transformroot.WithRelativeTo("host"),
    )
    return transform.Merge(driverRootTransformer, devRootTransformer)

若 devRoot 与 driverRoot 不同,额外创建设备根转换器并合并。ensureDev() 确保路径以 /dev 结尾。transform.Merge 将两个转换器合并为一个,同时处理驱动路径和设备路径的转换。

2.5.5 QualifiedName() --- CDI 设备引用名
go 复制代码
func (cdi *cdiHandler) QualifiedName(class string, id string) string {
    return cdiparser.QualifiedName(cdi.vendor, class, id)
}

生成标准 CDI 引用名 vendor/class/id,例如 nvidia.com/gpu/UUID。这是容器运行时识别设备的格式。

2.5.6 AdditionalDevices() --- 附加设备列表
go 复制代码
func (cdi *cdiHandler) AdditionalDevices() []string {
    var devices []string
    for _, mode := range cdi.additionalModes {
        if cdi.cdilibs[mode] == nil {
            continue
        }
        devices = append(devices, cdi.QualifiedName(mode, "all"))
    }
    return devices
}

为每个启用的附加模式返回 vendor/mode/all 格式的设备引用。all 是 CDI 的通配符,表示该 class 下的所有设备。例如 nvidia.com/gds/all

2.5.7 csvFilesForRoot() --- Tegra CSV 文件查找
go 复制代码
const defaultCSVMountSpecPath = "/etc/nvidia-container-runtime/host-files-for-container.d"
var defaultCSVFileNames = []string{"devices.csv", "drivers.csv", "l4t.csv"}

func csvFilesForRoot(driverRoot string) []string {
    roots := []string{driverRoot}
    if driverRoot != "/" {
        roots = append(roots, "/")
    }

优先在 driverRoot 下查找 CSV 目录,若 driverRoot 不是 /,则也检查绝对路径 /

go 复制代码
    for _, root := range roots {
        csvDir := filepath.Join(root, defaultCSVMountSpecPath)
        stat, err := os.Stat(csvDir)
        if err != nil || !stat.IsDir() { continue }
        paths := make([]string, len(defaultCSVFileNames))
        for i, f := range defaultCSVFileNames {
            paths[i] = filepath.Join(csvDir, f)
        }
        return paths
    }
    return nil
}

找到 CSV 目录后返回三个标准 CSV 文件路径。若都未找到,返回 nil,让 nvcdi 回退到默认文件列表。

2.6 IMEX 通道 CDI 生成 --- imex.go

IMEX(Import/Export)是 NVIDIA 的跨 GPU 内存直接访问通道机制。

go 复制代码
type imexChannelCDILib struct {
    vendor       string
    imexChannels imex.Channels
}

这是一个自定义的 nvcdi.SpecGenerator 实现,专门为 IMEX 通道生成 CDI Spec。

go 复制代码
func (cdi *cdiHandler) newImexChannelSpecGenerator() nvcdi.SpecGenerator {
    return &imexChannelCDILib{
        vendor:       cdi.vendor,
        imexChannels: cdi.imexChannels,
    }
}

工厂方法,由 cdiHandler.New() 在检测到 IMEX 通道时调用。

go 复制代码
func (l *imexChannelCDILib) GetSpec(...string) (spec.Interface, error) {
    var deviceSpecs []specs.Device
    for _, channel := range l.imexChannels {
        deviceSpec := specs.Device{
            Name: channel.ID,
            ContainerEdits: specs.ContainerEdits{
                DeviceNodes: []*specs.DeviceNode{
                    {
                        Path:     channel.Path,      // 容器内路径 /dev/nvidia-caps-imex-channels/channelN
                        HostPath: channel.HostPath,  // 宿主机路径 {devRoot}/dev/nvidia-caps-imex-channels/channelN
                    },
                },
            },
        }
        deviceSpecs = append(deviceSpecs, deviceSpec)
    }
    return spec.New(
        spec.WithDeviceSpecs(deviceSpecs),
        spec.WithVendor(l.vendor),
        spec.WithClass("imex-channel"),
    )
}

逐行解析:

  • 遍历所有 IMEX 通道,每个通道创建一个 CDI Device
  • ContainerEdits.DeviceNodes 声明容器内需要创建的设备节点
  • Path 是容器内路径,HostPath 是宿主机路径------这正是 CDI 的核心功能:声明设备如何从宿主机映射到容器
  • spec.WithClass("imex-channel") 设置 CDI class 为 imex-channel
  • 最终返回一个 spec.Interface 对象,供 CreateSpecFile() 使用

IMEX Channel 数据结构(来自 internal/imex):

go 复制代码
type Channel struct {
    ID       string  // 通道 ID,如 "0"
    Path     string  // 容器内路径 /dev/nvidia-caps-imex-channels/channel0
    HostPath string  // 宿主机路径 {devRoot}/dev/nvidia-caps-imex-channels/channel0
}

IMEX 通道的存在性检查会验证 HostPath 和 Path 是否为字符设备文件。

2.7 空实现 --- null.go

go 复制代码
type null struct{}
var _ Interface = &null{}

func NewNullHandler() Interface { return &null{} }
func (n *null) AdditionalDevices() []string { return nil }
func (n *null) CreateSpecFile() error { return nil }
func (n *null) QualifiedName(class string, id string) string {
    klog.Error("cannot return a qualified CDI device name with the null CDI handler")
    return ""
}

设计意图: Null Object 模式。当 CDI 未启用或无可用资源时,返回 null{} 而非 nil,避免调用方需要处理 nil 指针。所有方法为空操作,QualifiedName 记录错误日志并返回空串(理论上不应在 null 模式下被调用)。

2.8 CDI 设备注入完整流程图

#mermaid-svg-tMjPU3MWnREjAjyD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tMjPU3MWnREjAjyD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tMjPU3MWnREjAjyD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tMjPU3MWnREjAjyD .error-icon{fill:#552222;}#mermaid-svg-tMjPU3MWnREjAjyD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tMjPU3MWnREjAjyD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tMjPU3MWnREjAjyD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tMjPU3MWnREjAjyD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tMjPU3MWnREjAjyD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tMjPU3MWnREjAjyD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tMjPU3MWnREjAjyD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tMjPU3MWnREjAjyD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tMjPU3MWnREjAjyD .marker.cross{stroke:#333333;}#mermaid-svg-tMjPU3MWnREjAjyD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tMjPU3MWnREjAjyD p{margin:0;}#mermaid-svg-tMjPU3MWnREjAjyD .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-tMjPU3MWnREjAjyD .cluster-label text{fill:#333;}#mermaid-svg-tMjPU3MWnREjAjyD .cluster-label span{color:#333;}#mermaid-svg-tMjPU3MWnREjAjyD .cluster-label span p{background-color:transparent;}#mermaid-svg-tMjPU3MWnREjAjyD .label text,#mermaid-svg-tMjPU3MWnREjAjyD span{fill:#333;color:#333;}#mermaid-svg-tMjPU3MWnREjAjyD .node rect,#mermaid-svg-tMjPU3MWnREjAjyD .node circle,#mermaid-svg-tMjPU3MWnREjAjyD .node ellipse,#mermaid-svg-tMjPU3MWnREjAjyD .node polygon,#mermaid-svg-tMjPU3MWnREjAjyD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tMjPU3MWnREjAjyD .rough-node .label text,#mermaid-svg-tMjPU3MWnREjAjyD .node .label text,#mermaid-svg-tMjPU3MWnREjAjyD .image-shape .label,#mermaid-svg-tMjPU3MWnREjAjyD .icon-shape .label{text-anchor:middle;}#mermaid-svg-tMjPU3MWnREjAjyD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tMjPU3MWnREjAjyD .rough-node .label,#mermaid-svg-tMjPU3MWnREjAjyD .node .label,#mermaid-svg-tMjPU3MWnREjAjyD .image-shape .label,#mermaid-svg-tMjPU3MWnREjAjyD .icon-shape .label{text-align:center;}#mermaid-svg-tMjPU3MWnREjAjyD .node.clickable{cursor:pointer;}#mermaid-svg-tMjPU3MWnREjAjyD .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tMjPU3MWnREjAjyD .arrowheadPath{fill:#333333;}#mermaid-svg-tMjPU3MWnREjAjyD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tMjPU3MWnREjAjyD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tMjPU3MWnREjAjyD .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tMjPU3MWnREjAjyD .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tMjPU3MWnREjAjyD .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tMjPU3MWnREjAjyD .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tMjPU3MWnREjAjyD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tMjPU3MWnREjAjyD .cluster text{fill:#333;}#mermaid-svg-tMjPU3MWnREjAjyD .cluster span{color:#333;}#mermaid-svg-tMjPU3MWnREjAjyD div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-tMjPU3MWnREjAjyD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tMjPU3MWnREjAjyD rect.text{fill:none;stroke-width:0;}#mermaid-svg-tMjPU3MWnREjAjyD .icon-shape,#mermaid-svg-tMjPU3MWnREjAjyD .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tMjPU3MWnREjAjyD .icon-shape p,#mermaid-svg-tMjPU3MWnREjAjyD .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tMjPU3MWnREjAjyD .icon-shape .label rect,#mermaid-svg-tMjPU3MWnREjAjyD .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tMjPU3MWnREjAjyD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tMjPU3MWnREjAjyD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tMjPU3MWnREjAjyD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 容器创建
Allocate阶段
启动阶段
No
Yes
device plugin 启动
调用 cdi.New()
DeviceListStrategies

含CDI?
返回 null handler
构建 cdiHandler

初始化 cdilibs map
调用 CreateSpecFile()
遍历 GPU/IMEX/GDS/MOFED/GDRCopy
GetSpec() 获取设备信息
Root Transform

路径转换
写入 /var/run/cdi/{name}.json
kubelet 调用 Allocate
构建 Allocate Response
对每个请求的设备调用

QualifiedName(class, uuid)
得到 nvidia.com/gpu/UUID
调用 AdditionalDevices()

获取 GDS/MOFED 等
组合 CDI Device IDs
返回给容器运行时
容器运行时读取

CDI Device IDs
解析 /var/run/cdi/*.json
按 Spec 注入设备节点

挂载、环境变量等
容器启动

GPU 可用


3. MIG 模块深度分析

3.1 模块定位

MIG(Multi-Instance GPU)是 NVIDIA Ampere 及更新架构 GPU 的硬件级分区特性,允许将一张 GPU 划分为多个独立实例。MIG 模块负责:

  1. 设备分类管理:将节点上的 GPU 按 MIG 启用/禁用状态分类
  2. MIG 设备枚举:获取所有已配置的 MIG 实例设备
  3. MIG 能力路径映射 :将 /proc/driver/nvidia-caps/mig-minors 文件中的能力描述映射到 /dev/nvidia-caps/nvidia-capN 设备节点路径

3.2 模块整体结构

#mermaid-svg-ZUhXTjraArw856Oj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ZUhXTjraArw856Oj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZUhXTjraArw856Oj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZUhXTjraArw856Oj .error-icon{fill:#552222;}#mermaid-svg-ZUhXTjraArw856Oj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZUhXTjraArw856Oj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZUhXTjraArw856Oj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZUhXTjraArw856Oj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZUhXTjraArw856Oj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZUhXTjraArw856Oj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZUhXTjraArw856Oj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZUhXTjraArw856Oj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZUhXTjraArw856Oj .marker.cross{stroke:#333333;}#mermaid-svg-ZUhXTjraArw856Oj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZUhXTjraArw856Oj p{margin:0;}#mermaid-svg-ZUhXTjraArw856Oj g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-ZUhXTjraArw856Oj g.classGroup text .title{font-weight:bolder;}#mermaid-svg-ZUhXTjraArw856Oj .cluster-label text{fill:#333;}#mermaid-svg-ZUhXTjraArw856Oj .cluster-label span{color:#333;}#mermaid-svg-ZUhXTjraArw856Oj .cluster-label span p{background-color:transparent;}#mermaid-svg-ZUhXTjraArw856Oj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ZUhXTjraArw856Oj .cluster text{fill:#333;}#mermaid-svg-ZUhXTjraArw856Oj .cluster span{color:#333;}#mermaid-svg-ZUhXTjraArw856Oj .nodeLabel,#mermaid-svg-ZUhXTjraArw856Oj .edgeLabel{color:#131300;}#mermaid-svg-ZUhXTjraArw856Oj .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-ZUhXTjraArw856Oj .label text{fill:#131300;}#mermaid-svg-ZUhXTjraArw856Oj .labelBkg{background:#ECECFF;}#mermaid-svg-ZUhXTjraArw856Oj .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-ZUhXTjraArw856Oj .classTitle{font-weight:bolder;}#mermaid-svg-ZUhXTjraArw856Oj .node rect,#mermaid-svg-ZUhXTjraArw856Oj .node circle,#mermaid-svg-ZUhXTjraArw856Oj .node ellipse,#mermaid-svg-ZUhXTjraArw856Oj .node polygon,#mermaid-svg-ZUhXTjraArw856Oj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZUhXTjraArw856Oj .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-ZUhXTjraArw856Oj g.clickable{cursor:pointer;}#mermaid-svg-ZUhXTjraArw856Oj g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-ZUhXTjraArw856Oj g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-ZUhXTjraArw856Oj .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-ZUhXTjraArw856Oj .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-ZUhXTjraArw856Oj .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-ZUhXTjraArw856Oj .dashed-line{stroke-dasharray:3;}#mermaid-svg-ZUhXTjraArw856Oj .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-ZUhXTjraArw856Oj #compositionStart,#mermaid-svg-ZUhXTjraArw856Oj .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ZUhXTjraArw856Oj #compositionEnd,#mermaid-svg-ZUhXTjraArw856Oj .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ZUhXTjraArw856Oj #dependencyStart,#mermaid-svg-ZUhXTjraArw856Oj .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ZUhXTjraArw856Oj #dependencyStart,#mermaid-svg-ZUhXTjraArw856Oj .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ZUhXTjraArw856Oj #extensionStart,#mermaid-svg-ZUhXTjraArw856Oj .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ZUhXTjraArw856Oj #extensionEnd,#mermaid-svg-ZUhXTjraArw856Oj .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ZUhXTjraArw856Oj #aggregationStart,#mermaid-svg-ZUhXTjraArw856Oj .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ZUhXTjraArw856Oj #aggregationEnd,#mermaid-svg-ZUhXTjraArw856Oj .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ZUhXTjraArw856Oj #lollipopStart,#mermaid-svg-ZUhXTjraArw856Oj .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ZUhXTjraArw856Oj #lollipopEnd,#mermaid-svg-ZUhXTjraArw856Oj .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ZUhXTjraArw856Oj .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-ZUhXTjraArw856Oj .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ZUhXTjraArw856Oj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ZUhXTjraArw856Oj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ZUhXTjraArw856Oj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} manager
manages
devicesMap cache
DeviceInfo
-manager resource.Manager
-devicesMap mapbool\[\]resource.Device
+GetDevicesMap()(mapbool\[\]resource.Device, error)
+GetDevicesWithMigEnabled()(\[\]resource.Device, error)
+GetDevicesWithMigDisabled()(\[\]resource.Device, error)
+AnyMigEnabledDeviceIsEmpty()(bool, error)
+GetAllMigDevices()(\[\]resource.Device, error)
<<interface>>
resource_Manager
+GetDevices()(\[\]Device, error)
<<interface>>
resource_Device
+IsMigEnabled()(bool, error)
+GetMigDevices()(\[\]Device, error)

3.3 核心实现 --- mig.go

3.3.1 结构体定义
go 复制代码
type DeviceInfo struct {
    manager    resource.Manager           // 资源管理器接口
    devicesMap map[bool][]resource.Device  // 按 MIG 启用状态分类的设备缓存
}

设计意图:

  • devicesMap 使用 bool 键(true=MIG已启用,false=MIG未启用),实现 O(1) 分类查询
  • 懒加载+缓存devicesMap 初始为 nil,首次调用 GetDevicesMap() 时初始化,后续直接返回缓存
3.3.2 NewDeviceInfo()
go 复制代码
func NewDeviceInfo(manager resource.Manager) *DeviceInfo {
    return &DeviceInfo{
        manager:    manager,
        devicesMap: nil, // Is initialized on first use
    }
}

工厂方法,注入 resource.Manager 依赖。devicesMap 延迟初始化。

3.3.3 GetDevicesMap() --- 核心方法
go 复制代码
func (di *DeviceInfo) GetDevicesMap() (map[bool][]resource.Device, error) {
    if di.devicesMap != nil {
        return di.devicesMap, nil
    }

缓存检查:若已初始化则直接返回,避免重复扫描设备的开销。

go 复制代码
    devices, err := di.manager.GetDevices()
    if err != nil {
        return nil, err
    }

通过 resource.Manager 获取节点上所有 GPU 设备。底层调用 NVML 的 nvmlDeviceGetCount + nvmlDeviceGetHandleByIndex

go 复制代码
    migEnabledDevicesMap := make(map[bool][]resource.Device)
    for _, d := range devices {
        isMigEnabled, err := d.IsMigEnabled()
        if err != nil {
            return nil, err
        }
        migEnabledDevicesMap[isMigEnabled] = append(migEnabledDevicesMap[isMigEnabled], d)
    }

遍历每个设备,调用 IsMigEnabled() 检查 MIG 状态。底层通过 NVML nvmlDeviceGetMigMode 获取。将设备按 MIG 状态分桶到 map 中。

go 复制代码
    di.devicesMap = migEnabledDevicesMap
    return di.devicesMap, nil
}

缓存结果并返回。

3.3.4 辅助查询方法
go 复制代码
func (di *DeviceInfo) GetDevicesWithMigEnabled() ([]resource.Device, error) {
    devicesMap, err := di.GetDevicesMap()
    if err != nil { return nil, err }
    return devicesMap[true], nil
}

返回 MIG 已启用的设备列表。若无此类设备,返回 nil(map 中不存在的键返回零值 nil slice)。

go 复制代码
func (di *DeviceInfo) GetDevicesWithMigDisabled() ([]resource.Device, error) {
    devicesMap, err := di.GetDevicesMap()
    if err != nil { return nil, err }
    return devicesMap[false], nil
}

返回 MIG 未启用的设备列表。

3.3.5 AnyMigEnabledDeviceIsEmpty() --- 空实例检测
go 复制代码
func (di *DeviceInfo) AnyMigEnabledDeviceIsEmpty() (bool, error) {
    devicesMap, err := di.GetDevicesMap()
    if err != nil { return false, err }

    if len(devicesMap[true]) == 0 {
        return true, nil  // 空集:无 MIG 启用设备
    }

    for _, d := range devicesMap[true] {
        migs, err := d.GetMigDevices()
        if err != nil { return false, err }
        if len(migs) == 0 {
            return true, nil  // MIG 已启用但未配置任何实例
        }
    }
    return false, nil
}

业务逻辑: 检查是否存在"MIG 已启用但未创建任何 MIG 实例"的 GPU。这种情况意味着 MIG 模式已开启但尚未配置分区------device plugin 需要知道这个状态以决定是否暴露 MIG 资源或发出告警。

空集定义: 若没有 MIG 启用的设备,也返回 true("空集上的属性为真"------数学约定)。

3.3.6 GetAllMigDevices() --- 全量 MIG 设备
go 复制代码
func (di *DeviceInfo) GetAllMigDevices() ([]resource.Device, error) {
    devicesMap, err := di.GetDevicesMap()
    if err != nil { return nil, err }

    var migs []resource.Device
    for _, d := range devicesMap[true] {
        devs, err := d.GetMigDevices()
        if err != nil { return nil, err }
        migs = append(migs, devs...)
    }
    return migs, nil
}

遍历所有 MIG 启用的 GPU,聚合其下所有 MIG 实例设备。底层调用 nvmlDeviceGetMigDevices() 获取每个 GPU 的 MIG 实例列表。

3.4 MIG 设备节点映射 --- mig-dp.go

3.4.1 常量定义
go 复制代码
const (
    nvidiaProcDriverPath   = "/proc/driver/nvidia"
    nvidiaCapabilitiesPath = nvidiaProcDriverPath + "/capabilities"
    nvcapsProcDriverPath   = "/proc/driver/nvidia-caps"
    nvcapsMigMinorsPath    = nvcapsProcDriverPath + "/mig-minors"
    nvcapsDevicePath       = "/dev/nvidia-caps"
)
路径 用途
/proc/driver/nvidia/capabilities NVIDIA 能力文件目录,包含 MIG 配置/访问能力描述
/proc/driver/nvidia-caps/mig-minors MIG 设备 minor 号映射文件
/dev/nvidia-caps MIG 设备节点目录
3.4.2 GetMigCapabilityDevicePaths() --- 完整流程

#mermaid-svg-3Gr5IUqVJWJjTueb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3Gr5IUqVJWJjTueb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3Gr5IUqVJWJjTueb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3Gr5IUqVJWJjTueb .error-icon{fill:#552222;}#mermaid-svg-3Gr5IUqVJWJjTueb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3Gr5IUqVJWJjTueb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3Gr5IUqVJWJjTueb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3Gr5IUqVJWJjTueb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3Gr5IUqVJWJjTueb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3Gr5IUqVJWJjTueb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3Gr5IUqVJWJjTueb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3Gr5IUqVJWJjTueb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3Gr5IUqVJWJjTueb .marker.cross{stroke:#333333;}#mermaid-svg-3Gr5IUqVJWJjTueb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3Gr5IUqVJWJjTueb p{margin:0;}#mermaid-svg-3Gr5IUqVJWJjTueb .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3Gr5IUqVJWJjTueb .cluster-label text{fill:#333;}#mermaid-svg-3Gr5IUqVJWJjTueb .cluster-label span{color:#333;}#mermaid-svg-3Gr5IUqVJWJjTueb .cluster-label span p{background-color:transparent;}#mermaid-svg-3Gr5IUqVJWJjTueb .label text,#mermaid-svg-3Gr5IUqVJWJjTueb span{fill:#333;color:#333;}#mermaid-svg-3Gr5IUqVJWJjTueb .node rect,#mermaid-svg-3Gr5IUqVJWJjTueb .node circle,#mermaid-svg-3Gr5IUqVJWJjTueb .node ellipse,#mermaid-svg-3Gr5IUqVJWJjTueb .node polygon,#mermaid-svg-3Gr5IUqVJWJjTueb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3Gr5IUqVJWJjTueb .rough-node .label text,#mermaid-svg-3Gr5IUqVJWJjTueb .node .label text,#mermaid-svg-3Gr5IUqVJWJjTueb .image-shape .label,#mermaid-svg-3Gr5IUqVJWJjTueb .icon-shape .label{text-anchor:middle;}#mermaid-svg-3Gr5IUqVJWJjTueb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3Gr5IUqVJWJjTueb .rough-node .label,#mermaid-svg-3Gr5IUqVJWJjTueb .node .label,#mermaid-svg-3Gr5IUqVJWJjTueb .image-shape .label,#mermaid-svg-3Gr5IUqVJWJjTueb .icon-shape .label{text-align:center;}#mermaid-svg-3Gr5IUqVJWJjTueb .node.clickable{cursor:pointer;}#mermaid-svg-3Gr5IUqVJWJjTueb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3Gr5IUqVJWJjTueb .arrowheadPath{fill:#333333;}#mermaid-svg-3Gr5IUqVJWJjTueb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3Gr5IUqVJWJjTueb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3Gr5IUqVJWJjTueb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3Gr5IUqVJWJjTueb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3Gr5IUqVJWJjTueb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3Gr5IUqVJWJjTueb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3Gr5IUqVJWJjTueb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3Gr5IUqVJWJjTueb .cluster text{fill:#333;}#mermaid-svg-3Gr5IUqVJWJjTueb .cluster span{color:#333;}#mermaid-svg-3Gr5IUqVJWJjTueb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3Gr5IUqVJWJjTueb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3Gr5IUqVJWJjTueb rect.text{fill:none;stroke-width:0;}#mermaid-svg-3Gr5IUqVJWJjTueb .icon-shape,#mermaid-svg-3Gr5IUqVJWJjTueb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3Gr5IUqVJWJjTueb .icon-shape p,#mermaid-svg-3Gr5IUqVJWJjTueb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3Gr5IUqVJWJjTueb .icon-shape .label rect,#mermaid-svg-3Gr5IUqVJWJjTueb .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3Gr5IUqVJWJjTueb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3Gr5IUqVJWJjTueb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3Gr5IUqVJWJjTueb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 不存在
存在
gpu%d/gi%d/ci%d/access %d
gpu%d/gi%d/access %d
config %d
monitor %d
无法解析
Yes
No
GetMigCapabilityDevicePaths()
打开 /proc/driver/nvidia-caps/mig-minors
文件存在?
返回 nil, nil

(非 MIG 机器)
逐行扫描文件
行格式匹配
构造 CI access 能力路径

/proc/driver/nvidia/capabilities/gpuN/mig/giN/ciN/access
构造 GI access 能力路径

/proc/driver/nvidia/capabilities/gpuN/mig/giN/access
构造 MIG config 路径

/proc/driver/nvidia/capabilities/mig/config
构造 MIG monitor 路径

/proc/driver/nvidia/capabilities/mig/monitor
跳过,记录错误
映射到 /dev/nvidia-caps/nvidia-cap{minor}
还有更多行?
返回 capsDevicePaths map

逐行解析:

go 复制代码
func GetMigCapabilityDevicePaths() (map[string]string, error) {
    minorsFile, err := os.Open(nvcapsMigMinorsPath)
    if os.IsNotExist(err) {
        return nil, nil  // 非 MIG 机器,正常情况
    }
    if err != nil {
        return nil, fmt.Errorf("error opening MIG minors file: %v", err)
    }
    defer minorsFile.Close()

打开 mig-minors 文件。若不存在,说明内核未加载 NVIDIA MIG 模块,返回 nil(非错误)。

go 复制代码
    processLine := func(line string) (string, int, error) {
        var gpu, gi, ci, migMinor int

定义行解析闭包,返回 (能力路径, minor号, 错误)

go 复制代码
        // CI access: gpu0/gi1/ci0/access 123
        n, _ := fmt.Sscanf(line, "gpu%d/gi%d/ci%d/access %d", &gpu, &gi, &ci, &migMinor)
        if n == 4 {
            capPath := fmt.Sprintf(nvidiaCapabilitiesPath+"/gpu%d/mig/gi%d/ci%d/access", gpu, gi, ci)
            return capPath, migMinor, nil
        }

匹配 CI(Compute Instance)访问行。路径格式:/proc/driver/nvidia/capabilities/gpu0/mig/gi1/ci0/access

go 复制代码
        // GI access: gpu0/gi1/access 122
        n, _ = fmt.Sscanf(line, "gpu%d/gi%d/access %d", &gpu, &gi, &migMinor)
        if n == 3 {
            capPath := fmt.Sprintf(nvidiaCapabilitiesPath+"/gpu%d/mig/gi%d/access", gpu, gi)
            return capPath, migMinor, nil
        }

匹配 GI(GPU Instance)访问行。路径格式:/proc/driver/nvidia/capabilities/gpu0/mig/gi1/access

go 复制代码
        // Config: config 120
        n, _ = fmt.Sscanf(line, "config %d", &migMinor)
        if n == 1 {
            capPath := fmt.Sprintf(nvidiaCapabilitiesPath + "/mig/config")
            return capPath, migMinor, nil
        }

匹配 MIG 配置行。路径格式:/proc/driver/nvidia/capabilities/mig/config

go 复制代码
        // Monitor: monitor 121
        n, _ = fmt.Sscanf(line, "monitor %d", &migMinor)
        if n == 1 {
            capPath := fmt.Sprintf(nvidiaCapabilitiesPath + "/mig/monitor")
            return capPath, migMinor, nil
        }

匹配 MIG 监控行。路径格式:/proc/driver/nvidia/capabilities/mig/monitor

go 复制代码
    capsDevicePaths := make(map[string]string)
    scanner := bufio.NewScanner(minorsFile)
    for scanner.Scan() {
        capPath, migMinor, err := processLine(scanner.Text())
        if err != nil {
            klog.Errorf("Skipping line in MIG minors file: %v", err)
            continue
        }
        capsDevicePaths[capPath] = fmt.Sprintf(nvcapsDevicePath+"/nvidia-cap%d", migMinor)
    }
    return capsDevicePaths, nil
}

逐行扫描文件,将每行解析为 能力路径 → 设备节点路径 的映射。例如:

复制代码
/proc/driver/nvidia/capabilities/gpu0/mig/gi1/ci0/access → /dev/nvidia-caps/nvidia-cap123

业务意义: MIG 设备在 Linux 中通过 nvidia-caps 设备节点暴露。这个映射告诉 device plugin:当容器需要访问某个 MIG 实例时,需要注入哪个 /dev/nvidia-caps/nvidia-capN 设备节点。这是 MIG 资源 Allocate 的底层基础。

3.5 MIG 策略类图与流程图

#mermaid-svg-wQ2qDeWYUKv9aCez{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wQ2qDeWYUKv9aCez .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wQ2qDeWYUKv9aCez .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wQ2qDeWYUKv9aCez .error-icon{fill:#552222;}#mermaid-svg-wQ2qDeWYUKv9aCez .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wQ2qDeWYUKv9aCez .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wQ2qDeWYUKv9aCez .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wQ2qDeWYUKv9aCez .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wQ2qDeWYUKv9aCez .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wQ2qDeWYUKv9aCez .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wQ2qDeWYUKv9aCez .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wQ2qDeWYUKv9aCez .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wQ2qDeWYUKv9aCez .marker.cross{stroke:#333333;}#mermaid-svg-wQ2qDeWYUKv9aCez svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wQ2qDeWYUKv9aCez p{margin:0;}#mermaid-svg-wQ2qDeWYUKv9aCez .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-wQ2qDeWYUKv9aCez .cluster-label text{fill:#333;}#mermaid-svg-wQ2qDeWYUKv9aCez .cluster-label span{color:#333;}#mermaid-svg-wQ2qDeWYUKv9aCez .cluster-label span p{background-color:transparent;}#mermaid-svg-wQ2qDeWYUKv9aCez .label text,#mermaid-svg-wQ2qDeWYUKv9aCez span{fill:#333;color:#333;}#mermaid-svg-wQ2qDeWYUKv9aCez .node rect,#mermaid-svg-wQ2qDeWYUKv9aCez .node circle,#mermaid-svg-wQ2qDeWYUKv9aCez .node ellipse,#mermaid-svg-wQ2qDeWYUKv9aCez .node polygon,#mermaid-svg-wQ2qDeWYUKv9aCez .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wQ2qDeWYUKv9aCez .rough-node .label text,#mermaid-svg-wQ2qDeWYUKv9aCez .node .label text,#mermaid-svg-wQ2qDeWYUKv9aCez .image-shape .label,#mermaid-svg-wQ2qDeWYUKv9aCez .icon-shape .label{text-anchor:middle;}#mermaid-svg-wQ2qDeWYUKv9aCez .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-wQ2qDeWYUKv9aCez .rough-node .label,#mermaid-svg-wQ2qDeWYUKv9aCez .node .label,#mermaid-svg-wQ2qDeWYUKv9aCez .image-shape .label,#mermaid-svg-wQ2qDeWYUKv9aCez .icon-shape .label{text-align:center;}#mermaid-svg-wQ2qDeWYUKv9aCez .node.clickable{cursor:pointer;}#mermaid-svg-wQ2qDeWYUKv9aCez .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-wQ2qDeWYUKv9aCez .arrowheadPath{fill:#333333;}#mermaid-svg-wQ2qDeWYUKv9aCez .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-wQ2qDeWYUKv9aCez .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-wQ2qDeWYUKv9aCez .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wQ2qDeWYUKv9aCez .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-wQ2qDeWYUKv9aCez .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wQ2qDeWYUKv9aCez .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-wQ2qDeWYUKv9aCez .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wQ2qDeWYUKv9aCez .cluster text{fill:#333;}#mermaid-svg-wQ2qDeWYUKv9aCez .cluster span{color:#333;}#mermaid-svg-wQ2qDeWYUKv9aCez div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-wQ2qDeWYUKv9aCez .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-wQ2qDeWYUKv9aCez rect.text{fill:none;stroke-width:0;}#mermaid-svg-wQ2qDeWYUKv9aCez .icon-shape,#mermaid-svg-wQ2qDeWYUKv9aCez .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wQ2qDeWYUKv9aCez .icon-shape p,#mermaid-svg-wQ2qDeWYUKv9aCez .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-wQ2qDeWYUKv9aCez .icon-shape .label rect,#mermaid-svg-wQ2qDeWYUKv9aCez .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wQ2qDeWYUKv9aCez .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-wQ2qDeWYUKv9aCez .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-wQ2qDeWYUKv9aCez :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} MIG设备节点映射
MIG资源暴露
MIG设备发现
Yes
No
NewDeviceInfo(manager)
DeviceInfo 创建

devicesMap = nil
GetDevicesMap()
devicesMap 已缓存?
直接返回缓存
manager.GetDevices()

获取所有GPU
遍历每个GPU
d.IsMigEnabled()
按 true/false 分桶
缓存 devicesMap
返回 map
GetAllMigDevices()
获取 MIG-enabled GPU 列表
遍历每个GPU
d.GetMigDevices()
聚合所有MIG实例
返回MIG设备列表
GetMigCapabilityDevicePaths()
读取 mig-minors 文件
解析每行

CI/GI/config/monitor
构造能力路径→设备节点映射
返回 capsDevicePaths map


4. vGPU 模块深度分析

4.1 模块定位

vGPU 模块用于在虚拟化环境中:

  1. 发现 vGPU PCI 设备:通过 sysfs 扫描 NVIDIA PCI 设备,识别哪些是 vGPU 设备
  2. 解析宿主机驱动信息:从 vGPU PCI 配置空间的 Vendor Specific Capability 中提取宿主机驱动版本和分支
  3. 提供设备信息:为 GFD(GPU Feature Discovery)等组件提供 vGPU 设备元数据

4.2 模块整体结构

#mermaid-svg-Us2rEAHyOl5PZaaR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Us2rEAHyOl5PZaaR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Us2rEAHyOl5PZaaR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Us2rEAHyOl5PZaaR .error-icon{fill:#552222;}#mermaid-svg-Us2rEAHyOl5PZaaR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Us2rEAHyOl5PZaaR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Us2rEAHyOl5PZaaR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Us2rEAHyOl5PZaaR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Us2rEAHyOl5PZaaR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Us2rEAHyOl5PZaaR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Us2rEAHyOl5PZaaR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Us2rEAHyOl5PZaaR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Us2rEAHyOl5PZaaR .marker.cross{stroke:#333333;}#mermaid-svg-Us2rEAHyOl5PZaaR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Us2rEAHyOl5PZaaR p{margin:0;}#mermaid-svg-Us2rEAHyOl5PZaaR g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-Us2rEAHyOl5PZaaR g.classGroup text .title{font-weight:bolder;}#mermaid-svg-Us2rEAHyOl5PZaaR .cluster-label text{fill:#333;}#mermaid-svg-Us2rEAHyOl5PZaaR .cluster-label span{color:#333;}#mermaid-svg-Us2rEAHyOl5PZaaR .cluster-label span p{background-color:transparent;}#mermaid-svg-Us2rEAHyOl5PZaaR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Us2rEAHyOl5PZaaR .cluster text{fill:#333;}#mermaid-svg-Us2rEAHyOl5PZaaR .cluster span{color:#333;}#mermaid-svg-Us2rEAHyOl5PZaaR .nodeLabel,#mermaid-svg-Us2rEAHyOl5PZaaR .edgeLabel{color:#131300;}#mermaid-svg-Us2rEAHyOl5PZaaR .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-Us2rEAHyOl5PZaaR .label text{fill:#131300;}#mermaid-svg-Us2rEAHyOl5PZaaR .labelBkg{background:#ECECFF;}#mermaid-svg-Us2rEAHyOl5PZaaR .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-Us2rEAHyOl5PZaaR .classTitle{font-weight:bolder;}#mermaid-svg-Us2rEAHyOl5PZaaR .node rect,#mermaid-svg-Us2rEAHyOl5PZaaR .node circle,#mermaid-svg-Us2rEAHyOl5PZaaR .node ellipse,#mermaid-svg-Us2rEAHyOl5PZaaR .node polygon,#mermaid-svg-Us2rEAHyOl5PZaaR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Us2rEAHyOl5PZaaR .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-Us2rEAHyOl5PZaaR g.clickable{cursor:pointer;}#mermaid-svg-Us2rEAHyOl5PZaaR g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-Us2rEAHyOl5PZaaR g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-Us2rEAHyOl5PZaaR .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-Us2rEAHyOl5PZaaR .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-Us2rEAHyOl5PZaaR .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-Us2rEAHyOl5PZaaR .dashed-line{stroke-dasharray:3;}#mermaid-svg-Us2rEAHyOl5PZaaR .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-Us2rEAHyOl5PZaaR #compositionStart,#mermaid-svg-Us2rEAHyOl5PZaaR .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-Us2rEAHyOl5PZaaR #compositionEnd,#mermaid-svg-Us2rEAHyOl5PZaaR .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-Us2rEAHyOl5PZaaR #dependencyStart,#mermaid-svg-Us2rEAHyOl5PZaaR .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-Us2rEAHyOl5PZaaR #dependencyStart,#mermaid-svg-Us2rEAHyOl5PZaaR .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-Us2rEAHyOl5PZaaR #extensionStart,#mermaid-svg-Us2rEAHyOl5PZaaR .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-Us2rEAHyOl5PZaaR #extensionEnd,#mermaid-svg-Us2rEAHyOl5PZaaR .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-Us2rEAHyOl5PZaaR #aggregationStart,#mermaid-svg-Us2rEAHyOl5PZaaR .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-Us2rEAHyOl5PZaaR #aggregationEnd,#mermaid-svg-Us2rEAHyOl5PZaaR .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-Us2rEAHyOl5PZaaR #lollipopStart,#mermaid-svg-Us2rEAHyOl5PZaaR .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-Us2rEAHyOl5PZaaR #lollipopEnd,#mermaid-svg-Us2rEAHyOl5PZaaR .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-Us2rEAHyOl5PZaaR .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-Us2rEAHyOl5PZaaR .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Us2rEAHyOl5PZaaR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Us2rEAHyOl5PZaaR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Us2rEAHyOl5PZaaR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} implements
implements
implements
pci
pci
creates
<<interface>>
Interface
Devices()(\[\]*Device, error)
Lib
-pci NvidiaPCI
+Devices()(\[\]*Device, error)
+IsVGPUDevice(capability \[\]byte) : bool
<<interface>>
NvidiaPCI
Devices()(\[\]*PCIDevice, error)
NvidiaPCILib
+Devices()(\[\]*PCIDevice, error)
PCIDevice
+Path string
+Address string
+Class string
+Vendor string
+Config \[\]byte
+GetVendorSpecificCapability()(\[\]byte, error)
Device
-pci *PCIDevice
-vGPUCapability \[\]byte
+GetInfo()(*Info, error)
Info
+HostDriverVersion string
+HostDriverBranch string
MockNvidiaPCI
-devices \[\]*PCIDevice
+Devices()(\[\]*PCIDevice, error)

4.3 核心实现 --- vgpu.go

4.3.1 常量定义
go 复制代码
const (
    VGPUCapabilityRecordStart uint8 = 5     // vGPU 能力记录起始偏移
    HostDriverVersionLength         = 10    // 驱动版本字符串最大长度
    HostDriverBranchLength          = 10    // 驱动分支字符串最大长度
)

PCI 配置空间 Vendor Specific Capability 布局:

复制代码
偏移 0: Capability ID (0x09)
偏移 1: Next Capability Pointer
偏移 2: Capability Length
偏移 3: vGPU 签名高字节 (0x56 = 'V')
偏移 4: vGPU 签名低字节 (0x46 = 'F')
偏移 5: 第一条记录起始 (VGPUCapabilityRecordStart)
4.3.2 结构体与接口
go 复制代码
type Interface interface {
    Devices() ([]*Device, error)
}

type Device struct {
    pci            *PCIDevice
    vGPUCapability []byte
}

type Info struct {
    HostDriverVersion string
    HostDriverBranch  string
}

type Lib struct {
    pci NvidiaPCI
}

设计意图:

  • Interface 只暴露一个 Devices() 方法,简洁的接口隔离
  • Device 持有底层 PCIDevice 引用和已读取的 Vendor Specific Capability 数据
  • Info 结构体封装宿主机驱动版本和分支信息
  • Lib 依赖 NvidiaPCI 接口而非具体实现,便于测试时注入 Mock
4.3.3 NewVGPULib() / NewMockVGPU()
go 复制代码
func NewVGPULib(pci NvidiaPCI) Interface {
    return &Lib{pci: pci}
}

func NewMockVGPU() Interface {
    return NewVGPULib(NewMockNvidiaPCI())
}

生产代码注入 NvidiaPCILib(真实 sysfs 读取),测试代码注入 MockNvidiaPCI(预置 PCI 配置数据)。

4.3.4 Devices() --- vGPU 设备发现

#mermaid-svg-38EHWQfchAaF9uq8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-38EHWQfchAaF9uq8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-38EHWQfchAaF9uq8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-38EHWQfchAaF9uq8 .error-icon{fill:#552222;}#mermaid-svg-38EHWQfchAaF9uq8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-38EHWQfchAaF9uq8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-38EHWQfchAaF9uq8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-38EHWQfchAaF9uq8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-38EHWQfchAaF9uq8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-38EHWQfchAaF9uq8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-38EHWQfchAaF9uq8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-38EHWQfchAaF9uq8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-38EHWQfchAaF9uq8 .marker.cross{stroke:#333333;}#mermaid-svg-38EHWQfchAaF9uq8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-38EHWQfchAaF9uq8 p{margin:0;}#mermaid-svg-38EHWQfchAaF9uq8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-38EHWQfchAaF9uq8 .cluster-label text{fill:#333;}#mermaid-svg-38EHWQfchAaF9uq8 .cluster-label span{color:#333;}#mermaid-svg-38EHWQfchAaF9uq8 .cluster-label span p{background-color:transparent;}#mermaid-svg-38EHWQfchAaF9uq8 .label text,#mermaid-svg-38EHWQfchAaF9uq8 span{fill:#333;color:#333;}#mermaid-svg-38EHWQfchAaF9uq8 .node rect,#mermaid-svg-38EHWQfchAaF9uq8 .node circle,#mermaid-svg-38EHWQfchAaF9uq8 .node ellipse,#mermaid-svg-38EHWQfchAaF9uq8 .node polygon,#mermaid-svg-38EHWQfchAaF9uq8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-38EHWQfchAaF9uq8 .rough-node .label text,#mermaid-svg-38EHWQfchAaF9uq8 .node .label text,#mermaid-svg-38EHWQfchAaF9uq8 .image-shape .label,#mermaid-svg-38EHWQfchAaF9uq8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-38EHWQfchAaF9uq8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-38EHWQfchAaF9uq8 .rough-node .label,#mermaid-svg-38EHWQfchAaF9uq8 .node .label,#mermaid-svg-38EHWQfchAaF9uq8 .image-shape .label,#mermaid-svg-38EHWQfchAaF9uq8 .icon-shape .label{text-align:center;}#mermaid-svg-38EHWQfchAaF9uq8 .node.clickable{cursor:pointer;}#mermaid-svg-38EHWQfchAaF9uq8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-38EHWQfchAaF9uq8 .arrowheadPath{fill:#333333;}#mermaid-svg-38EHWQfchAaF9uq8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-38EHWQfchAaF9uq8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-38EHWQfchAaF9uq8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-38EHWQfchAaF9uq8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-38EHWQfchAaF9uq8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-38EHWQfchAaF9uq8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-38EHWQfchAaF9uq8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-38EHWQfchAaF9uq8 .cluster text{fill:#333;}#mermaid-svg-38EHWQfchAaF9uq8 .cluster span{color:#333;}#mermaid-svg-38EHWQfchAaF9uq8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-38EHWQfchAaF9uq8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-38EHWQfchAaF9uq8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-38EHWQfchAaF9uq8 .icon-shape,#mermaid-svg-38EHWQfchAaF9uq8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-38EHWQfchAaF9uq8 .icon-shape p,#mermaid-svg-38EHWQfchAaF9uq8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-38EHWQfchAaF9uq8 .icon-shape .label rect,#mermaid-svg-38EHWQfchAaF9uq8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-38EHWQfchAaF9uq8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-38EHWQfchAaF9uq8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-38EHWQfchAaF9uq8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Yes
No
false
true
Yes
No
Lib.Devices()
v.pci.Devices()

获取所有NVIDIA PCI设备
遍历每个PCI设备
device.GetVendorSpecificCapability()
capability == nil?
跳过此设备

(无Vendor Specific Capability)
IsVGPUDevice(capability)
创建 Device{pci, vGPUCapability}
追加到 vgpus 列表
还有更多设备?
返回 vgpus 列表

逐行解析:

go 复制代码
func (v *Lib) Devices() ([]*Device, error) {
    pciDevices, err := v.pci.Devices()
    if err != nil {
        return nil, fmt.Errorf("error getting NVIDIA specific PCI devices: %v", err)
    }

通过 NvidiaPCI 接口获取所有 NVIDIA PCI 设备。

go 复制代码
    var vgpus []*Device
    for _, device := range pciDevices {
        capability, err := device.GetVendorSpecificCapability()
        if err != nil {
            return nil, fmt.Errorf("unable to read vendor specific capability for %s: %v", device.Address, err)
        }
        if capability == nil {
            continue  // 非 vGPU 设备(如直通 GPU)
        }

对每个 PCI 设备读取 Vendor Specific Capability。若无此能力,说明不是 vGPU 设备,跳过。

go 复制代码
        if exists := v.IsVGPUDevice(capability); exists {
            vgpu := &Device{
                pci:            device,
                vGPUCapability: capability,
            }
            vgpus = append(vgpus, vgpu)
        }
    }
    return vgpus, nil
}

检查 capability 中的 vGPU 签名(0x56 0x46 = "VF"),若匹配则创建 Device 并加入列表。

4.3.5 IsVGPUDevice() --- vGPU 签名检测
go 复制代码
func (v *Lib) IsVGPUDevice(capability []byte) bool {
    if len(capability) < 5 {
        return false
    }
    if capability[3] != 0x56 { return false }  // 'V'
    if capability[4] != 0x46 { return false }  // 'F'
    return true
}

逻辑解析:

  • 首先检查 capability 长度至少 5 字节(前 3 字节为 Cap ID、Next Ptr、Length,后 2 字节为签名)
  • 检查偏移 3 和 4 处的字节是否为 0x56 ('V') 和 0x46 ('F')------这是 NVIDIA vGPU 设备的 "VF" 签名
  • 这是一个简单但精确的检测方法:物理 GPU 直通设备的 Vendor Specific Capability 不含此签名
4.3.6 GetInfo() --- 宿主机驱动信息解析

#mermaid-svg-j6XkgEMHkMjgq4Kb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-j6XkgEMHkMjgq4Kb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-j6XkgEMHkMjgq4Kb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-j6XkgEMHkMjgq4Kb .error-icon{fill:#552222;}#mermaid-svg-j6XkgEMHkMjgq4Kb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-j6XkgEMHkMjgq4Kb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-j6XkgEMHkMjgq4Kb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-j6XkgEMHkMjgq4Kb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-j6XkgEMHkMjgq4Kb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-j6XkgEMHkMjgq4Kb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-j6XkgEMHkMjgq4Kb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-j6XkgEMHkMjgq4Kb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-j6XkgEMHkMjgq4Kb .marker.cross{stroke:#333333;}#mermaid-svg-j6XkgEMHkMjgq4Kb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-j6XkgEMHkMjgq4Kb p{margin:0;}#mermaid-svg-j6XkgEMHkMjgq4Kb .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-j6XkgEMHkMjgq4Kb .cluster-label text{fill:#333;}#mermaid-svg-j6XkgEMHkMjgq4Kb .cluster-label span{color:#333;}#mermaid-svg-j6XkgEMHkMjgq4Kb .cluster-label span p{background-color:transparent;}#mermaid-svg-j6XkgEMHkMjgq4Kb .label text,#mermaid-svg-j6XkgEMHkMjgq4Kb span{fill:#333;color:#333;}#mermaid-svg-j6XkgEMHkMjgq4Kb .node rect,#mermaid-svg-j6XkgEMHkMjgq4Kb .node circle,#mermaid-svg-j6XkgEMHkMjgq4Kb .node ellipse,#mermaid-svg-j6XkgEMHkMjgq4Kb .node polygon,#mermaid-svg-j6XkgEMHkMjgq4Kb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-j6XkgEMHkMjgq4Kb .rough-node .label text,#mermaid-svg-j6XkgEMHkMjgq4Kb .node .label text,#mermaid-svg-j6XkgEMHkMjgq4Kb .image-shape .label,#mermaid-svg-j6XkgEMHkMjgq4Kb .icon-shape .label{text-anchor:middle;}#mermaid-svg-j6XkgEMHkMjgq4Kb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-j6XkgEMHkMjgq4Kb .rough-node .label,#mermaid-svg-j6XkgEMHkMjgq4Kb .node .label,#mermaid-svg-j6XkgEMHkMjgq4Kb .image-shape .label,#mermaid-svg-j6XkgEMHkMjgq4Kb .icon-shape .label{text-align:center;}#mermaid-svg-j6XkgEMHkMjgq4Kb .node.clickable{cursor:pointer;}#mermaid-svg-j6XkgEMHkMjgq4Kb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-j6XkgEMHkMjgq4Kb .arrowheadPath{fill:#333333;}#mermaid-svg-j6XkgEMHkMjgq4Kb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-j6XkgEMHkMjgq4Kb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-j6XkgEMHkMjgq4Kb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-j6XkgEMHkMjgq4Kb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-j6XkgEMHkMjgq4Kb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-j6XkgEMHkMjgq4Kb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-j6XkgEMHkMjgq4Kb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-j6XkgEMHkMjgq4Kb .cluster text{fill:#333;}#mermaid-svg-j6XkgEMHkMjgq4Kb .cluster span{color:#333;}#mermaid-svg-j6XkgEMHkMjgq4Kb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-j6XkgEMHkMjgq4Kb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-j6XkgEMHkMjgq4Kb rect.text{fill:none;stroke-width:0;}#mermaid-svg-j6XkgEMHkMjgq4Kb .icon-shape,#mermaid-svg-j6XkgEMHkMjgq4Kb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-j6XkgEMHkMjgq4Kb .icon-shape p,#mermaid-svg-j6XkgEMHkMjgq4Kb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-j6XkgEMHkMjgq4Kb .icon-shape .label rect,#mermaid-svg-j6XkgEMHkMjgq4Kb .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-j6XkgEMHkMjgq4Kb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-j6XkgEMHkMjgq4Kb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-j6XkgEMHkMjgq4Kb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Yes
No
No
Yes
No
Yes
No
Yes
Device.GetInfo()
vGPUCapability 为空?
返回 error
从偏移5开始遍历

capability 记录链
record == 0?

(驱动版本记录ID)
跳过当前记录

pos += recordLength
pos < len(capability)?
未找到记录

返回 error
有足够空间

读取版本+分支?
读取10字节 HostDriverVersion
读取10字节 HostDriverBranch
Trim null字节
返回 Info{version, branch}

逐行深度解析:

go 复制代码
func (d *Device) GetInfo() (*Info, error) {
    if len(d.vGPUCapability) == 0 {
        return nil, fmt.Errorf("vendor capability record is not populated for device %s", d.pci.Address)
    }

防御性检查:确保 capability 数据已填充。

go 复制代码
    var hostDriverVersion string
    var hostDriverBranch string
    foundDriverVersionRecord := false
    pos := VGPUCapabilityRecordStart  // 5
    record := GetByte(d.vGPUCapability, VGPUCapabilityRecordStart)

从偏移 5 开始扫描记录链。record 变量保存当前记录的 ID。

go 复制代码
    for record != 0 && int(pos) < len(d.vGPUCapability) {
        recordLength := GetByte(d.vGPUCapability, pos+1)
        pos += recordLength
        record = GetByte(d.vGPUCapability, pos)
    }

记录链遍历: vGPU Vendor Specific Capability 包含多个记录,每个记录格式为:

复制代码
[record_id: 1byte] [record_length: 1byte] [record_data: N bytes]

循环跳过非 0 的记录,直到找到 record_id == 0(驱动版本记录)或到达数据末尾。

go 复制代码
    if record == 0 && int(pos+2+HostDriverVersionLength+HostDriverBranchLength) <= len(d.vGPUCapability) {
        foundDriverVersionRecord = true
        i := pos + 2  // 跳过 record_id (1byte) + record_length (1byte)

找到 record_id == 0 后,验证有足够的空间读取 10+10=20 字节数据。i 指向记录数据起始。

go 复制代码
        for ; i < pos+2+HostDriverVersionLength; i++ {
            hostDriverVersion += string(GetByte(d.vGPUCapability, i))
        }
        hostDriverVersion = strings.Trim(hostDriverVersion, "\x00")

逐字节读取 10 字节的驱动版本字符串,去除 null 填充符。例如 "460.16\x00\x00\x00\x00""460.16"

go 复制代码
        for ; i < pos+2+HostDriverVersionLength+HostDriverBranchLength; i++ {
            hostDriverBranch += string(GetByte(d.vGPUCapability, i))
        }
        hostDriverBranch = strings.Trim(hostDriverBranch, "\x00")
    }

继续读取 10 字节的驱动分支字符串。例如 "r460_00\x00\x00""r460_00"

go 复制代码
    if !foundDriverVersionRecord {
        return nil, fmt.Errorf("cannot find driver version record in vendor specific capability for device %s", d.pci.Address)
    }

    return &Info{
        HostDriverVersion: hostDriverVersion,
        HostDriverBranch:  hostDriverBranch,
    }, nil
}

若未找到驱动版本记录则报错。否则返回解析结果。

4.4 PCI 工具层 --- pciutil.go

4.4.1 常量定义
go 复制代码
const (
    PciDevicesRoot                = "/sys/bus/pci/devices"
    PciStatusByte                 = 0x06
    PciStatusCapabilityList       = 0x10
    PciCapabilityList             = 0x34
    PciCapabilityListID           = 0
    PciCapabilityListNext         = 1
    PciCapabilityLength           = 2
    PciCapabilityVendorSpecificID = 0x09
    PciNvidiaVendorID             = "0x10de"
)
常量 含义
PciDevicesRoot sysfs PCI 设备根目录
PciStatusByte PCI 配置空间状态寄存器偏移 (0x06)
PciStatusCapabilityList 状态寄存器中"支持能力列表"位 (bit 4)
PciCapabilityList 能力链表第一个入口的偏移 (0x34)
PciCapabilityListID/Next/Length 能力入口内各字段的偏移
PciCapabilityVendorSpecificID Vendor Specific Capability 的 ID (0x09)
PciNvidiaVendorID NVIDIA 的 PCI Vendor ID
4.4.2 NvidiaPCILib.Devices() --- PCI 设备扫描

#mermaid-svg-fZOBpTpqknMU0ONF{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-fZOBpTpqknMU0ONF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fZOBpTpqknMU0ONF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fZOBpTpqknMU0ONF .error-icon{fill:#552222;}#mermaid-svg-fZOBpTpqknMU0ONF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fZOBpTpqknMU0ONF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fZOBpTpqknMU0ONF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fZOBpTpqknMU0ONF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fZOBpTpqknMU0ONF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fZOBpTpqknMU0ONF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fZOBpTpqknMU0ONF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fZOBpTpqknMU0ONF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fZOBpTpqknMU0ONF .marker.cross{stroke:#333333;}#mermaid-svg-fZOBpTpqknMU0ONF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fZOBpTpqknMU0ONF p{margin:0;}#mermaid-svg-fZOBpTpqknMU0ONF .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-fZOBpTpqknMU0ONF .cluster-label text{fill:#333;}#mermaid-svg-fZOBpTpqknMU0ONF .cluster-label span{color:#333;}#mermaid-svg-fZOBpTpqknMU0ONF .cluster-label span p{background-color:transparent;}#mermaid-svg-fZOBpTpqknMU0ONF .label text,#mermaid-svg-fZOBpTpqknMU0ONF span{fill:#333;color:#333;}#mermaid-svg-fZOBpTpqknMU0ONF .node rect,#mermaid-svg-fZOBpTpqknMU0ONF .node circle,#mermaid-svg-fZOBpTpqknMU0ONF .node ellipse,#mermaid-svg-fZOBpTpqknMU0ONF .node polygon,#mermaid-svg-fZOBpTpqknMU0ONF .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-fZOBpTpqknMU0ONF .rough-node .label text,#mermaid-svg-fZOBpTpqknMU0ONF .node .label text,#mermaid-svg-fZOBpTpqknMU0ONF .image-shape .label,#mermaid-svg-fZOBpTpqknMU0ONF .icon-shape .label{text-anchor:middle;}#mermaid-svg-fZOBpTpqknMU0ONF .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-fZOBpTpqknMU0ONF .rough-node .label,#mermaid-svg-fZOBpTpqknMU0ONF .node .label,#mermaid-svg-fZOBpTpqknMU0ONF .image-shape .label,#mermaid-svg-fZOBpTpqknMU0ONF .icon-shape .label{text-align:center;}#mermaid-svg-fZOBpTpqknMU0ONF .node.clickable{cursor:pointer;}#mermaid-svg-fZOBpTpqknMU0ONF .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-fZOBpTpqknMU0ONF .arrowheadPath{fill:#333333;}#mermaid-svg-fZOBpTpqknMU0ONF .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-fZOBpTpqknMU0ONF .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-fZOBpTpqknMU0ONF .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fZOBpTpqknMU0ONF .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-fZOBpTpqknMU0ONF .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fZOBpTpqknMU0ONF .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-fZOBpTpqknMU0ONF .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-fZOBpTpqknMU0ONF .cluster text{fill:#333;}#mermaid-svg-fZOBpTpqknMU0ONF .cluster span{color:#333;}#mermaid-svg-fZOBpTpqknMU0ONF div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-fZOBpTpqknMU0ONF .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-fZOBpTpqknMU0ONF rect.text{fill:none;stroke-width:0;}#mermaid-svg-fZOBpTpqknMU0ONF .icon-shape,#mermaid-svg-fZOBpTpqknMU0ONF .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fZOBpTpqknMU0ONF .icon-shape p,#mermaid-svg-fZOBpTpqknMU0ONF .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-fZOBpTpqknMU0ONF .icon-shape .label rect,#mermaid-svg-fZOBpTpqknMU0ONF .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fZOBpTpqknMU0ONF .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-fZOBpTpqknMU0ONF .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-fZOBpTpqknMU0ONF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} No
Yes
Yes
No
NvidiaPCILib.Devices()
os.ReadDir(/sys/bus/pci/devices)
遍历每个设备目录
读取 vendor 文件
vendor == 0x10de?

(NVIDIA)
跳过
读取 class 文件
读取 config 文件

(256字节配置空间)
构造 PCIDevice{Path, Address, Vendor, Class, Config}
追加到 devices 列表
还有更多?
返回 devices

逐行解析:

go 复制代码
func (p *NvidiaPCILib) Devices() ([]*PCIDevice, error) {
    deviceDirs, err := os.ReadDir(PciDevicesRoot)
    if err != nil {
        return nil, fmt.Errorf("unable to read PCI bus devices: %v", err)
    }

读取 /sys/bus/pci/devices 目录,获取所有 PCI 设备的目录条目。

go 复制代码
    for _, deviceDir := range deviceDirs {
        devicePath := path.Join(PciDevicesRoot, deviceDir.Name())
        address := deviceDir.Name()

        vendor, err := os.ReadFile(path.Join(devicePath, "vendor"))
        if err != nil { return nil, ... }

        if strings.TrimSpace(string(vendor)) != PciNvidiaVendorID {
            continue
        }

目录名就是 PCI 地址(如 0000:01:00.0)。读取 vendor 文件,只处理 NVIDIA 设备(0x10de)。

go 复制代码
        class, err := os.ReadFile(path.Join(devicePath, "class"))
        if err != nil { return nil, ... }

        config, err := os.ReadFile(path.Join(devicePath, "config"))
        if err != nil { return nil, ... }

        device := &PCIDevice{
            Path:    devicePath,
            Address: address,
            Vendor:  strings.TrimSpace(string(vendor)),
            Class:   string(class)[0:4],
            Config:  config,
        }
        devices = append(devices, device)
    }

读取 class 文件(设备类代码,如 0x030000 表示显示控制器)和 config 文件(PCI 配置空间的原始 256 字节数据)。Class 取前 4 个字符(如 0x03)。

4.4.3 GetVendorSpecificCapability() --- 能力链表遍历

#mermaid-svg-sUaFgdCOlETn1BxF{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-sUaFgdCOlETn1BxF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-sUaFgdCOlETn1BxF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-sUaFgdCOlETn1BxF .error-icon{fill:#552222;}#mermaid-svg-sUaFgdCOlETn1BxF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-sUaFgdCOlETn1BxF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-sUaFgdCOlETn1BxF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-sUaFgdCOlETn1BxF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-sUaFgdCOlETn1BxF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-sUaFgdCOlETn1BxF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-sUaFgdCOlETn1BxF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-sUaFgdCOlETn1BxF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-sUaFgdCOlETn1BxF .marker.cross{stroke:#333333;}#mermaid-svg-sUaFgdCOlETn1BxF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-sUaFgdCOlETn1BxF p{margin:0;}#mermaid-svg-sUaFgdCOlETn1BxF .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-sUaFgdCOlETn1BxF .cluster-label text{fill:#333;}#mermaid-svg-sUaFgdCOlETn1BxF .cluster-label span{color:#333;}#mermaid-svg-sUaFgdCOlETn1BxF .cluster-label span p{background-color:transparent;}#mermaid-svg-sUaFgdCOlETn1BxF .label text,#mermaid-svg-sUaFgdCOlETn1BxF span{fill:#333;color:#333;}#mermaid-svg-sUaFgdCOlETn1BxF .node rect,#mermaid-svg-sUaFgdCOlETn1BxF .node circle,#mermaid-svg-sUaFgdCOlETn1BxF .node ellipse,#mermaid-svg-sUaFgdCOlETn1BxF .node polygon,#mermaid-svg-sUaFgdCOlETn1BxF .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-sUaFgdCOlETn1BxF .rough-node .label text,#mermaid-svg-sUaFgdCOlETn1BxF .node .label text,#mermaid-svg-sUaFgdCOlETn1BxF .image-shape .label,#mermaid-svg-sUaFgdCOlETn1BxF .icon-shape .label{text-anchor:middle;}#mermaid-svg-sUaFgdCOlETn1BxF .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-sUaFgdCOlETn1BxF .rough-node .label,#mermaid-svg-sUaFgdCOlETn1BxF .node .label,#mermaid-svg-sUaFgdCOlETn1BxF .image-shape .label,#mermaid-svg-sUaFgdCOlETn1BxF .icon-shape .label{text-align:center;}#mermaid-svg-sUaFgdCOlETn1BxF .node.clickable{cursor:pointer;}#mermaid-svg-sUaFgdCOlETn1BxF .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-sUaFgdCOlETn1BxF .arrowheadPath{fill:#333333;}#mermaid-svg-sUaFgdCOlETn1BxF .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-sUaFgdCOlETn1BxF .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-sUaFgdCOlETn1BxF .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-sUaFgdCOlETn1BxF .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-sUaFgdCOlETn1BxF .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-sUaFgdCOlETn1BxF .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-sUaFgdCOlETn1BxF .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-sUaFgdCOlETn1BxF .cluster text{fill:#333;}#mermaid-svg-sUaFgdCOlETn1BxF .cluster span{color:#333;}#mermaid-svg-sUaFgdCOlETn1BxF div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-sUaFgdCOlETn1BxF .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-sUaFgdCOlETn1BxF rect.text{fill:none;stroke-width:0;}#mermaid-svg-sUaFgdCOlETn1BxF .icon-shape,#mermaid-svg-sUaFgdCOlETn1BxF .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-sUaFgdCOlETn1BxF .icon-shape p,#mermaid-svg-sUaFgdCOlETn1BxF .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-sUaFgdCOlETn1BxF .icon-shape .label rect,#mermaid-svg-sUaFgdCOlETn1BxF .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-sUaFgdCOlETn1BxF .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-sUaFgdCOlETn1BxF .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-sUaFgdCOlETn1BxF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Yes
No
No
Yes
Yes
No
Yes
No
Yes
No
Yes
No
GetVendorSpecificCapability()
Config 长度 < 256?
返回 error

需要特权模式读取完整配置空间
Status寄存器

支持能力列表?
返回 nil

(无能力列表)
pos = Config0x34

(第一个能力入口)
pos == 0?
链表结束

返回 nil
读取 Cap ID = Configpos
visitedpos != 0?

(已访问过)
链表成环

break
ID == 0xff?
ID == 0x09?

(Vendor Specific)
返回 Configpos:pos+length

(Vendor Specific Capability 数据)
pos = next

继续遍历
visitedpos++

逐行深度解析:

go 复制代码
func (d *PCIDevice) GetVendorSpecificCapability() ([]byte, error) {
    if len(d.Config) < 256 {
        return nil, fmt.Errorf("entire PCI configuration is not read for device %s. Please run GFD with privileged mode to read complete PCI configuration data", d.Address)
    }

PCI 标准配置空间为 256 字节。若读取不足,说明权限不够,需要特权模式。

go 复制代码
    if d.Config[PciStatusByte]&PciStatusCapabilityList == 0 {
        return nil, nil
    }

检查状态寄存器(偏移 0x06)的 bit 4(值 0x10)。若该位为 0,设备不支持能力链表,直接返回。

go 复制代码
    var visited [256]byte
    pos := GetByte(d.Config, PciCapabilityList)  // 0x34

visited 数组防止链表成环导致死循环。pos 从配置空间偏移 0x34 读取第一个能力入口的位置。

go 复制代码
    for pos != 0 {
        id := GetByte(d.Config, pos+PciCapabilityListID)       // pos+0: Capability ID
        next := GetByte(d.Config, pos+PciCapabilityListNext)   // pos+1: Next Capability Pointer
        length := GetByte(d.Config, pos+PciCapabilityLength)   // pos+2: Capability Length

        if visited[pos] != 0 { break }  // 环检测
        if id == 0xff { break }          // 链表断裂

遍历能力链表。每个入口 3 字节头部:ID、Next指针、Length。环检测和断裂检测是防御性编程。

go 复制代码
        if id == PciCapabilityVendorSpecificID {
            capability := d.Config[pos+PciCapabilityListID : pos+PciCapabilityListID+length]
            return capability, nil
        }

找到 Vendor Specific Capability(ID=0x09),返回从 pos 开始、长度为 length 的数据切片。这个切片包含了 vGPU 模块用于检测和解析的全部数据。

go 复制代码
        visited[pos]++
        pos = next
    }
    return nil, nil
}

标记当前位置已访问,跳到下一个能力入口。

4.4.4 辅助函数
go 复制代码
func GetByte(buffer []byte, pos uint8) uint8 {
    return buffer[pos]
}

func GetWord(buffer []byte, pos int) uint16 {
    return uint16(buffer[pos]) | (uint16(buffer[pos+1]) << 8)
}

func GetLong(buffer []byte, pos int) uint32 {
    return uint32(buffer[pos]) |
        uint32(buffer[pos+1])<<8 |
        uint32(buffer[pos+2])<<16 |
        uint32(buffer[pos+3])<<24
}

PCI 配置空间是小端序(Little-Endian)。GetWordGetLong 分别读取 2 字节和 4 字节的小端序值。GetByte 简单读取单字节。

4.4.5 Mock 实现
go 复制代码
type MockNvidiaPCI struct {
    devices []*PCIDevice
}

func NewMockNvidiaPCI() NvidiaPCI {
    gpuPassThroughConfig := []byte{...}  // 256 bytes, GPU 直通配置
    vgpuConfig := []byte{...}            // 256 bytes, vGPU 配置

    return &MockNvidiaPCI{
        devices: []*PCIDevice{
            {Address: "passthrough", Vendor: "0x10de", Class: "300", Config: gpuPassThroughConfig},
            {Address: "vgpu", Vendor: "0x10de", Class: "300", Config: vgpuConfig},
        },
    }
}

Mock 包含两个设备:

  1. passthrough:物理 GPU 直通,Vendor Specific Capability 中无 "VF" 签名
  2. vgpu:vGPU 设备,配置空间偏移 0x68 处有 Vendor Specific Capability(ID=0x09),包含 "VF" 签名和驱动版本记录

Mock 数据验证(vgpuConfig 关键偏移):

  • 偏移 0x06(Status): 0x06 → 二进制 00000110,bit 4 为 0... 但实际值为 0xff 在偏移 7... 让我重新检查:0x06 是 PciStatusByte 的偏移值,实际 d.Config[0x06] = 0x06... 查看 mock 数据:偏移 6 为 0x06,偏移 7 为 0x050x06 & 0x10 == 0? 不对。

实际上 mock 的 vgpuConfig 偏移 6 和 7 是 0x06, 0x05,组合为 status 寄存器值 0x0506。但 PCI 是小端序,d.Config[0x06] = 0x060x06 & 0x10 = 0------这意味着不支持能力列表?

再看完整的 vgpuConfig 数据,偏移 6 的值实际上需要仔细数。实际上 mock 数据是按字节列出的,偏移从 0 开始计数。让我验证关键偏移:

复制代码
偏移 0: 0xde (vendor low)
偏移 1: 0x10 (vendor high) → vendor = 0x10de ✓
偏移 2: 0xb8 (device low)
偏移 3: 0x1e (device high)
偏移 4: 0x02 (command low)
偏移 5: 0x05 (command high)
偏移 6: 0xff (status low)
偏移 7: 0x06 (status high)

d.Config[0x06] = 0xff0xff & 0x10 = 0x10 ≠ 0 → 支持能力列表 ✓

复制代码
偏移 0x34: 0x68 → 第一个能力入口在偏移 0x68
偏移 0x68: 0x09 → Vendor Specific Capability ID ✓
偏移 0x69: 0x1b → Next pointer = 0x1b
偏移 0x6a: 0x56 → 'V'
偏移 0x6b: 0x46 → 'F' → vGPU 签名 ✓
偏移 0x6c: 0x00 → record_id = 0 (驱动版本记录)
偏移 0x6d: 0x16 → record_length = 22
偏移 0x6e: 0x34 → '4'
偏移 0x6f: 0x36 → '6'
偏移 0x70: 0x30 → '0'
偏移 0x71: 0x2e → '.'
偏移 0x72: 0x31 → '1'
偏移 0x73: 0x36 → '6'
偏移 0x74-0x77: 0x00... → null padding
→ HostDriverVersion = "460.16"
偏移 0x78: 0x72 → 'r'
偏移 0x79: 0x34 → '4'
偏移 0x7a: 0x36 → '6'
偏移 0x7b: 0x30 → '0'
偏移 0x7c: 0x5f → '_'
偏移 0x7d: 0x30 → '0'
偏移 0x7e: 0x30 → '0'
偏移 0x7f-0x81: 0x00... → null padding
→ HostDriverBranch = "r460_00"

4.5 vGPU 设备发现与 PCI 流程图

#mermaid-svg-XfduBD4sb1gPqRmi{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XfduBD4sb1gPqRmi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XfduBD4sb1gPqRmi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XfduBD4sb1gPqRmi .error-icon{fill:#552222;}#mermaid-svg-XfduBD4sb1gPqRmi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XfduBD4sb1gPqRmi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XfduBD4sb1gPqRmi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XfduBD4sb1gPqRmi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XfduBD4sb1gPqRmi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XfduBD4sb1gPqRmi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XfduBD4sb1gPqRmi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XfduBD4sb1gPqRmi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XfduBD4sb1gPqRmi .marker.cross{stroke:#333333;}#mermaid-svg-XfduBD4sb1gPqRmi svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XfduBD4sb1gPqRmi p{margin:0;}#mermaid-svg-XfduBD4sb1gPqRmi .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XfduBD4sb1gPqRmi .cluster-label text{fill:#333;}#mermaid-svg-XfduBD4sb1gPqRmi .cluster-label span{color:#333;}#mermaid-svg-XfduBD4sb1gPqRmi .cluster-label span p{background-color:transparent;}#mermaid-svg-XfduBD4sb1gPqRmi .label text,#mermaid-svg-XfduBD4sb1gPqRmi span{fill:#333;color:#333;}#mermaid-svg-XfduBD4sb1gPqRmi .node rect,#mermaid-svg-XfduBD4sb1gPqRmi .node circle,#mermaid-svg-XfduBD4sb1gPqRmi .node ellipse,#mermaid-svg-XfduBD4sb1gPqRmi .node polygon,#mermaid-svg-XfduBD4sb1gPqRmi .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XfduBD4sb1gPqRmi .rough-node .label text,#mermaid-svg-XfduBD4sb1gPqRmi .node .label text,#mermaid-svg-XfduBD4sb1gPqRmi .image-shape .label,#mermaid-svg-XfduBD4sb1gPqRmi .icon-shape .label{text-anchor:middle;}#mermaid-svg-XfduBD4sb1gPqRmi .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XfduBD4sb1gPqRmi .rough-node .label,#mermaid-svg-XfduBD4sb1gPqRmi .node .label,#mermaid-svg-XfduBD4sb1gPqRmi .image-shape .label,#mermaid-svg-XfduBD4sb1gPqRmi .icon-shape .label{text-align:center;}#mermaid-svg-XfduBD4sb1gPqRmi .node.clickable{cursor:pointer;}#mermaid-svg-XfduBD4sb1gPqRmi .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XfduBD4sb1gPqRmi .arrowheadPath{fill:#333333;}#mermaid-svg-XfduBD4sb1gPqRmi .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XfduBD4sb1gPqRmi .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XfduBD4sb1gPqRmi .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XfduBD4sb1gPqRmi .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XfduBD4sb1gPqRmi .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XfduBD4sb1gPqRmi .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XfduBD4sb1gPqRmi .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XfduBD4sb1gPqRmi .cluster text{fill:#333;}#mermaid-svg-XfduBD4sb1gPqRmi .cluster span{color:#333;}#mermaid-svg-XfduBD4sb1gPqRmi div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XfduBD4sb1gPqRmi .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XfduBD4sb1gPqRmi rect.text{fill:none;stroke-width:0;}#mermaid-svg-XfduBD4sb1gPqRmi .icon-shape,#mermaid-svg-XfduBD4sb1gPqRmi .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XfduBD4sb1gPqRmi .icon-shape p,#mermaid-svg-XfduBD4sb1gPqRmi .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XfduBD4sb1gPqRmi .icon-shape .label rect,#mermaid-svg-XfduBD4sb1gPqRmi .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XfduBD4sb1gPqRmi .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XfduBD4sb1gPqRmi .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XfduBD4sb1gPqRmi :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} vGPU识别与信息解析
能力链表遍历
PCI设备扫描
No
Yes
No
Yes
Yes
No
No
Yes
No
Yes
os.ReadDir(/sys/bus/pci/devices)
遍历设备目录
读取 vendor 文件
vendor == 0x10de?
跳过
读取 class + config 文件
构造 PCIDevice
追加到列表
GetVendorSpecificCapability()
检查 Config0x06 & 0x10
支持能力列表?
返回 nil
pos = Config0x34
读取 Configpos = Cap ID
ID == 0x09?
返回 Vendor Specific 数据
pos = Next pointer
pos == 0 或成环?
IsVGPUDevice()
capability3 == 0x56 &&

capability4 == 0x46?
非 vGPU 设备
创建 vGPU Device
GetInfo()
遍历记录链找 record_id == 0
读取 10 字节版本 + 10 字节分支
返回 Info{version, branch}


5. 三模块协作关系

#mermaid-svg-IpGNonJSOvuhsDiY{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-IpGNonJSOvuhsDiY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-IpGNonJSOvuhsDiY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-IpGNonJSOvuhsDiY .error-icon{fill:#552222;}#mermaid-svg-IpGNonJSOvuhsDiY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-IpGNonJSOvuhsDiY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-IpGNonJSOvuhsDiY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-IpGNonJSOvuhsDiY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-IpGNonJSOvuhsDiY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-IpGNonJSOvuhsDiY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-IpGNonJSOvuhsDiY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-IpGNonJSOvuhsDiY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-IpGNonJSOvuhsDiY .marker.cross{stroke:#333333;}#mermaid-svg-IpGNonJSOvuhsDiY svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-IpGNonJSOvuhsDiY p{margin:0;}#mermaid-svg-IpGNonJSOvuhsDiY .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-IpGNonJSOvuhsDiY .cluster-label text{fill:#333;}#mermaid-svg-IpGNonJSOvuhsDiY .cluster-label span{color:#333;}#mermaid-svg-IpGNonJSOvuhsDiY .cluster-label span p{background-color:transparent;}#mermaid-svg-IpGNonJSOvuhsDiY .label text,#mermaid-svg-IpGNonJSOvuhsDiY span{fill:#333;color:#333;}#mermaid-svg-IpGNonJSOvuhsDiY .node rect,#mermaid-svg-IpGNonJSOvuhsDiY .node circle,#mermaid-svg-IpGNonJSOvuhsDiY .node ellipse,#mermaid-svg-IpGNonJSOvuhsDiY .node polygon,#mermaid-svg-IpGNonJSOvuhsDiY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-IpGNonJSOvuhsDiY .rough-node .label text,#mermaid-svg-IpGNonJSOvuhsDiY .node .label text,#mermaid-svg-IpGNonJSOvuhsDiY .image-shape .label,#mermaid-svg-IpGNonJSOvuhsDiY .icon-shape .label{text-anchor:middle;}#mermaid-svg-IpGNonJSOvuhsDiY .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-IpGNonJSOvuhsDiY .rough-node .label,#mermaid-svg-IpGNonJSOvuhsDiY .node .label,#mermaid-svg-IpGNonJSOvuhsDiY .image-shape .label,#mermaid-svg-IpGNonJSOvuhsDiY .icon-shape .label{text-align:center;}#mermaid-svg-IpGNonJSOvuhsDiY .node.clickable{cursor:pointer;}#mermaid-svg-IpGNonJSOvuhsDiY .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-IpGNonJSOvuhsDiY .arrowheadPath{fill:#333333;}#mermaid-svg-IpGNonJSOvuhsDiY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-IpGNonJSOvuhsDiY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-IpGNonJSOvuhsDiY .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IpGNonJSOvuhsDiY .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-IpGNonJSOvuhsDiY .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IpGNonJSOvuhsDiY .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-IpGNonJSOvuhsDiY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-IpGNonJSOvuhsDiY .cluster text{fill:#333;}#mermaid-svg-IpGNonJSOvuhsDiY .cluster span{color:#333;}#mermaid-svg-IpGNonJSOvuhsDiY div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-IpGNonJSOvuhsDiY .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-IpGNonJSOvuhsDiY rect.text{fill:none;stroke-width:0;}#mermaid-svg-IpGNonJSOvuhsDiY .icon-shape,#mermaid-svg-IpGNonJSOvuhsDiY .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IpGNonJSOvuhsDiY .icon-shape p,#mermaid-svg-IpGNonJSOvuhsDiY .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-IpGNonJSOvuhsDiY .icon-shape .label rect,#mermaid-svg-IpGNonJSOvuhsDiY .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IpGNonJSOvuhsDiY .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-IpGNonJSOvuhsDiY .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-IpGNonJSOvuhsDiY :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Device Plugin 主流程
vGPU模块
CDI模块
MIG模块
设备发现层
NVML Library
resource.Manager
sysfs /sys/bus/pci
vgpu.Lib
procfs /proc/driver/nvidia-caps
mig.GetMigCapabilityDevicePaths
mig.DeviceInfo
devicesMapbool\[\]Device
GetAllMigDevices()
cdi.cdiHandler
imex.Channels
CDI Spec JSON

/var/run/cdi/*.json
QualifiedName()
AdditionalDevices()
vGPU Device 列表
HostDriverVersion

HostDriverBranch
device plugin Server
Allocate Response
Container Runtime

(containerd/CRI-O)

协作说明:

  1. MIG → CDI:MIG 模块识别 MIG 设备后,CDI 模块为这些设备生成 CDI Spec,使容器运行时知道如何注入 MIG 设备节点
  2. MIG mig-dp → AllocateGetMigCapabilityDevicePaths() 提供的能力路径映射用于 Allocate 时确定需要注入哪些 /dev/nvidia-caps/nvidia-capN 设备
  3. vGPU → GFD/Labels:vGPU 模块发现的宿主机驱动信息用于节点标签标注,帮助调度器将 vGPU 工作负载调度到正确的节点
  4. CDI 独立性 :CDI 模块通过 resource.Managernvcdi 库直接获取设备信息,不直接依赖 MIG/vGPU 模块,但共享底层的 NVML 接口

6. 关键设计总结

6.1 设计模式运用

模式 应用位置 目的
Interface + Null Object CDI Interface + null{} CDI 未启用时的空操作处理
Functional Options CDI Option 函数族 灵活构造,避免大量参数
Factory Method NewVGPULib()newImexChannelSpecGenerator() 封装对象创建逻辑
Strategy deviceIDStrategydeviceDiscoveryStrategy 运行时策略切换
Dependency Injection resource.Manager 注入 MIG、NVML 注入 CDI 解耦,便于测试
Lazy Init + Cache MIG devicesMap 避免重复设备扫描
Mock MockNvidiaPCIapi_mock.go 单元测试隔离

6.2 关键架构决策

  1. CDI Spec 生成委托给 nvcdi 库 :k8s-device-plugin 不直接操作 CDI Spec JSON,而是通过 nvidia-container-toolkit/pkg/nvcdi 库生成,保持与 NVIDIA 容器工具链的一致性

  2. Root Transformer 分离 :driver root 和 dev root 可独立配置并分别转换,适应复杂的容器化部署场景(如驱动安装在 host 的 /driver-root,但设备节点在 /dev

  3. MIG 设备路径通过 procfs 映射 :而非直接使用 NVML API 获取设备路径,因为 MIG 设备节点的 minor 号由内核动态分配,只有 /proc/driver/nvidia-caps/mig-minors 文件提供权威映射

  4. vGPU 检测基于 PCI 配置空间:而非依赖 NVIDIA 驱动或 NVML,因为 vGPU 设备的 PCI 配置空间由 hypervisor 直接填充,即使驱动未正确加载也能检测

  5. IMEX 通道自定义 SpecGenerator :不通过 nvcdi 库而是自定义实现 imexChannelCDILib,因为 IMEX 通道是简单的设备节点注入,不需要 nvcdi 的复杂设备发现逻辑

6.3 错误处理策略

场景 策略
CDI 未启用 返回 null handler,静默降级
无 NVML 且非 Tegra 返回 null handler,记录警告
空 CDI Spec(无设备) 跳过该 class,从 cdilibs 中删除
mig-minors 文件不存在 返回 nil(正常,非 MIG 机器)
PCI 配置空间 < 256 字节 返回 error,提示需要特权模式
能力链表成环 检测并 break,避免死循环
IMEX 通道不存在 若 Required 则报错,否则跳过

本文档基于 NVIDIA k8s-device-plugin v0.19.3 源码逐行分析生成。所有代码引用均来自实际源文件,图表基于代码结构绘制。

相关推荐
StarkCoder1 小时前
AI 会做多、看少、不收尾:七种失效和拦住它们的办法
人工智能·架构
beibeix20152 小时前
3D Slicer 架构分析
架构·slicer
snow@li2 小时前
命名规范:企业级微服务模块前缀命名规范全景梳理
微服务·云原生·架构
逻极3 小时前
C# 从入门到精通:现代云原生与AI应用开发实战
ai·云原生·c#·.net
roman_日积跬步-终至千里3 小时前
【从零开始学架构】系统设计的价值:从业务目标到可演进系统
架构
煎饼学大模型12 小时前
Agent 的“大脑-手“解耦架构:当推理层和工具执行层各自独立演进
数据库·人工智能·oracle·架构·agent
Cloud云卷云舒13 小时前
智能体长期记忆技术选型分析报告
云原生·ai-native·haishandb
IT二叔14 小时前
Kubernetes(K8s)-01-简介
云原生·容器·kubernetes
暖和_白开水15 小时前
数据分析agent(十三_3):docker mysql容器镜像服务的异常错误
mysql·docker·容器