NVIDIA k8s-device-plugin v0.19.3 --- CDI / MIG / vGPU 模块超深度代码分析
分析对象:
internal/cdi、internal/mig、internal/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 模块负责:
- 生成 CDI Spec 文件 :将 NVIDIA GPU 设备信息转换为 CDI 规范的 JSON 文件,写入
/var/run/cdi/ - 提供 CDI Qualified Name :为 device plugin 的 Allocate 响应提供
vendor/class/deviceID格式的设备引用名 - 管理附加设备:为 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 |
设备命名策略(uuid 或 index) |
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 |
设备发现策略(nvml 或 tegra) |
设计意图: 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(gpu、imex-channel、gds、mofed、gdrcopy)存储不同的 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")
}
必须有明确的设备发现策略(nvml 或 tegra),否则报错。
段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
}
为 gdrcopy、gds、mofed 各创建一个 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 生成器(gpu、imex-channel、gds 等)。
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 模块负责:
- 设备分类管理:将节点上的 GPU 按 MIG 启用/禁用状态分类
- MIG 设备枚举:获取所有已配置的 MIG 实例设备
- 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 模块用于在虚拟化环境中:
- 发现 vGPU PCI 设备:通过 sysfs 扫描 NVIDIA PCI 设备,识别哪些是 vGPU 设备
- 解析宿主机驱动信息:从 vGPU PCI 配置空间的 Vendor Specific Capability 中提取宿主机驱动版本和分支
- 提供设备信息:为 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)。GetWord 和 GetLong 分别读取 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 包含两个设备:
- passthrough:物理 GPU 直通,Vendor Specific Capability 中无 "VF" 签名
- 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 为0x05。0x06 & 0x10 == 0? 不对。
实际上 mock 的 vgpuConfig 偏移 6 和 7 是 0x06, 0x05,组合为 status 寄存器值 0x0506。但 PCI 是小端序,d.Config[0x06] = 0x06,0x06 & 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] = 0xff,0xff & 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)
协作说明:
- MIG → CDI:MIG 模块识别 MIG 设备后,CDI 模块为这些设备生成 CDI Spec,使容器运行时知道如何注入 MIG 设备节点
- MIG mig-dp → Allocate :
GetMigCapabilityDevicePaths()提供的能力路径映射用于 Allocate 时确定需要注入哪些/dev/nvidia-caps/nvidia-capN设备 - vGPU → GFD/Labels:vGPU 模块发现的宿主机驱动信息用于节点标签标注,帮助调度器将 vGPU 工作负载调度到正确的节点
- CDI 独立性 :CDI 模块通过
resource.Manager和nvcdi库直接获取设备信息,不直接依赖 MIG/vGPU 模块,但共享底层的 NVML 接口
6. 关键设计总结
6.1 设计模式运用
| 模式 | 应用位置 | 目的 |
|---|---|---|
| Interface + Null Object | CDI Interface + null{} |
CDI 未启用时的空操作处理 |
| Functional Options | CDI Option 函数族 |
灵活构造,避免大量参数 |
| Factory Method | NewVGPULib()、newImexChannelSpecGenerator() |
封装对象创建逻辑 |
| Strategy | deviceIDStrategy、deviceDiscoveryStrategy |
运行时策略切换 |
| Dependency Injection | resource.Manager 注入 MIG、NVML 注入 CDI |
解耦,便于测试 |
| Lazy Init + Cache | MIG devicesMap |
避免重复设备扫描 |
| Mock | MockNvidiaPCI、api_mock.go |
单元测试隔离 |
6.2 关键架构决策
-
CDI Spec 生成委托给 nvcdi 库 :k8s-device-plugin 不直接操作 CDI Spec JSON,而是通过
nvidia-container-toolkit/pkg/nvcdi库生成,保持与 NVIDIA 容器工具链的一致性 -
Root Transformer 分离 :driver root 和 dev root 可独立配置并分别转换,适应复杂的容器化部署场景(如驱动安装在 host 的
/driver-root,但设备节点在/dev) -
MIG 设备路径通过 procfs 映射 :而非直接使用 NVML API 获取设备路径,因为 MIG 设备节点的 minor 号由内核动态分配,只有
/proc/driver/nvidia-caps/mig-minors文件提供权威映射 -
vGPU 检测基于 PCI 配置空间:而非依赖 NVIDIA 驱动或 NVML,因为 vGPU 设备的 PCI 配置空间由 hypervisor 直接填充,即使驱动未正确加载也能检测
-
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 源码逐行分析生成。所有代码引用均来自实际源文件,图表基于代码结构绘制。