【NVIDIA】 NVIDIA Container Toolkit v1.19.1 — OCI 模块超深度分析之三

NVIDIA Container Toolkit v1.19.1 --- OCI 模块超深度分析

源码路径:internal/oci/

版本:v1.19.1

文档定位:Go源码架构逐行分析系列 · 第三篇


一、模块定位

1.1 模块职责

internal/oci 模块是 NVIDIA Container Toolkit 的 OCI 抽象层,负责封装 OCI 运行时规范的读写操作和低层运行时调用。其核心职责包括:

  1. OCI Spec 读写 :从文件加载 config.json,修改后写回文件
  2. Runtime 调用 :通过 syscall.Exec 将当前进程替换为低层运行时(runc/crun)
  3. Spec 修改框架 :定义 SpecModifier 接口,允许外部代码修改 OCI spec
  4. 参数解析:从命令行参数中提取 bundle 目录、spec 文件路径
  5. 容器状态:加载和解析 OCI 容器状态
  6. 进程替换 :通过 syscall.Exec 实现进程替换,将控制权交给 runc

1.2 在系统中的位置

复制代码
┌─────────────────────────────────────────────────────────────────────┐
│                     internal/runtime 模块                           │
│              (调用 OCI 抽象层来创建和执行运行时)                      │
└──────────────────────────┬──────────────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────────────┐
│                      internal/oci 模块                               │
│  ┌─────────────┐  ┌──────────────┐  ┌─────────────────────────┐    │
│  │ Spec 接口   │  │ Runtime 接口  │  │ SpecModifier 接口       │    │
│  │ (spec.go)   │  │ (runtime.go) │  │ (spec.go)               │    │
│  └──────┬──────┘  └──────┬───────┘  └─────────────────────────┘    │
│         │                 │                                          │
│  ┌──────┴──────┐  ┌──────┴───────┐  ┌─────────────────────────┐    │
│  │ fileSpec    │  │ pathRuntime   │  │ modifyingRuntimeWrapper │    │
│  │ memorySpec  │  │ syscallExec   │  │ lowLevelRuntime         │    │
│  └─────────────┘  └──────────────┘  └─────────────────────────┘    │
│  ┌─────────────┐  ┌──────────────┐  ┌─────────────────────────┐    │
│  │ args.go     │  │ state.go     │  │ options.go              │    │
│  └─────────────┘  └──────────────┘  └─────────────────────────┘    │
└─────────────────────────────────────────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────────────┐
│                     runc / crun (低层 OCI 运行时)                    │
│              通过 syscall.Exec 进程替换执行                          │
└─────────────────────────────────────────────────────────────────────┘

1.3 模块边界

方向 依赖 说明
上游 internal/runtime runtime 模块调用 OCI 层创建和执行运行时
下游 pkg/lookup 可执行文件查找(查找 runc/crun)
下游 internal/logger 日志接口
下游 specs-go OCI 运行时规范类型定义
平行 internal/modifier 修改器实现 SpecModifier 接口

二、模块整体结构

2.1 文件清单

文件 职责 行数(约)
spec.go Spec 接口定义、SpecModifier 接口、NewSpec() 工厂 ~75
spec_file.go fileSpec 结构体,文件支持的 Spec 实现 ~110
spec_memory.go memorySpec 结构体,内存中的 Spec 实现 ~70
spec_mock.go SpecMock,自动生成的 mock(moq) ~180
runtime.go Runtime 接口定义 ~20
runtime_modifier.go modifyingRuntimeWrapper,修改 spec 后转发 ~80
runtime_low_level.go lowLevelRuntime,查找和包装 runc/crun ~55
runtime_path.go pathRuntime,通过路径执行运行时 ~60
runtime_syscall_exec.go syscallExec,通过 syscall.Exec 执行 ~30
runtime_mock.go RuntimeMock,自动生成的 mock(moq) ~130
args.go 命令行参数解析(bundle dir, create 子命令检测) ~100
state.go State 结构体,OCI 容器状态 ~90
options.go Option 模式(WithLogger, WithAllowUnknownFields) ~35
*_test.go 测试文件(6个) ~400

2.2 接口定义

Spec 接口
go 复制代码
// Spec 定义了 OCI 规范的操作接口
type Spec interface {
    Load() (*specs.Spec, error)           // 从源加载 spec
    Flush() error                          // 将 spec 写回源
    Modify(SpecModifier) error             // 应用修改器
    LookupEnv(string) (string, bool)       // 查找环境变量
}
Runtime 接口
go 复制代码
// Runtime 是运行时 shim 的接口
type Runtime interface {
    Exec([]string) error    // 执行运行时
    String() string          // 返回运行时标识
}
SpecModifier 接口
go 复制代码
// SpecModifier 定义了修改 OCI spec 的接口
type SpecModifier interface {
    Modify(*specs.Spec) error    // 就地修改 spec
}

2.3 实现类

接口 实现类 文件 说明
Spec fileSpec spec_file.go 文件支持的 spec,读写 config.json
Spec memorySpec spec_memory.go 内存 spec,无文件 I/O
Spec SpecMock spec_mock.go 测试用 mock
Runtime pathRuntime runtime_path.go 通过路径执行二进制
Runtime modifyingRuntimeWrapper runtime_modifier.go 修改 spec 后转发
Runtime syscallExec runtime_syscall_exec.go syscall.Exec 实现
Runtime RuntimeMock runtime_mock.go 测试用 mock
SpecModifier SpecModifiers spec.go 修改器列表
SpecModifier (外部实现) modifier/ 各种具体修改器

2.4 类结构关系图

#mermaid-svg-G8igSaQI3RSsuhdp{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-G8igSaQI3RSsuhdp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-G8igSaQI3RSsuhdp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-G8igSaQI3RSsuhdp .error-icon{fill:#552222;}#mermaid-svg-G8igSaQI3RSsuhdp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-G8igSaQI3RSsuhdp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-G8igSaQI3RSsuhdp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-G8igSaQI3RSsuhdp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-G8igSaQI3RSsuhdp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-G8igSaQI3RSsuhdp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-G8igSaQI3RSsuhdp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-G8igSaQI3RSsuhdp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-G8igSaQI3RSsuhdp .marker.cross{stroke:#333333;}#mermaid-svg-G8igSaQI3RSsuhdp svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-G8igSaQI3RSsuhdp p{margin:0;}#mermaid-svg-G8igSaQI3RSsuhdp g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-G8igSaQI3RSsuhdp g.classGroup text .title{font-weight:bolder;}#mermaid-svg-G8igSaQI3RSsuhdp .cluster-label text{fill:#333;}#mermaid-svg-G8igSaQI3RSsuhdp .cluster-label span{color:#333;}#mermaid-svg-G8igSaQI3RSsuhdp .cluster-label span p{background-color:transparent;}#mermaid-svg-G8igSaQI3RSsuhdp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-G8igSaQI3RSsuhdp .cluster text{fill:#333;}#mermaid-svg-G8igSaQI3RSsuhdp .cluster span{color:#333;}#mermaid-svg-G8igSaQI3RSsuhdp .nodeLabel,#mermaid-svg-G8igSaQI3RSsuhdp .edgeLabel{color:#131300;}#mermaid-svg-G8igSaQI3RSsuhdp .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-G8igSaQI3RSsuhdp .label text{fill:#131300;}#mermaid-svg-G8igSaQI3RSsuhdp .labelBkg{background:#ECECFF;}#mermaid-svg-G8igSaQI3RSsuhdp .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-G8igSaQI3RSsuhdp .classTitle{font-weight:bolder;}#mermaid-svg-G8igSaQI3RSsuhdp .node rect,#mermaid-svg-G8igSaQI3RSsuhdp .node circle,#mermaid-svg-G8igSaQI3RSsuhdp .node ellipse,#mermaid-svg-G8igSaQI3RSsuhdp .node polygon,#mermaid-svg-G8igSaQI3RSsuhdp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-G8igSaQI3RSsuhdp .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-G8igSaQI3RSsuhdp g.clickable{cursor:pointer;}#mermaid-svg-G8igSaQI3RSsuhdp g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-G8igSaQI3RSsuhdp g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-G8igSaQI3RSsuhdp .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-G8igSaQI3RSsuhdp .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-G8igSaQI3RSsuhdp .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-G8igSaQI3RSsuhdp .dashed-line{stroke-dasharray:3;}#mermaid-svg-G8igSaQI3RSsuhdp .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-G8igSaQI3RSsuhdp #compositionStart,#mermaid-svg-G8igSaQI3RSsuhdp .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-G8igSaQI3RSsuhdp #compositionEnd,#mermaid-svg-G8igSaQI3RSsuhdp .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-G8igSaQI3RSsuhdp #dependencyStart,#mermaid-svg-G8igSaQI3RSsuhdp .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-G8igSaQI3RSsuhdp #dependencyStart,#mermaid-svg-G8igSaQI3RSsuhdp .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-G8igSaQI3RSsuhdp #extensionStart,#mermaid-svg-G8igSaQI3RSsuhdp .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-G8igSaQI3RSsuhdp #extensionEnd,#mermaid-svg-G8igSaQI3RSsuhdp .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-G8igSaQI3RSsuhdp #aggregationStart,#mermaid-svg-G8igSaQI3RSsuhdp .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-G8igSaQI3RSsuhdp #aggregationEnd,#mermaid-svg-G8igSaQI3RSsuhdp .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-G8igSaQI3RSsuhdp #lollipopStart,#mermaid-svg-G8igSaQI3RSsuhdp .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-G8igSaQI3RSsuhdp #lollipopEnd,#mermaid-svg-G8igSaQI3RSsuhdp .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-G8igSaQI3RSsuhdp .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-G8igSaQI3RSsuhdp .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-G8igSaQI3RSsuhdp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-G8igSaQI3RSsuhdp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-G8igSaQI3RSsuhdp :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 实现
实现
实现
实现
实现
实现
实现
实现
嵌入
持有
持有
持有
持有
<<interface>>
Spec
+Load()(*Spec, error)
+Flush() : error
+Modify(SpecModifier) : error
+LookupEnv(key string)(string, bool)
<<interface>>
Runtime
+Exec(args \[\]string) : error
+String() : string
<<interface>>
SpecModifier
+Modify(*Spec) : error
fileSpec
-memorySpec
-path string
-loader loader
+Load()(*Spec, error)
+Flush() : error
memorySpec
+Spec *specs.Spec
+Load()(*Spec, error)
+Flush() : error
+Modify(SpecModifier) : error
+LookupEnv(key)(string, bool)
pathRuntime
-logger logger.Interface
-path string
-execRuntime Runtime
+Exec(args \[\]string) : error
+String() : string
modifyingRuntimeWrapper
-logger logger.Interface
-runtime Runtime
-ociSpec Spec
-modifier SpecModifier
+Exec(args \[\]string) : error
+modify() : error
+String() : string
syscallExec
+Exec(args \[\]string) : error
+String() : string
<<type>>
SpecModifiers
+Modify(*Spec) : error
SpecMock
RuntimeMock

2.5 核心方法清单

方法 所在文件 签名 职责
NewSpec() spec.go func NewSpec(args []string, opts ...Option) (Spec, error) 创建 fileSpec
NewFileSpec() spec_file.go func NewFileSpec(filepath string, isStrict bool) Spec 创建文件 spec
NewMemorySpec() spec_memory.go func NewMemorySpec(spec *specs.Spec) Spec 创建内存 spec
fileSpec.Load() spec_file.go func (s *fileSpec) Load() (*specs.Spec, error) 从文件加载
fileSpec.Flush() spec_file.go func (s fileSpec) Flush() error 写入文件
memorySpec.Modify() spec_memory.go func (s *memorySpec) Modify(m SpecModifier) error 应用修改器
memorySpec.LookupEnv() spec_memory.go func (s memorySpec) LookupEnv(key string) (string, bool) 查找环境变量
NewLowLevelRuntime() runtime_low_level.go func NewLowLevelRuntime(logger, candidates) (Runtime, error) 查找 runc/crun
NewRuntimeForPath() runtime_path.go func NewRuntimeForPath(logger, path string) (Runtime, error) 按路径创建
NewModifyingRuntimeWrapper() runtime_modifier.go func NewModifyingRuntimeWrapper(logger, runtime, spec, modifier) Runtime 创建包装运行时
GetBundleDir() args.go func GetBundleDir(args []string) (string, error) 获取 bundle 目录
HasCreateSubcommand() args.go func HasCreateSubcommand(args []string) bool 检测 create 子命令
LoadContainerState() state.go func LoadContainerState(filename string) (*State, error) 加载容器状态

三、核心逻辑深度解析

3.1 spec.go 完整代码逐行注释

go 复制代码
// =============================================================================
// 文件: internal/oci/spec.go
// 职责: 定义 Spec 接口、SpecModifier 接口、NewSpec 工厂函数
// 这是 OCI 模块的核心抽象定义文件
// =============================================================================

package oci

import (
    "fmt"       // 格式化错误

    // OCI 运行时规范类型定义
    "github.com/opencontainers/runtime-spec/specs-go"
)

// SpecModifier 定义了修改 OCI spec 的接口。
// 任何需要修改 OCI spec 的组件都必须实现此接口。
// 修改是就地的(in-place),通过指针直接修改 spec 结构体。
//
// 实现此接口的组件包括:
//   - stableRuntimeModifier (legacy 模式: 注入 prestart hook)
//   - csvModifier (csv 模式: 基于 CSV 文件修改)
//   - cdiModifier (cdi 模式: 注入 CDI 设备)
//   - hookRemover (移除旧 hook)
//   - graphicsModifier (注入图形库)
//   - discoverModifier (基于发现器修改)
//   - list (修改器列表)
//   - Factory (工厂修改器)
type SpecModifier interface {
    // Modify 接收一个指向 OCI Spec 的指针,就地修改并返回错误。
    // 设计意图:
    // - 使用指针避免拷贝整个 spec
    // - 返回 error 而非 bool,允许传递详细错误信息
    // - 修改是幂等的(理论上),重复调用不应有副作用
    Modify(*specs.Spec) error
}

// SpecModifiers 是 SpecModifier 的集合,实现了 SpecModifier 接口。
// 这允许将多个修改器组合为一个,按顺序执行。
//
// 用法:
//   modifiers := SpecModifiers{modifier1, modifier2, modifier3}
//   spec.Modify(modifiers)  // 按顺序应用所有修改器
//
// 空修改器 (nil) 会被跳过,不会导致 panic
type SpecModifiers []SpecModifier

// 确保 SpecModifiers 实现了 SpecModifier 接口
var _ SpecModifier = (SpecModifiers)(nil)

// Spec 定义了 OCI 规范的操作接口。
// 这是一个抽象接口,有多种实现:
//   - fileSpec: 从文件加载,修改后写回文件
//   - memorySpec: 纯内存操作,不涉及文件 I/O
//   - SpecMock: 测试用 mock
//
// 生命周期:
//   1. New() / NewFileSpec() / NewMemorySpec() → 创建 Spec 实例
//   2. Load() → 从源加载 specs.Spec 到内存
//   3. Modify(modifier) → 应用修改器修改 spec
//   4. Flush() → 将修改后的 spec 写回源
//
//go:generate moq -rm -fmt=goimports -stub -out spec_mock.go . Spec
type Spec interface {
    // Load 从源(文件/内存)加载 OCI spec 并返回。
    // 对于 fileSpec: 读取 config.json 文件
    // 对于 memorySpec: 直接返回内存中的 spec
    // 返回的 *specs.Spec 是内部持有的指针,修改它会直接影响 Spec 的状态
    Load() (*specs.Spec, error)

    // Flush 将当前 spec 写回源。
    // 对于 fileSpec: 将 spec 写入 config.json 文件
    // 对于 memorySpec: 无操作(no-op)
    // 必须在 Load 和 Modify 之后调用
    Flush() error

    // Modify 应用指定的修改器到 spec 上。
    // 这通常在 Load 之后、Flush 之前调用。
    // 修改器接收 spec 指针,就地修改
    Modify(SpecModifier) error

    // LookupEnv 在 spec 的环境变量中查找指定键。
    // 类似于 os.LookupEnv,但查找的是容器 spec 中的环境变量
    // 而非当前进程的环境变量
    // 返回: (值, 是否存在)
    LookupEnv(string) (string, bool)
}

// NewSpec 根据命令行参数创建文件支持的 Spec 实例。
// 这是 runtime 模块创建 Spec 的标准入口。
//
// 处理流程:
// 1. 应用 Option 配置
// 2. 从 args 中提取 bundle 目录路径
// 3. 构造 spec 文件路径: {bundle}/config.json
// 4. 创建 fileSpec 实例
//
// 参数:
//   args []string     - 命令行参数(用于提取 --bundle 路径)
//   opts ...Option    - 配置选项(logger, allowUnknownFields)
//
// 返回:
//   Spec  - fileSpec 实例
//   error - 如果无法获取 bundle 目录
func NewSpec(args []string, opts ...Option) (Spec, error) {
    // 创建默认 options,allowUnkownFields 默认为 false
    // 注意: 字段名有拼写错误 "allowUnkownFields"(少了一个 n),但在代码中保持一致
    o := &options{
        allowUnkownFields: false,
    }
    // 应用所有 Option 函数
    for _, opt := range opts {
        opt(o)
    }

    // 从命令行参数获取 bundle 目录
    // GetBundleDir 内部调用 GetBundleDirFromArgs
    // 支持 --bundle=/path, --bundle /path, -b=/path, -b /path 格式
    bundleDir, err := GetBundleDir(args)
    if err != nil {
        return nil, fmt.Errorf("error getting bundle directory: %v", err)
    }
    o.logger.Debugf("Using bundle directory: %v", bundleDir)

    // 构造 spec 文件路径: {bundleDir}/config.json
    ociSpecPath := GetSpecFilePath(bundleDir)
    o.logger.Infof("Using OCI specification file path: %v", ociSpecPath)

    // 创建 fileSpec 实例
    // 第二个参数 !o.allowUnkownFields:
    //   true = 严格模式,DisallowUnknownFields
    //   false = 宽松模式,允许未知字段
    ociSpec := NewFileSpec(ociSpecPath, !o.allowUnkownFields)

    return ociSpec, nil
}

// Modify 实现 SpecModifier 接口 for SpecModifiers 列表。
// 按顺序遍历所有修改器,依次应用。
// 如果任一修改器返回错误,立即停止并返回该错误。
// nil 修改器会被跳过。
func (ms SpecModifiers) Modify(s *specs.Spec) error {
    for _, m := range ms {
        // 跳过 nil 修改器,避免 panic
        if m == nil {
            continue
        }
        // 应用修改器
        if err := m.Modify(s); err != nil {
            // 遇到错误立即返回,不继续执行后续修改器
            return err
        }
    }
    return nil
}

3.2 spec_file.go 完整代码逐行注释

go 复制代码
// =============================================================================
// 文件: internal/oci/spec_file.go
// 职责: fileSpec 结构体 --- 文件支持的 OCI Spec 实现
// 这是实际使用的 Spec 实现,负责读写 config.json 文件
// =============================================================================

package oci

import (
    "encoding/json"  // JSON 编解码
    "fmt"            // 格式化错误
    "io"             // I/O 接口
    "os"             // 文件操作

    "github.com/opencontainers/runtime-spec/specs-go"
)

// fileSpec 是文件支持的 Spec 实现。
// 它嵌入了 memorySpec,复用了 Modify 和 LookupEnv 方法。
// 额外添加了 path(文件路径)和 loader(解析模式)字段。
//
// 继承关系:
//   fileSpec
//     └── memorySpec (嵌入)
//           └── *specs.Spec (嵌入)
//
// 方法覆盖:
//   Load()   --- fileSpec 自己实现(从文件读取)
//   Flush()  --- fileSpec 自己实现(写入文件)
//   Modify() --- 继承自 memorySpec
//   LookupEnv() --- 继承自 memorySpec
type fileSpec struct {
    memorySpec        // 嵌入 memorySpec,复用 Modify 和 LookupEnv
    path   string     // OCI spec 文件路径(如 "/tmp/bundle/config.json")
    loader loader     // JSON 解析模式:严格或宽松
}

// loader 是一个布尔类型,表示 JSON 解析模式
type loader bool

const (
    // strictLoader 严格模式,使用 DisallowUnknownFields()
    // 解码时遇到未知字段会返回错误
    strictLoader = loader(true)
)

// 确保 fileSpec 实现了 Spec 接口
var _ Spec = (*fileSpec)(nil)

// NewFileSpec 创建一个文件支持的 Spec 实例。
//
// 参数:
//   filepath string  - spec 文件路径(如 "/tmp/bundle/config.json")
//   isStrict bool    - 是否使用严格模式解析 JSON
//                      true: DisallowUnknownFields,遇到未知字段报错
//                      false: 忽略未知字段
//
// 返回:
//   Spec - fileSpec 实例(指针)
//
// 注意: 此函数不检查文件是否存在,文件在 Load() 时才打开
func NewFileSpec(filepath string, isStrict bool) Spec {
    var loader loader
    if isStrict {
        loader = strictLoader
    }
    oci := fileSpec{
        path:   filepath,
        loader: loader,
    }
    // 此时 memorySpec.Spec 为 nil,需要调用 Load() 后才有值
    return &oci
}

// Load 从文件读取 OCI spec 并加载到内存。
// 文件以只读模式打开。
//
// 处理流程:
// 1. 以只读模式打开文件
// 2. 使用 JSON 解码器解析文件内容
// 3. 将解析结果存入 memorySpec.Spec
// 4. 返回 spec 指针
//
// 返回:
//   *specs.Spec - 加载的 spec(也是内部存储的指针)
//   error       - 文件打开或解析错误
func (s *fileSpec) Load() (*specs.Spec, error) {
    // 以只读模式打开 spec 文件
    specFile, err := os.Open(s.path)
    if err != nil {
        return nil, fmt.Errorf("error opening OCI specification file: %v", err)
    }
    // defer 确保文件在函数返回时关闭
    defer specFile.Close()

    // 使用 loader 解析文件内容
    spec, err := s.loadFrom(specFile)
    if err != nil {
        return nil, fmt.Errorf("error loading OCI specification from file: %v", err)
    }

    // 将解析后的 spec 存入 memorySpec.Spec
    // 这使得后续的 Modify() 和 LookupEnv() 可以直接操作该 spec
    s.Spec = spec
    return s.Spec, nil
}

// loadFrom 从 io.Reader 读取并解析 OCI spec。
// 这是一个方法,接收者是 loader 类型。
//
// 解析模式:
//   strictLoader (true): 调用 DisallowUnknownFields(),未知字段报错
//   非严格 (false):      允许未知字段,忽略它们
//
// 参数:
//   reader io.Reader - 数据源(文件、缓冲区等)
//
// 返回:
//   *specs.Spec - 解析后的 spec
//   error       - 解析错误
func (isStrict loader) loadFrom(reader io.Reader) (*specs.Spec, error) {
    // 创建 JSON 解码器
    decoder := json.NewDecoder(reader)

    // 严格模式:禁止未知字段
    if isStrict {
        decoder.DisallowUnknownFields()
        // DisallowUnknownFields 使得解码时遇到未知字段返回错误
        // 这是一种安全措施,防止 OCI spec 中注入意外字段
    }

    // 声明 spec 变量
    var spec specs.Spec

    // 解码 JSON 到 spec
    err := decoder.Decode(&spec)
    if err != nil {
        return nil, fmt.Errorf("error reading OCI specification: %v", err)
    }

    return &spec, nil
}

// Modify 应用修改器到 spec。
// fileSpec 直接继承 memorySpec.Modify,不额外实现。
// 这是因为修改操作不涉及文件 I/O,只操作内存中的 spec 指针。
func (s *fileSpec) Modify(m SpecModifier) error {
    return s.memorySpec.Modify(m)
}

// Flush 将当前 spec 写入文件。
// 文件被截断(覆盖写入),不是追加。
//
// 前提条件:
//   必须先调用 Load(),否则 s.Spec 为 nil
//
// 处理流程:
// 1. 检查 spec 是否已加载
// 2. 创建(截断)文件
// 3. 使用 JSON 编码器写入 spec
// 4. 关闭文件
//
// 返回:
//   error - 如果 spec 未加载或文件操作失败
func (s fileSpec) Flush() error {
    // 检查 spec 是否已加载
    if s.Spec == nil {
        return fmt.Errorf("no OCI specification loaded")
    }

    // 创建/截断文件
    // os.Create 等价于 OpenFile(path, O_RDWR|O_CREATE|O_TRUNC, 0666)
    specFile, err := os.Create(s.path)
    if err != nil {
        return fmt.Errorf("error opening OCI specification file: %v", err)
    }
    defer specFile.Close()

    // 使用 flushTo 写入 spec
    return flushTo(s.Spec, specFile)
}

// flushTo 将 spec 写入指定的 io.Writer。
// 这是一个辅助函数,也可以用于写入缓冲区等。
//
// 参数:
//   spec *specs.Spec  - 要写入的 spec
//   writer io.Writer   - 目标写入器
//
// 返回:
//   error - 写入错误
//
// 注意: 如果 spec 为 nil,返回 nil(不写入任何内容)
func flushTo(spec *specs.Spec, writer io.Writer) error {
    // nil spec → 不写入
    if spec == nil {
        return nil
    }
    // 创建 JSON 编码器
    encoder := json.NewEncoder(writer)

    // 编码并写入
    err := encoder.Encode(spec)
    if err != nil {
        return fmt.Errorf("error writing OCI specification: %v", err)
    }

    return nil
}

3.3 spec_memory.go 完整代码逐行注释

go 复制代码
// =============================================================================
// 文件: internal/oci/spec_memory.go
// 职责: memorySpec 结构体 --- 纯内存的 OCI Spec 实现
// 不涉及文件 I/O,用于测试和内存操作场景
// =============================================================================

package oci

import (
    "fmt"       // 格式化错误
    "strings"   // 字符串操作(环境变量解析)

    "github.com/opencontainers/runtime-spec/specs-go"
)

// memorySpec 是纯内存的 Spec 实现。
// 它直接持有一个 *specs.Spec 指针,不涉及文件 I/O。
//
// fileSpec 嵌入 memorySpec,复用了 Modify 和 LookupEnv 方法。
//
// 与 fileSpec 的对比:
//   Load()  --- memorySpec: 直接返回 Spec (no-op)
//             fileSpec:   从文件读取
//   Flush() --- memorySpec: 返回 nil (no-op)
//             fileSpec:   写入文件
//   Modify() --- 两者相同(继承)
//   LookupEnv() --- 两者相同(继承)
type memorySpec struct {
    *specs.Spec    // 嵌入 OCI spec 指针
}

// NewMemorySpec 从已有的 OCI spec 创建内存 Spec 实例。
//
// 参数:
//   spec *specs.Spec - 已有的 spec 指针
//
// 返回:
//   Spec - memorySpec 实例
//
// 用途:
//   - 测试场景:不需要文件 I/O
//   - 内存操作:直接操作 spec 而不读写文件
func NewMemorySpec(spec *specs.Spec) Spec {
    s := memorySpec{
        Spec: spec,
    }
    return &s
}

// Load 是 memorySpec 的 no-op 实现。
// 直接返回内存中已有的 spec 指针。
// 不进行任何文件 I/O。
func (s *memorySpec) Load() (*specs.Spec, error) {
    return s.Spec, nil
}

// Flush 是 memorySpec 的 no-op 实现。
// 不写入任何文件,直接返回 nil。
func (s *memorySpec) Flush() error {
    return nil
}

// Modify 应用修改器到 spec。
// 这是核心的修改方法,被 fileSpec 继承。
//
// 处理流程:
// 1. 检查 spec 是否为 nil(防止 panic)
// 2. 调用修改器的 Modify 方法
// 3. 修改器直接操作 s.Spec 指针,就地修改
//
// 参数:
//   m SpecModifier - 要应用的修改器
//
// 返回:
//   error - 如果 spec 为 nil 或修改器返回错误
func (s *memorySpec) Modify(m SpecModifier) error {
    // 安全检查:防止 nil spec 导致 panic
    if s.Spec == nil {
        return fmt.Errorf("cannot modify nil spec")
    }
    // 调用修改器的 Modify 方法
    // 修改器接收 *specs.Spec 指针,直接修改内存中的 spec
    return m.Modify(s.Spec)
}

// LookupEnv 在 spec 的环境变量中查找指定键。
// 这模拟了 os.LookupEnv 的行为,但查找的是容器 spec 中的环境变量。
//
// 查找逻辑:
// 1. 检查 spec 和 Process 是否为 nil
// 2. 遍历 spec.Process.Env 数组
// 3. 先用 HasPrefix 快速过滤
// 4. 再用 SplitN 分割 key=value
// 5. 检查 key 是否完全匹配
//
// 参数:
//   key string - 要查找的环境变量名
//
// 返回:
//   string - 环境变量的值(可能为空)
//   bool   - 是否找到
//
// 边界情况处理:
//   - spec 为 nil → ("", false)
//   - Process 为 nil → ("", false)
//   - KEY 存在但无 = → ("", true)
//   - KEY= → ("", true)
//   - KEY=value → ("value", true)
//   - KEY=a=b → ("a=b", true)  // 只在第一个 = 处分割
func (s memorySpec) LookupEnv(key string) (string, bool) {
    // 安全检查
    if s.Spec == nil || s.Process == nil {
        return "", false
    }

    // 遍历环境变量列表
    for _, env := range s.Process.Env {
        // 快速过滤:只处理以 key 开头的环境变量
        if !strings.HasPrefix(env, key) {
            continue
        }

        // 分割 key=value(最多 2 部分)
        parts := strings.SplitN(env, "=", 2)
        // 检查 key 是否完全匹配
        if parts[0] == key {
            if len(parts) < 2 {
                // 只有 key 没有 = 的情况
                // 例如: "NVIDIA_VISIBLE_DEVICES"(无 = 和值)
                return "", true
            }
            // 返回值
            return parts[1], true
        }
    }

    return "", false
}

3.4 runtime.go 完整代码逐行注释

go 复制代码
// =============================================================================
// 文件: internal/oci/runtime.go
// 职责: 定义 Runtime 接口 --- 运行时 shim 的抽象接口
// 虽然代码量很小,但这是 OCI 模块的核心接口定义
// =============================================================================

package oci

// Runtime 是运行时 shim 的接口。
// "shim" 意为垫片/适配器,它坐在低层运行时(如 runc)之上,
// 在调用低层运行时之前可能执行额外操作(如修改 spec)。
//
// 实现此接口的类:
//   - pathRuntime: 直接通过路径执行二进制
//   - modifyingRuntimeWrapper: 修改 spec 后转发到包装的运行时
//   - RuntimeMock: 测试用 mock
//
// 使用场景:
//   1. runtime 模块通过 NewLowLevelRuntime 创建 pathRuntime
//   2. NewModifyingRuntimeWrapper 包装 pathRuntime,添加 spec 修改能力
//   3. 最终调用 Exec() 通过 syscall.Exec 进程替换到 runc
//
//go:generate moq -rm -fmt=goimports -stub -out runtime_mock.go . Runtime
type Runtime interface {
    // Exec 接收命令行参数并执行运行时。
    // 对于 pathRuntime: 通过 syscall.Exec 替换当前进程
    // 对于 modifyingRuntimeWrapper: 先修改 spec(如果是 create),再 exec
    // 对于 syscallExec: 直接调用 syscall.Exec
    //
    // 注意: 成功的 Exec 通常不会返回(进程被替换)
    // 如果 Exec 返回,要么是错误,要么是意外状态
    Exec([]string) error

    // String 返回运行时的字符串表示。
    // 用于日志记录和调试。
    // 例如:
    //   pathRuntime.String() → "/usr/bin/runc"
    //   modifyingRuntimeWrapper.String() → "modify on-create and forward to /usr/bin/runc"
    //   syscallExec.String() → "exec"
    String() string
}

3.5 runtime_modifier.go 完整代码逐行注释

go 复制代码
// =============================================================================
// 文件: internal/oci/runtime_modifier.go
// 职责: modifyingRuntimeWrapper --- 修改 OCI spec 后转发到低层运行时
// 这是 NVIDIA Container Runtime 的核心包装器,实现 "修改后执行" 模式
// =============================================================================

package oci

import (
    "fmt"       // 格式化错误和字符串

    "github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
)

// modifyingRuntimeWrapper 包装一个 Runtime,在执行前修改 OCI spec。
// 这是装饰器模式(Decorator Pattern)的典型应用。
//
// 工作原理:
// 1. 当 Exec 被调用时,检查是否为 create 子命令
// 2. 如果是 create: 加载 spec → 修改 spec → 写回 spec → 转发到低层运行时
// 3. 如果不是 create: 直接转发到低层运行时
//
// 修改 spec 的三步骤 (modify 方法):
//   Load() → Modify(modifier) → Flush()
//   加载 → 修改 → 写回
type modifyingRuntimeWrapper struct {
    logger   logger.Interface     // 日志记录器
    runtime  Runtime               // 被包装的低层运行时(如 pathRuntime)
    ociSpec  Spec                  // OCI spec 读写器(如 fileSpec)
    modifier SpecModifier          // spec 修改器(如 stableRuntimeModifier)
}

// 确保 modifyingRuntimeWrapper 实现了 Runtime 接口
var _ Runtime = (*modifyingRuntimeWrapper)(nil)

// NewModifyingRuntimeWrapper 创建一个修改包装运行时。
// 如果 modifier 为 nil,直接返回被包装的 runtime(不做修改)。
// 这是一个优化:避免不必要的包装层。
//
// 参数:
//   logger   logger.Interface  - 日志记录器
//   runtime  Runtime            - 被包装的低层运行时
//   spec     Spec               - OCI spec 读写器
//   modifier SpecModifier       - spec 修改器
//
// 返回:
//   Runtime - 如果 modifier 为 nil,返回原 runtime;否则返回 wrapper
func NewModifyingRuntimeWrapper(logger logger.Interface, runtime Runtime, spec Spec, modifier SpecModifier) Runtime {
    // 如果修改器为 nil,不需要修改 spec,直接返回原运行时
    if modifier == nil {
        logger.Tracef("Using low-level runtime with no modification")
        return runtime
        // 这种设计避免了空修改器的额外开销
        // 也避免了 modify() 中 Load/Modify/Flush 的不必要调用
    }

    // 创建包装器
    rt := modifyingRuntimeWrapper{
        logger:   logger,
        runtime:  runtime,
        ociSpec:  spec,
        modifier: modifier,
    }
    return &rt
}

// Exec 执行运行时,在必要时先修改 OCI spec。
//
// 处理流程:
// 1. 检查是否为 create 子命令
// 2. 如果是 create: 调用 modify() 修改 spec
// 3. 转发到包装的低层运行时
//
// 参数:
//   args []string - 命令行参数
//
// 返回:
//   error - 修改或执行错误
//
// 注意: 成功的 syscall.Exec 不会返回,所以如果一切正常,
//       这里的 return 语句永远不会被执行
func (r *modifyingRuntimeWrapper) Exec(args []string) error {
    // 检查是否为 create 子命令
    if HasCreateSubcommand(args) {
        r.logger.Debugf("Create command detected; applying OCI specification modifications")

        // 修改 OCI spec
        err := r.modify()
        if err != nil {
            return fmt.Errorf("could not apply required modification to OCI specification: %w", err)
            // %w 包装错误,保留原始错误链
        }
        r.logger.Debugf("Applied required modification to OCI specification")
    }

    // 转发到低层运行时
    r.logger.Debugf("Forwarding command to runtime %v", r.runtime.String())
    return r.runtime.Exec(args)
}

// modify 执行 spec 的三步操作: Load → Modify → Flush
//
// 1. Load: 从文件加载 OCI spec 到内存
// 2. Modify: 应用修改器到 spec
// 3. Flush: 将修改后的 spec 写回文件
//
// 这三步是原子性的概念:如果任一步骤失败,整个操作视为失败。
// 但注意:如果 Modify 成功但 Flush 失败,文件可能处于不一致状态
// (内存中已修改但文件未更新)。不过由于后续 syscall.Exec 会替换进程,
// 这种不一致通常不会有实际影响。
//
// 返回:
//   error - 任一步骤的错误
func (r *modifyingRuntimeWrapper) modify() error {
    // 步骤1: 加载 spec
    _, err := r.ociSpec.Load()
    if err != nil {
        return fmt.Errorf("error loading OCI specification for modification: %v", err)
    }

    // 步骤2: 修改 spec
    err = r.ociSpec.Modify(r.modifier)
    if err != nil {
        return fmt.Errorf("error modifying OCI spec: %v", err)
    }

    // 步骤3: 写回 spec
    err = r.ociSpec.Flush()
    if err != nil {
        return fmt.Errorf("error writing modified OCI specification: %v", err)
    }

    return nil
}

// String 返回运行时的字符串表示。
// 格式: "modify on-create and forward to {低层运行时}"
// 例如: "modify on-create and forward to /usr/bin/runc"
//
// 这用于日志记录,帮助理解运行时的包装层次
func (r *modifyingRuntimeWrapper) String() string {
    return fmt.Sprintf("modify on-create and forward to %s", r.runtime.String())
}

3.6 runtime_low_level.go 完整代码逐行注释

go 复制代码
// =============================================================================
// 文件: internal/oci/runtime_low_level.go
// 职责: NewLowLevelRuntime --- 查找和创建低层运行时实例
// 负责在 PATH 中搜索 runc 或 crun 二进制文件
// =============================================================================

package oci

import (
    "fmt"       // 格式化错误

    "github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
    "github.com/NVIDIA/nvidia-container-toolkit/pkg/lookup"  // 可执行文件查找器
)

// NewLowLevelRuntime 创建一个低层运行时 Runtime 实例。
// 从候选列表中按顺序搜索可执行文件,返回第一个找到的。
//
// 参数:
//   logger logger.Interface  - 日志记录器
//   candidates []string      - 候选运行时名称列表(如 ["runc", "crun"])
//
// 返回:
//   Runtime - pathRuntime 实例
//   error    - 如果没有找到任何候选运行时
//
// 搜索流程:
// 1. 遍历候选列表
// 2. 对每个候选,在 PATH 中搜索可执行文件
// 3. 返回第一个找到的可执行文件路径
// 4. 创建 pathRuntime 实例
//
// 示例:
//   runtime, err := NewLowLevelRuntime(logger, []string{"runc", "crun"})
//   // 如果 /usr/bin/runc 存在,返回包装 /usr/bin/runc 的 pathRuntime
func NewLowLevelRuntime(logger logger.Interface, candidates []string) (Runtime, error) {
    // 查找运行时二进制文件路径
    runtimePath, err := findRuntime(logger, candidates)
    if err != nil {
        return nil, fmt.Errorf("error locating runtime: %v", err)
    }
    // 使用找到的路径创建 pathRuntime
    return NewRuntimeForPath(logger, runtimePath)
}

// findRuntime 在 PATH 中搜索候选运行时二进制文件。
// 返回第一个找到的可执行文件的绝对路径。
//
// 参数:
//   logger logger.Interface  - 日志记录器
//   candidates []string      - 候选名称列表
//
// 返回:
//   string - 可执行文件的绝对路径
//   error  - 如果没有找到任何候选
//
// 搜索逻辑:
// 1. 创建 ExecutableLocator,根路径为 "/"
// 2. 遍历候选列表
// 3. 对每个候选调用 locator.Locate()
// 4. 返回第一个找到的结果
//
// 注意: locator.Locate() 可能返回多个结果(如 PATH 中有多个目录包含同名文件)
//       只取第一个结果 (targets[0])
func findRuntime(logger logger.Interface, candidates []string) (string, error) {
    // 检查候选列表是否为空
    if len(candidates) == 0 {
        return "", fmt.Errorf("at least one runtime candidate must be specified")
    }

    // 创建可执行文件查找器
    // 根路径为 "/",意味着搜索整个文件系统的 PATH
    locator := lookup.NewExecutableLocator(logger, "/")

    // 遍历候选列表
    for _, candidate := range candidates {
        logger.Tracef("Looking for runtime binary '%v'", candidate)

        // 在 PATH 中搜索候选
        // Locate 返回匹配的路径列表
        targets, err := locator.Locate(candidate)
        if err == nil && len(targets) > 0 {
            // 找到了,记录并返回第一个匹配
            logger.Tracef("Found runtime binary '%v'", targets)
            return targets[0], nil
        }
        // 如果没找到(err != nil 或 targets 为空),继续尝试下一个候选
    }

    // 所有候选都未找到
    return "", fmt.Errorf("no runtime binary found from candidate list: %v", candidates)
}

3.7 runtime_path.go 完整代码逐行注释

go 复制代码
// =============================================================================
// 文件: internal/oci/runtime_path.go
// 职责: pathRuntime --- 通过路径执行运行时二进制文件
// 这是 Runtime 接口的最基本实现,直接通过路径执行二进制
// =============================================================================

package oci

import (
    "fmt"       // 格式化错误
    "os"        // 文件状态检查

    "github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
)

// pathRuntime 通过路径执行运行时二进制文件。
// 它持有一个 execRuntime 接口,实际执行通过 syscall.Exec 完成。
//
// 结构:
//   pathRuntime
//     ├── logger:       日志记录
//     ├── path:         运行时二进制路径(如 "/usr/bin/runc")
//     └── execRuntime:  执行器(syscallExec)
//
// 设计意图:
// - path 字段和 execRuntime 分离,便于测试 mock
// - 实际生产中 execRuntime 是 syscallExec{}
// - 测试中 execRuntime 是 RuntimeMock
type pathRuntime struct {
    logger      logger.Interface  // 日志记录器
    path        string            // 运行时二进制的绝对路径
    execRuntime Runtime            // 实际执行器(syscallExec)
}

// 确保 pathRuntime 实现了 Runtime 接口
var _ Runtime = (*pathRuntime)(nil)

// NewRuntimeForPath 为指定路径创建 Runtime 实例。
// 在创建前验证路径是否为有效可执行文件。
//
// 参数:
//   logger logger.Interface - 日志记录器
//   path string              - 运行时二进制路径
//
// 返回:
//   Runtime - pathRuntime 实例
//   error    - 如果路径无效
//
// 验证步骤:
// 1. os.Stat(path) --- 检查路径是否存在
// 2. info.IsDir() --- 检查是否为目录(目录不能执行)
// 3. info.Mode()&0111 --- 检查是否有执行权限位
//    0111 是八进制,表示任何用户的执行权限
func NewRuntimeForPath(logger logger.Interface, path string) (Runtime, error) {
    // 获取文件信息
    info, err := os.Stat(path)
    if err != nil {
        return nil, fmt.Errorf("invalid path '%v': %v", path, err)
    }
    // 检查是否为目录
    if info.IsDir() || info.Mode()&0111 == 0 {
        return nil, fmt.Errorf("specified path '%v' is not an executable file", path)
    }

    // 创建 pathRuntime 实例
    shim := pathRuntime{
        logger:      logger,
        path:        path,
        execRuntime: syscallExec{},  // 默认使用 syscall.Exec
    }

    return &shim, nil
}

// Exec 执行运行时二进制文件。
//
// 处理流程:
// 1. 构造参数列表: [path, ...args[1:]]
//    - args[0] 被替换为 path(运行时二进制路径)
//    - args[1:] 保留原有参数
// 2. 调用 execRuntime.Exec() 执行
//    - 实际为 syscall.Exec(),替换当前进程
//
// 参数替换的原因:
//   args[0] 通常是 "nvidia-container-runtime"(当前程序名)
//   需要替换为实际的运行时路径(如 "/usr/bin/runc")
//   args[1:] 是传给运行时的参数(如 "--bundle", "/path", "create")
//
// 参数:
//   args []string - 命令行参数
//
// 返回:
//   error - 执行错误
//   注意: 成功的 syscall.Exec 不会返回
func (s pathRuntime) Exec(args []string) error {
    // 构造新的参数列表
    // 第一个元素是运行时路径
    runtimeArgs := []string{s.path}
    // 保留 args[1:](跳过 args[0])
    if len(args) > 1 {
        runtimeArgs = append(runtimeArgs, args[1:]...)
    }

    // 调用执行器
    return s.execRuntime.Exec(runtimeArgs)
}

// String 返回运行时路径作为字符串表示。
// 例如: "/usr/bin/runc"
// 这用于日志记录和 modifyingRuntimeWrapper.String()
func (s pathRuntime) String() string {
    return s.path
}

3.8 args.go 完整代码逐行注释

go 复制代码
// =============================================================================
// 文件: internal/oci/args.go
// 职责: 命令行参数解析 --- 提取 bundle 目录、检测 create 子命令
// 这些函数从 runc 兼容的命令行参数中提取信息
// =============================================================================

package oci

import (
    "fmt"           // 格式化错误
    "path/filepath" // 路径拼接
    "strings"       // 字符串操作
)

// specFileName 定义 OCI spec 文件的标准名称
const (
    specFileName = "config.json"  // OCI 规范定义的 spec 文件名
)

// GetBundleDir 从命令行参数获取 bundle 目录。
// 这是 GetBundleDirFromArgs 的包装函数。
//
// 参数:
//   args []string - 命令行参数
//
// 返回:
//   string - bundle 目录路径(可能为空)
//   error  - 错误信息
func GetBundleDir(args []string) (string, error) {
    bundleDir, err := GetBundleDirFromArgs(args)
    if err != nil {
        return "", fmt.Errorf("error getting bundle dir from args: %v", err)
    }
    return bundleDir, nil
}

// GetBundleDirFromArgs 从命令行参数中提取 bundle 目录。
// 支持 runc 兼容的 bundle 标志格式。
//
// 支持的格式:
//   --bundle=BUNDLE_PATH    (用 = 分隔)
//   -bundle=BUNDLE_PATH     (用 = 分隔)
//   --bundle BUNDLE_PATH    (用空格分隔)
//   -b=BUNDLE_PATH          (用 = 分隔)
//   -b BUNDLE_PATH          (用空格分隔)
//
// 其中 {{SEP}} 是空格或 =
//
// 参数:
//   args []string - 命令行参数
//
// 返回:
//   string - bundle 目录路径
//   error  - 如果 --bundle/-b 标志后没有参数
//
// 边界情况:
//   - 没有 bundle 标志 → 返回 ("", nil)
//   - --bundle 是最后一个参数 → 返回错误
//   - 多个 --bundle 标志 → 使用最后一个
func GetBundleDirFromArgs(args []string) (string, error) {
    var bundleDir string

    for i := 0; i < len(args); i++ {
        param := args[i]

        // 分割参数名和值
        parts := strings.SplitN(param, "=", 2)
        if !IsBundleFlag(parts[0]) {
            // 不是 bundle 标志,跳过
            continue
        }

        // 格式: --bundle=/path
        if len(parts) == 2 {
            bundleDir = parts[1]
            continue
        }

        // 格式: --bundle /path(下一个参数是值)
        if i+1 < len(args) {
            bundleDir = args[i+1]
            i++  // 跳过下一个参数(它已被消费为 bundle 路径)
            continue
        }

        // --bundle 或 -b 是最后一个参数,没有值
        return "", fmt.Errorf("bundle option requires an argument")
    }

    return bundleDir, nil
}

// GetSpecFilePath 返回指定 bundle 目录下的 spec 文件路径。
// OCI 规范定义 spec 文件名为 config.json,位于 bundle 目录中。
//
// 参数:
//   bundleDir string - bundle 目录路径
//
// 返回:
//   string - spec 文件完整路径: {bundleDir}/config.json
//
// 示例:
//   GetSpecFilePath("/tmp/bundle") → "/tmp/bundle/config.json"
//   GetSpecFilePath("") → "config.json"
//   GetSpecFilePath("/not/empty/") → "/not/empty/config.json"
func GetSpecFilePath(bundleDir string) string {
    specFilePath := filepath.Join(bundleDir, specFileName)
    return specFilePath
}

// IsBundleFlag 检查参数是否为 bundle 标志。
// 支持 --bundle, -bundle, --b, -b 格式。
//
// 参数:
//   arg string - 要检查的参数
//
// 返回:
//   bool - true 表示是 bundle 标志
//
// 逻辑:
// 1. 必须以 - 开头
// 2. 去掉所有前导 - 后,必须是 "b" 或 "bundle"
func IsBundleFlag(arg string) bool {
    if !strings.HasPrefix(arg, "-") {
        return false
    }
    trimmed := strings.TrimLeft(arg, "-")
    return trimmed == "b" || trimmed == "bundle"
}

// HasCreateSubcommand 检查参数中是否包含 "create" 子命令。
// 这是一个关键判断:只有 create 子命令需要修改 OCI spec。
//
// 特殊处理:
// - 检测 "--bundle create" 的情况,避免误判 bundle 路径为 "create" 子命令
// - 使用 previousWasBundle 标志跟踪前一个参数是否为 bundle 标志
//
// 参数:
//   args []string - 命令行参数
//
// 返回:
//   bool - true 表示包含 create 子命令
//
// 测试用例:
//   []                        → false
//   ["create"]                → true
//   ["--bundle=create"]       → false (create 是 bundle 路径)
//   ["--bundle", "create"]    → false (create 是 bundle 路径)
//   ["create"]                → true
func HasCreateSubcommand(args []string) bool {
    var previousWasBundle bool
    for _, a := range args {
        // 如果前一个参数是 bundle 标志,当前参数是 bundle 路径
        // 即使值为 "create",也不应视为子命令
        if !previousWasBundle && IsBundleFlag(a) {
            previousWasBundle = true
            continue
        }

        // 检查当前参数是否为 "create" 子命令
        // 如果前一个是 bundle 标志,当前是 bundle 路径,跳过
        if !previousWasBundle && a == "create" {
            return true
        }

        // 重置 bundle 标志
        previousWasBundle = false
    }

    return false
}

3.9 state.go 完整代码逐行注释

go 复制代码
// =============================================================================
// 文件: internal/oci/state.go
// 职责: State 结构体 --- OCI 容器状态
// 用于在容器生命周期 hook 中加载和解析容器状态
// =============================================================================

package oci

import (
    "encoding/json"   // JSON 解析
    "fmt"             // 格式化错误
    "io"              // I/O 接口
    "os"              // 文件操作
    "path/filepath"   // 路径处理

    "github.com/opencontainers/runtime-spec/specs-go"
)

// State 存储 OCI 容器状态。
// 它直接嵌入 specs.State,并添加了辅助方法。
//
// OCI 状态包含:
//   - ociVersion: OCI 规范版本
//   - id:         容器 ID
//   - status:     容器状态(created, running, stopped 等)
//   - pid:        容器进程 PID
//   - bundle:     bundle 目录路径
//   - annotations: 注解
type State specs.State

// LoadContainerState 从指定文件加载容器状态。
// 如果文件名为空或 "-",从 STDIN 读取。
//
// 参数:
//   filename string - 状态文件路径,空或 "-" 表示 STDIN
//
// 返回:
//   *State - 容器状态
//   error  - 错误信息
//
// 使用场景:
//   在 nvidia-container-toolkit hook (prestart) 中,
//   容器状态通过 STDIN 传入
func LoadContainerState(filename string) (*State, error) {
    if filename == "" || filename == "-" {
        // 从 STDIN 读取
        return ReadContainerState(os.Stdin)
    }

    // 从文件读取
    inputFile, err := os.Open(filename)
    if err != nil {
        return nil, fmt.Errorf("failed to open file: %v", err)
    }
    defer inputFile.Close()

    return ReadContainerState(inputFile)
}

// ReadContainerState 从指定的 reader 读取容器状态。
//
// 参数:
//   reader io.Reader - 数据源
//
// 返回:
//   *State - 容器状态
//   error  - 解析错误
func ReadContainerState(reader io.Reader) (*State, error) {
    var s State

    d := json.NewDecoder(reader)
    if err := d.Decode(&s); err != nil {
        return nil, fmt.Errorf("failed to decode container state: %v", err)
    }

    return &s, nil
}

// GetContainerRoot 返回容器的根文件系统路径。
// 从关联的 spec 中提取 root.path,如果是相对路径则拼接 bundle 目录。
//
// 处理流程:
// 1. 加载最小 spec(只解析 root 字段)
// 2. 获取 root.path
// 3. 如果是绝对路径,直接返回
// 4. 如果是相对路径,拼接 bundle + root
//
// 返回:
//   string - 容器根文件系统路径
//   error  - 错误信息
func (s *State) GetContainerRoot() (string, error) {
    // 加载最小 spec
    spec, err := s.loadMinimalSpec()
    if err != nil {
        return "", err
    }

    var containerRoot string
    if spec.Root != nil {
        containerRoot = spec.Root.Path
    }

    // 如果是绝对路径,直接返回
    if filepath.IsAbs(containerRoot) {
        return containerRoot, nil
    }

    // 相对路径:拼接 bundle 目录
    return filepath.Join(s.Bundle, containerRoot), nil
}

// loadMinimalSpec 加载精简版的 OCI spec。
// 只解析 root 字段,避免解码不需要的字段。
// 这是一种性能优化,特别是在容器生命周期 hook 中。
//
// 返回:
//   *minimalSpec - 精简 spec
//   error        - 错误信息
func (s *State) loadMinimalSpec() (*minimalSpec, error) {
    // 构造 spec 文件路径: {bundle}/config.json
    specFilePath := GetSpecFilePath(s.Bundle)
    specFile, err := os.Open(specFilePath)
    if err != nil {
        return nil, fmt.Errorf("failed to open OCI spec file: %v", err)
    }
    defer specFile.Close()

    // 解析为 minimalSpec
    ms := &minimalSpec{}
    if err := json.NewDecoder(specFile).Decode(ms); err != nil {
        return nil, fmt.Errorf("failed to load minimal OCI spec: %v", err)
    }
    return ms, nil
}

// minimalSpec 是精简版的 OCI spec,只包含需要的字段。
// 用于容器生命周期 hook 中避免解码完整 spec。
// 只解析 root 字段,其他字段被忽略。
type minimalSpec struct {
    // Root 配置容器的根文件系统
    Root *specs.Root `json:"root,omitempty"`
}

3.10 options.go 完整代码逐行注释

go 复制代码
// =============================================================================
// 文件: internal/oci/options.go
// 职责: Option 模式 --- 为 NewSpec 提供函数式配置选项
// =============================================================================

package oci

import "github.com/NVIDIA/nvidia-container-toolkit/internal/logger"

// options 是 NewSpec 的内部配置结构体
type options struct {
    logger            logger.Interface  // 日志记录器
    allowUnkownFields bool              // 是否允许 OCI spec 中的未知字段
    // 注意: 字段名有拼写错误 "allowUnkownFields"(应为 allowUnknownFields)
    // 但在代码中保持一致使用
}

// Option 是配置函数类型
type Option func(*options)

// WithLogger 设置日志记录器
func WithLogger(logger logger.Interface) Option {
    return func(o *options) {
        o.logger = logger
    }
}

// WithAllowUnknownFields 设置是否允许未知字段
// true: 允许(宽松模式)
// false: 不允许(严格模式,使用 DisallowUnknownFields)
func WithAllowUnknownFields(allowUnknownFields bool) Option {
    return func(o *options) {
        o.allowUnkownFields = allowUnknownFields
    }
}

3.11 runtime_syscall_exec.go 完整代码逐行注释

go 复制代码
// =============================================================================
// 文件: internal/oci/runtime_syscall_exec.go
// 职责: syscallExec --- 通过 syscall.Exec 实现进程替换
// 这是最终的执行层,将当前进程替换为低层运行时
// =============================================================================

package oci

import (
    "fmt"       // 格式化错误
    "os"        // 获取环境变量
    "syscall"   // 系统调用
)

// syscallExec 是通过 syscall.Exec 执行的 Runtime 实现。
// 它是一个空结构体(不持有任何状态),因为 syscall.Exec 只需要参数。
//
// 进程替换机制:
//   syscall.Exec(path, args, env) 是 Unix 系统调用 execve 的 Go 封装。
//   它用新程序替换当前进程的内存空间和执行上下文:
//   - PID 不变
//   - 文件描述符保持打开(除非设置了 FD_CLOEXEC)
//   - 环境变量被替换为传入的 env
//   - 命令行参数被替换为传入的 args
//   - 当前进程的代码和数据被新程序替换
//
//   重要: 如果成功,syscall.Exec 不会返回。
//   如果返回,说明出错了。
type syscallExec struct{}

// 确保 syscallExec 实现了 Runtime 接口
var _ Runtime = (*syscallExec)(nil)

// Exec 通过 syscall.Exec 执行指定程序。
//
// 参数:
//   args []string - 参数列表,args[0] 是要执行的程序路径
//
// 返回:
//   error - 如果 exec 失败或意外返回
//
// 安全注意:
//   //nolint:gosec 注释表示安全检查工具应忽略此行。
//   syscall.Exec 的参数来自配置和命令行,存在潜在的命令注入风险。
//   但在实际使用中,路径已通过 lookup 验证,参数来自受信任的来源。
//
// 执行流程:
// 1. syscall.Exec(args[0], args, os.Environ())
//    - args[0]: 程序路径(如 "/usr/bin/runc")
//    - args:    参数列表(如 ["/usr/bin/runc", "create", "--bundle", "/path"])
//    - os.Environ(): 当前进程的环境变量
// 2. 如果成功:当前进程被替换,此行不会执行
// 3. 如果失败:返回错误
func (r syscallExec) Exec(args []string) error {
    //nolint:gosec  // TODO: Can we harden this so that there is less risk of command injection
    err := syscall.Exec(args[0], args, os.Environ())
    if err != nil {
        return fmt.Errorf("could not exec '%v': %v", args[0], err)
    }

    // 如果 syscall.Exec 返回了(无论 err 是否为 nil),这是意外状态
    // 成功的 exec 不会返回,因为进程已被替换
    return fmt.Errorf("unexpected return from exec '%v'", args[0])
}

// String 返回 "exec" 作为标识
func (r syscallExec) String() string {
    return "exec"
}

四、Mermaid 图表

图1: OCI 模块类图(接口 + 实现)

#mermaid-svg-akwlpl4f4ZTLk6ky{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-akwlpl4f4ZTLk6ky .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-akwlpl4f4ZTLk6ky .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-akwlpl4f4ZTLk6ky .error-icon{fill:#552222;}#mermaid-svg-akwlpl4f4ZTLk6ky .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-akwlpl4f4ZTLk6ky .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-akwlpl4f4ZTLk6ky .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-akwlpl4f4ZTLk6ky .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-akwlpl4f4ZTLk6ky .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-akwlpl4f4ZTLk6ky .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-akwlpl4f4ZTLk6ky .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-akwlpl4f4ZTLk6ky .marker{fill:#333333;stroke:#333333;}#mermaid-svg-akwlpl4f4ZTLk6ky .marker.cross{stroke:#333333;}#mermaid-svg-akwlpl4f4ZTLk6ky svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-akwlpl4f4ZTLk6ky p{margin:0;}#mermaid-svg-akwlpl4f4ZTLk6ky g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-akwlpl4f4ZTLk6ky g.classGroup text .title{font-weight:bolder;}#mermaid-svg-akwlpl4f4ZTLk6ky .cluster-label text{fill:#333;}#mermaid-svg-akwlpl4f4ZTLk6ky .cluster-label span{color:#333;}#mermaid-svg-akwlpl4f4ZTLk6ky .cluster-label span p{background-color:transparent;}#mermaid-svg-akwlpl4f4ZTLk6ky .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-akwlpl4f4ZTLk6ky .cluster text{fill:#333;}#mermaid-svg-akwlpl4f4ZTLk6ky .cluster span{color:#333;}#mermaid-svg-akwlpl4f4ZTLk6ky .nodeLabel,#mermaid-svg-akwlpl4f4ZTLk6ky .edgeLabel{color:#131300;}#mermaid-svg-akwlpl4f4ZTLk6ky .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-akwlpl4f4ZTLk6ky .label text{fill:#131300;}#mermaid-svg-akwlpl4f4ZTLk6ky .labelBkg{background:#ECECFF;}#mermaid-svg-akwlpl4f4ZTLk6ky .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-akwlpl4f4ZTLk6ky .classTitle{font-weight:bolder;}#mermaid-svg-akwlpl4f4ZTLk6ky .node rect,#mermaid-svg-akwlpl4f4ZTLk6ky .node circle,#mermaid-svg-akwlpl4f4ZTLk6ky .node ellipse,#mermaid-svg-akwlpl4f4ZTLk6ky .node polygon,#mermaid-svg-akwlpl4f4ZTLk6ky .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-akwlpl4f4ZTLk6ky .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-akwlpl4f4ZTLk6ky g.clickable{cursor:pointer;}#mermaid-svg-akwlpl4f4ZTLk6ky g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-akwlpl4f4ZTLk6ky g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-akwlpl4f4ZTLk6ky .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-akwlpl4f4ZTLk6ky .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-akwlpl4f4ZTLk6ky .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-akwlpl4f4ZTLk6ky .dashed-line{stroke-dasharray:3;}#mermaid-svg-akwlpl4f4ZTLk6ky .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-akwlpl4f4ZTLk6ky #compositionStart,#mermaid-svg-akwlpl4f4ZTLk6ky .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-akwlpl4f4ZTLk6ky #compositionEnd,#mermaid-svg-akwlpl4f4ZTLk6ky .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-akwlpl4f4ZTLk6ky #dependencyStart,#mermaid-svg-akwlpl4f4ZTLk6ky .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-akwlpl4f4ZTLk6ky #dependencyStart,#mermaid-svg-akwlpl4f4ZTLk6ky .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-akwlpl4f4ZTLk6ky #extensionStart,#mermaid-svg-akwlpl4f4ZTLk6ky .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-akwlpl4f4ZTLk6ky #extensionEnd,#mermaid-svg-akwlpl4f4ZTLk6ky .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-akwlpl4f4ZTLk6ky #aggregationStart,#mermaid-svg-akwlpl4f4ZTLk6ky .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-akwlpl4f4ZTLk6ky #aggregationEnd,#mermaid-svg-akwlpl4f4ZTLk6ky .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-akwlpl4f4ZTLk6ky #lollipopStart,#mermaid-svg-akwlpl4f4ZTLk6ky .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-akwlpl4f4ZTLk6ky #lollipopEnd,#mermaid-svg-akwlpl4f4ZTLk6ky .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-akwlpl4f4ZTLk6ky .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-akwlpl4f4ZTLk6ky .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-akwlpl4f4ZTLk6ky .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-akwlpl4f4ZTLk6ky .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-akwlpl4f4ZTLk6ky :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 实现
实现
实现
实现
实现
实现
嵌入
持有
持有
持有
持有
<<interface>>
Spec
+Load()(*Spec, error)
+Flush() : error
+Modify(SpecModifier) : error
+LookupEnv(key string)(string, bool)
<<interface>>
Runtime
+Exec(args \[\]string) : error
+String() : string
<<interface>>
SpecModifier
+Modify(*Spec) : error
fileSpec
-memorySpec
-path string
-loader loader
+NewFileSpec(filepath, isStrict) : Spec
+Load()(*Spec, error)
+Flush() : error
memorySpec
+Spec *specs.Spec
+NewMemorySpec(spec) : Spec
+Load()(*Spec, error)
+Flush() : error
+Modify(SpecModifier) : error
+LookupEnv(key)(string, bool)
pathRuntime
-logger logger.Interface
-path string
-execRuntime Runtime
+NewRuntimeForPath(logger, path)(Runtime, error)
+Exec(args \[\]string) : error
+String() : string
modifyingRuntimeWrapper
-logger logger.Interface
-runtime Runtime
-ociSpec Spec
-modifier SpecModifier
+NewModifyingRuntimeWrapper(logger, runtime, spec, modifier) : Runtime
+Exec(args \[\]string) : error
+modify() : error
+String() : string
syscallExec
+Exec(args \[\]string) : error
+String() : string
<<type SpecModifier>>
SpecModifiers
+Modify(*Spec) : error
State
+State specs.State
+LoadContainerState(filename)(*State, error)
+GetContainerRoot()(string, error)

图2: fileSpec 生命周期图

#mermaid-svg-UIwsOPJGwYQ3XiM1{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-UIwsOPJGwYQ3XiM1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .error-icon{fill:#552222;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .marker.cross{stroke:#333333;}#mermaid-svg-UIwsOPJGwYQ3XiM1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UIwsOPJGwYQ3XiM1 p{margin:0;}#mermaid-svg-UIwsOPJGwYQ3XiM1 defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-UIwsOPJGwYQ3XiM1 g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-UIwsOPJGwYQ3XiM1 g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-UIwsOPJGwYQ3XiM1 g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-UIwsOPJGwYQ3XiM1 g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-UIwsOPJGwYQ3XiM1 g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-UIwsOPJGwYQ3XiM1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UIwsOPJGwYQ3XiM1 .edgeLabel .label text{fill:#333;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .label div .edgeLabel{color:#333;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UIwsOPJGwYQ3XiM1 #statediagram-barbEnd{fill:#333333;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .cluster-label,#mermaid-svg-UIwsOPJGwYQ3XiM1 .nodeLabel{color:#131300;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .note-edge{stroke-dasharray:5;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .statediagram-note text{fill:black;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .statediagram-note .nodeLabel{color:black;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .statediagram .edgeLabel{color:red;}#mermaid-svg-UIwsOPJGwYQ3XiM1 #dependencyStart,#mermaid-svg-UIwsOPJGwYQ3XiM1 #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-UIwsOPJGwYQ3XiM1 .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-UIwsOPJGwYQ3XiM1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} NewFileSpec(path, isStrict)
Load()
Modify(modifier)
Flush()
Load() 失败
Modify() 失败
Flush() 失败
Created
Loaded
s.Spec 被填充

JSON 从文件解析
Modified
spec 被修改器修改

仅内存中的改变
Flushed
修改写入文件

config.json 被覆盖
Error

图3: modifyingRuntimeWrapper 时序图

syscallExec pathRuntime SpecModifier fileSpec modifyingRuntimeWrapper 调用者 syscallExec pathRuntime SpecModifier fileSpec modifyingRuntimeWrapper 调用者 #mermaid-svg-j3aZpbGBQH3srt2t{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-j3aZpbGBQH3srt2t .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-j3aZpbGBQH3srt2t .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-j3aZpbGBQH3srt2t .error-icon{fill:#552222;}#mermaid-svg-j3aZpbGBQH3srt2t .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-j3aZpbGBQH3srt2t .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-j3aZpbGBQH3srt2t .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-j3aZpbGBQH3srt2t .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-j3aZpbGBQH3srt2t .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-j3aZpbGBQH3srt2t .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-j3aZpbGBQH3srt2t .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-j3aZpbGBQH3srt2t .marker{fill:#333333;stroke:#333333;}#mermaid-svg-j3aZpbGBQH3srt2t .marker.cross{stroke:#333333;}#mermaid-svg-j3aZpbGBQH3srt2t svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-j3aZpbGBQH3srt2t p{margin:0;}#mermaid-svg-j3aZpbGBQH3srt2t .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-j3aZpbGBQH3srt2t text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-j3aZpbGBQH3srt2t .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-j3aZpbGBQH3srt2t .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-j3aZpbGBQH3srt2t .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-j3aZpbGBQH3srt2t .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-j3aZpbGBQH3srt2t #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-j3aZpbGBQH3srt2t .sequenceNumber{fill:white;}#mermaid-svg-j3aZpbGBQH3srt2t #sequencenumber{fill:#333;}#mermaid-svg-j3aZpbGBQH3srt2t #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-j3aZpbGBQH3srt2t .messageText{fill:#333;stroke:none;}#mermaid-svg-j3aZpbGBQH3srt2t .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-j3aZpbGBQH3srt2t .labelText,#mermaid-svg-j3aZpbGBQH3srt2t .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-j3aZpbGBQH3srt2t .loopText,#mermaid-svg-j3aZpbGBQH3srt2t .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-j3aZpbGBQH3srt2t .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-j3aZpbGBQH3srt2t .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-j3aZpbGBQH3srt2t .noteText,#mermaid-svg-j3aZpbGBQH3srt2t .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-j3aZpbGBQH3srt2t .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-j3aZpbGBQH3srt2t .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-j3aZpbGBQH3srt2t .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-j3aZpbGBQH3srt2t .actorPopupMenu{position:absolute;}#mermaid-svg-j3aZpbGBQH3srt2t .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-j3aZpbGBQH3srt2t .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-j3aZpbGBQH3srt2t .actor-man circle,#mermaid-svg-j3aZpbGBQH3srt2t line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-j3aZpbGBQH3srt2t :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 进程替换,不返回 进程替换,不返回 alt非 create 子命令create 子命令 Exec(args)HasCreateSubcommand(args)Exec(args)Exec(path, args\[1:])syscall.Exec(path, args, environ)modify()Load()os.Open(path)json.Decode(&spec)*specs.SpecModify(modifier)Modify(spec)修改 spec (注入hook/设备)nil/errornil/errorFlush()os.Create(path)json.Encode(spec)nil/errorExec(args)Exec(path, args\[1:])syscall.Exec(path, args, environ)

图4: LowLevelRuntime 查找流程图

#mermaid-svg-2V3Urx0P5ubgzKYi{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-2V3Urx0P5ubgzKYi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2V3Urx0P5ubgzKYi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2V3Urx0P5ubgzKYi .error-icon{fill:#552222;}#mermaid-svg-2V3Urx0P5ubgzKYi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2V3Urx0P5ubgzKYi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2V3Urx0P5ubgzKYi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2V3Urx0P5ubgzKYi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2V3Urx0P5ubgzKYi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2V3Urx0P5ubgzKYi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2V3Urx0P5ubgzKYi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2V3Urx0P5ubgzKYi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2V3Urx0P5ubgzKYi .marker.cross{stroke:#333333;}#mermaid-svg-2V3Urx0P5ubgzKYi svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2V3Urx0P5ubgzKYi p{margin:0;}#mermaid-svg-2V3Urx0P5ubgzKYi .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-2V3Urx0P5ubgzKYi .cluster-label text{fill:#333;}#mermaid-svg-2V3Urx0P5ubgzKYi .cluster-label span{color:#333;}#mermaid-svg-2V3Urx0P5ubgzKYi .cluster-label span p{background-color:transparent;}#mermaid-svg-2V3Urx0P5ubgzKYi .label text,#mermaid-svg-2V3Urx0P5ubgzKYi span{fill:#333;color:#333;}#mermaid-svg-2V3Urx0P5ubgzKYi .node rect,#mermaid-svg-2V3Urx0P5ubgzKYi .node circle,#mermaid-svg-2V3Urx0P5ubgzKYi .node ellipse,#mermaid-svg-2V3Urx0P5ubgzKYi .node polygon,#mermaid-svg-2V3Urx0P5ubgzKYi .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2V3Urx0P5ubgzKYi .rough-node .label text,#mermaid-svg-2V3Urx0P5ubgzKYi .node .label text,#mermaid-svg-2V3Urx0P5ubgzKYi .image-shape .label,#mermaid-svg-2V3Urx0P5ubgzKYi .icon-shape .label{text-anchor:middle;}#mermaid-svg-2V3Urx0P5ubgzKYi .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2V3Urx0P5ubgzKYi .rough-node .label,#mermaid-svg-2V3Urx0P5ubgzKYi .node .label,#mermaid-svg-2V3Urx0P5ubgzKYi .image-shape .label,#mermaid-svg-2V3Urx0P5ubgzKYi .icon-shape .label{text-align:center;}#mermaid-svg-2V3Urx0P5ubgzKYi .node.clickable{cursor:pointer;}#mermaid-svg-2V3Urx0P5ubgzKYi .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2V3Urx0P5ubgzKYi .arrowheadPath{fill:#333333;}#mermaid-svg-2V3Urx0P5ubgzKYi .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2V3Urx0P5ubgzKYi .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2V3Urx0P5ubgzKYi .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2V3Urx0P5ubgzKYi .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2V3Urx0P5ubgzKYi .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2V3Urx0P5ubgzKYi .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2V3Urx0P5ubgzKYi .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2V3Urx0P5ubgzKYi .cluster text{fill:#333;}#mermaid-svg-2V3Urx0P5ubgzKYi .cluster span{color:#333;}#mermaid-svg-2V3Urx0P5ubgzKYi 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-2V3Urx0P5ubgzKYi .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2V3Urx0P5ubgzKYi rect.text{fill:none;stroke-width:0;}#mermaid-svg-2V3Urx0P5ubgzKYi .icon-shape,#mermaid-svg-2V3Urx0P5ubgzKYi .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2V3Urx0P5ubgzKYi .icon-shape p,#mermaid-svg-2V3Urx0P5ubgzKYi .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2V3Urx0P5ubgzKYi .icon-shape .label rect,#mermaid-svg-2V3Urx0P5ubgzKYi .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2V3Urx0P5ubgzKYi .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2V3Urx0P5ubgzKYi .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2V3Urx0P5ubgzKYi :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是







NewLowLevelRuntime

(logger, candidates)
findRuntime

(logger, candidates)
candidates 为空?
返回错误:

at least one runtime

candidate must be specified
创建 ExecutableLocator

根路径 = /
遍历 candidates
locator.Locate(candidate)

在 PATH 中搜索
找到?
返回 targets0

第一个匹配的路径
还有候选?
返回错误:

no runtime binary found
NewRuntimeForPath

(logger, path)
os.Stat(path)

验证路径
路径有效?
返回错误:

not an executable file
创建 pathRuntime

{path, syscallExec{}}
返回 pathRuntime

图5: Spec Load → Modify → Flush 流程图

#mermaid-svg-GbjjfjwuAApFD2OB{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-GbjjfjwuAApFD2OB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-GbjjfjwuAApFD2OB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-GbjjfjwuAApFD2OB .error-icon{fill:#552222;}#mermaid-svg-GbjjfjwuAApFD2OB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-GbjjfjwuAApFD2OB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-GbjjfjwuAApFD2OB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-GbjjfjwuAApFD2OB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-GbjjfjwuAApFD2OB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-GbjjfjwuAApFD2OB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-GbjjfjwuAApFD2OB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-GbjjfjwuAApFD2OB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-GbjjfjwuAApFD2OB .marker.cross{stroke:#333333;}#mermaid-svg-GbjjfjwuAApFD2OB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-GbjjfjwuAApFD2OB p{margin:0;}#mermaid-svg-GbjjfjwuAApFD2OB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-GbjjfjwuAApFD2OB .cluster-label text{fill:#333;}#mermaid-svg-GbjjfjwuAApFD2OB .cluster-label span{color:#333;}#mermaid-svg-GbjjfjwuAApFD2OB .cluster-label span p{background-color:transparent;}#mermaid-svg-GbjjfjwuAApFD2OB .label text,#mermaid-svg-GbjjfjwuAApFD2OB span{fill:#333;color:#333;}#mermaid-svg-GbjjfjwuAApFD2OB .node rect,#mermaid-svg-GbjjfjwuAApFD2OB .node circle,#mermaid-svg-GbjjfjwuAApFD2OB .node ellipse,#mermaid-svg-GbjjfjwuAApFD2OB .node polygon,#mermaid-svg-GbjjfjwuAApFD2OB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-GbjjfjwuAApFD2OB .rough-node .label text,#mermaid-svg-GbjjfjwuAApFD2OB .node .label text,#mermaid-svg-GbjjfjwuAApFD2OB .image-shape .label,#mermaid-svg-GbjjfjwuAApFD2OB .icon-shape .label{text-anchor:middle;}#mermaid-svg-GbjjfjwuAApFD2OB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-GbjjfjwuAApFD2OB .rough-node .label,#mermaid-svg-GbjjfjwuAApFD2OB .node .label,#mermaid-svg-GbjjfjwuAApFD2OB .image-shape .label,#mermaid-svg-GbjjfjwuAApFD2OB .icon-shape .label{text-align:center;}#mermaid-svg-GbjjfjwuAApFD2OB .node.clickable{cursor:pointer;}#mermaid-svg-GbjjfjwuAApFD2OB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-GbjjfjwuAApFD2OB .arrowheadPath{fill:#333333;}#mermaid-svg-GbjjfjwuAApFD2OB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-GbjjfjwuAApFD2OB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-GbjjfjwuAApFD2OB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GbjjfjwuAApFD2OB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-GbjjfjwuAApFD2OB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GbjjfjwuAApFD2OB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-GbjjfjwuAApFD2OB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-GbjjfjwuAApFD2OB .cluster text{fill:#333;}#mermaid-svg-GbjjfjwuAApFD2OB .cluster span{color:#333;}#mermaid-svg-GbjjfjwuAApFD2OB 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-GbjjfjwuAApFD2OB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-GbjjfjwuAApFD2OB rect.text{fill:none;stroke-width:0;}#mermaid-svg-GbjjfjwuAApFD2OB .icon-shape,#mermaid-svg-GbjjfjwuAApFD2OB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GbjjfjwuAApFD2OB .icon-shape p,#mermaid-svg-GbjjfjwuAApFD2OB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-GbjjfjwuAApFD2OB .icon-shape .label rect,#mermaid-svg-GbjjfjwuAApFD2OB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GbjjfjwuAApFD2OB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-GbjjfjwuAApFD2OB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-GbjjfjwuAApFD2OB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否















初始状态

fileSpec.Spec = nil
Load()
os.Open(s.path)

只读打开 config.json
打开成功?
返回错误:

error opening OCI

specification file
json.NewDecoder(file)
严格模式?
decoder.DisallowUnknownFields()
decoder.Decode(&spec)

宽松解析
解析成功?
返回错误:

error reading

OCI specification
s.Spec = spec

存储解析结果
返回 *specs.Spec
Modify(modifier)
s.Spec == nil?
返回错误:

cannot modify nil spec
modifier.Modify(s.Spec)

就地修改 spec
修改成功?
返回修改器错误
返回 nil
Flush()
s.Spec == nil?
返回错误:

no OCI specification loaded
os.Create(s.path)

截断打开文件
创建成功?
返回错误:

error opening OCI

specification file
json.NewEncoder(file)
encoder.Encode(spec)

写入 JSON
编码成功?
返回错误:

error writing

OCI specification
返回 nil

spec 已写入文件

图6: parseArgs(HasCreateSubcommand)流程图

#mermaid-svg-96Xa6ML4TFOH6pTd{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-96Xa6ML4TFOH6pTd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-96Xa6ML4TFOH6pTd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-96Xa6ML4TFOH6pTd .error-icon{fill:#552222;}#mermaid-svg-96Xa6ML4TFOH6pTd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-96Xa6ML4TFOH6pTd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-96Xa6ML4TFOH6pTd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-96Xa6ML4TFOH6pTd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-96Xa6ML4TFOH6pTd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-96Xa6ML4TFOH6pTd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-96Xa6ML4TFOH6pTd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-96Xa6ML4TFOH6pTd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-96Xa6ML4TFOH6pTd .marker.cross{stroke:#333333;}#mermaid-svg-96Xa6ML4TFOH6pTd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-96Xa6ML4TFOH6pTd p{margin:0;}#mermaid-svg-96Xa6ML4TFOH6pTd .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-96Xa6ML4TFOH6pTd .cluster-label text{fill:#333;}#mermaid-svg-96Xa6ML4TFOH6pTd .cluster-label span{color:#333;}#mermaid-svg-96Xa6ML4TFOH6pTd .cluster-label span p{background-color:transparent;}#mermaid-svg-96Xa6ML4TFOH6pTd .label text,#mermaid-svg-96Xa6ML4TFOH6pTd span{fill:#333;color:#333;}#mermaid-svg-96Xa6ML4TFOH6pTd .node rect,#mermaid-svg-96Xa6ML4TFOH6pTd .node circle,#mermaid-svg-96Xa6ML4TFOH6pTd .node ellipse,#mermaid-svg-96Xa6ML4TFOH6pTd .node polygon,#mermaid-svg-96Xa6ML4TFOH6pTd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-96Xa6ML4TFOH6pTd .rough-node .label text,#mermaid-svg-96Xa6ML4TFOH6pTd .node .label text,#mermaid-svg-96Xa6ML4TFOH6pTd .image-shape .label,#mermaid-svg-96Xa6ML4TFOH6pTd .icon-shape .label{text-anchor:middle;}#mermaid-svg-96Xa6ML4TFOH6pTd .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-96Xa6ML4TFOH6pTd .rough-node .label,#mermaid-svg-96Xa6ML4TFOH6pTd .node .label,#mermaid-svg-96Xa6ML4TFOH6pTd .image-shape .label,#mermaid-svg-96Xa6ML4TFOH6pTd .icon-shape .label{text-align:center;}#mermaid-svg-96Xa6ML4TFOH6pTd .node.clickable{cursor:pointer;}#mermaid-svg-96Xa6ML4TFOH6pTd .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-96Xa6ML4TFOH6pTd .arrowheadPath{fill:#333333;}#mermaid-svg-96Xa6ML4TFOH6pTd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-96Xa6ML4TFOH6pTd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-96Xa6ML4TFOH6pTd .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-96Xa6ML4TFOH6pTd .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-96Xa6ML4TFOH6pTd .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-96Xa6ML4TFOH6pTd .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-96Xa6ML4TFOH6pTd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-96Xa6ML4TFOH6pTd .cluster text{fill:#333;}#mermaid-svg-96Xa6ML4TFOH6pTd .cluster span{color:#333;}#mermaid-svg-96Xa6ML4TFOH6pTd 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-96Xa6ML4TFOH6pTd .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-96Xa6ML4TFOH6pTd rect.text{fill:none;stroke-width:0;}#mermaid-svg-96Xa6ML4TFOH6pTd .icon-shape,#mermaid-svg-96Xa6ML4TFOH6pTd .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-96Xa6ML4TFOH6pTd .icon-shape p,#mermaid-svg-96Xa6ML4TFOH6pTd .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-96Xa6ML4TFOH6pTd .icon-shape .label rect,#mermaid-svg-96Xa6ML4TFOH6pTd .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-96Xa6ML4TFOH6pTd .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-96Xa6ML4TFOH6pTd .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-96Xa6ML4TFOH6pTd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是





GetBundleDirFromArgs 流程








GetBundleDirFromArgs(args)
遍历 args
IsBundleFlag

(parts0)?
有 '=' 分隔?

len(parts)==2
bundleDir = parts1
有下一个参数?
bundleDir = argsi+1

i++
错误:

bundle option requires

an argument
还有参数?
返回 bundleDir
HasCreateSubcommand(args)
初始化

previousWasBundle = false
遍历 args
previousWasBundle

== false 且

IsBundleFlag(a)?
previousWasBundle = true

跳过此参数
previousWasBundle

== false 且

a == 'create'?
返回 true
previousWasBundle = false
还有参数?
返回 false

图7: syscall.Exec 进程替换图

#mermaid-svg-kL0N0rblcnpfHftg{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-kL0N0rblcnpfHftg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kL0N0rblcnpfHftg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kL0N0rblcnpfHftg .error-icon{fill:#552222;}#mermaid-svg-kL0N0rblcnpfHftg .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kL0N0rblcnpfHftg .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kL0N0rblcnpfHftg .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kL0N0rblcnpfHftg .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kL0N0rblcnpfHftg .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kL0N0rblcnpfHftg .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kL0N0rblcnpfHftg .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kL0N0rblcnpfHftg .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kL0N0rblcnpfHftg .marker.cross{stroke:#333333;}#mermaid-svg-kL0N0rblcnpfHftg svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kL0N0rblcnpfHftg p{margin:0;}#mermaid-svg-kL0N0rblcnpfHftg .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-kL0N0rblcnpfHftg .cluster-label text{fill:#333;}#mermaid-svg-kL0N0rblcnpfHftg .cluster-label span{color:#333;}#mermaid-svg-kL0N0rblcnpfHftg .cluster-label span p{background-color:transparent;}#mermaid-svg-kL0N0rblcnpfHftg .label text,#mermaid-svg-kL0N0rblcnpfHftg span{fill:#333;color:#333;}#mermaid-svg-kL0N0rblcnpfHftg .node rect,#mermaid-svg-kL0N0rblcnpfHftg .node circle,#mermaid-svg-kL0N0rblcnpfHftg .node ellipse,#mermaid-svg-kL0N0rblcnpfHftg .node polygon,#mermaid-svg-kL0N0rblcnpfHftg .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kL0N0rblcnpfHftg .rough-node .label text,#mermaid-svg-kL0N0rblcnpfHftg .node .label text,#mermaid-svg-kL0N0rblcnpfHftg .image-shape .label,#mermaid-svg-kL0N0rblcnpfHftg .icon-shape .label{text-anchor:middle;}#mermaid-svg-kL0N0rblcnpfHftg .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-kL0N0rblcnpfHftg .rough-node .label,#mermaid-svg-kL0N0rblcnpfHftg .node .label,#mermaid-svg-kL0N0rblcnpfHftg .image-shape .label,#mermaid-svg-kL0N0rblcnpfHftg .icon-shape .label{text-align:center;}#mermaid-svg-kL0N0rblcnpfHftg .node.clickable{cursor:pointer;}#mermaid-svg-kL0N0rblcnpfHftg .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-kL0N0rblcnpfHftg .arrowheadPath{fill:#333333;}#mermaid-svg-kL0N0rblcnpfHftg .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-kL0N0rblcnpfHftg .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-kL0N0rblcnpfHftg .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kL0N0rblcnpfHftg .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-kL0N0rblcnpfHftg .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kL0N0rblcnpfHftg .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-kL0N0rblcnpfHftg .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-kL0N0rblcnpfHftg .cluster text{fill:#333;}#mermaid-svg-kL0N0rblcnpfHftg .cluster span{color:#333;}#mermaid-svg-kL0N0rblcnpfHftg 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-kL0N0rblcnpfHftg .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-kL0N0rblcnpfHftg rect.text{fill:none;stroke-width:0;}#mermaid-svg-kL0N0rblcnpfHftg .icon-shape,#mermaid-svg-kL0N0rblcnpfHftg .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kL0N0rblcnpfHftg .icon-shape p,#mermaid-svg-kL0N0rblcnpfHftg .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-kL0N0rblcnpfHftg .icon-shape .label rect,#mermaid-svg-kL0N0rblcnpfHftg .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kL0N0rblcnpfHftg .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-kL0N0rblcnpfHftg .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-kL0N0rblcnpfHftg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 进程替换后
syscall.Exec 调用
进程替换前
nvidia-container-runtime 进程
PID: 12345
内存: runtime 代码
文件描述符: 打开的状态
环境变量: 当前进程的 env
命令行: nvidia-container-runtime

--bundle /path create
syscall.Exec(

'/usr/bin/runc',

'/usr/bin/runc', 'create', ...,

os.Environ()

)
runc 进程
PID: 12345 (不变!)
内存: runc 代码
文件描述符: 继承
环境变量: 传入的 env
命令行: /usr/bin/runc

create --bundle /path
关键特性:

  1. PID 不变

  2. 进程的内存被替换

  3. 不会返回到调用者

  4. 文件描述符继承

  5. 环境变量可控

图8: OCI 与 Runtime 交互时序图

oci模块 newNVIDIAContainerRuntime runtime模块 oci模块 newNVIDIAContainerRuntime runtime模块 #mermaid-svg-WRREHdqUx1395eGb{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-WRREHdqUx1395eGb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WRREHdqUx1395eGb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WRREHdqUx1395eGb .error-icon{fill:#552222;}#mermaid-svg-WRREHdqUx1395eGb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WRREHdqUx1395eGb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WRREHdqUx1395eGb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WRREHdqUx1395eGb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WRREHdqUx1395eGb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WRREHdqUx1395eGb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WRREHdqUx1395eGb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WRREHdqUx1395eGb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WRREHdqUx1395eGb .marker.cross{stroke:#333333;}#mermaid-svg-WRREHdqUx1395eGb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WRREHdqUx1395eGb p{margin:0;}#mermaid-svg-WRREHdqUx1395eGb .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-WRREHdqUx1395eGb text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-WRREHdqUx1395eGb .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-WRREHdqUx1395eGb .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-WRREHdqUx1395eGb .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-WRREHdqUx1395eGb .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-WRREHdqUx1395eGb #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-WRREHdqUx1395eGb .sequenceNumber{fill:white;}#mermaid-svg-WRREHdqUx1395eGb #sequencenumber{fill:#333;}#mermaid-svg-WRREHdqUx1395eGb #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-WRREHdqUx1395eGb .messageText{fill:#333;stroke:none;}#mermaid-svg-WRREHdqUx1395eGb .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-WRREHdqUx1395eGb .labelText,#mermaid-svg-WRREHdqUx1395eGb .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-WRREHdqUx1395eGb .loopText,#mermaid-svg-WRREHdqUx1395eGb .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-WRREHdqUx1395eGb .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-WRREHdqUx1395eGb .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-WRREHdqUx1395eGb .noteText,#mermaid-svg-WRREHdqUx1395eGb .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-WRREHdqUx1395eGb .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-WRREHdqUx1395eGb .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-WRREHdqUx1395eGb .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-WRREHdqUx1395eGb .actorPopupMenu{position:absolute;}#mermaid-svg-WRREHdqUx1395eGb .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-WRREHdqUx1395eGb .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-WRREHdqUx1395eGb .actor-man circle,#mermaid-svg-WRREHdqUx1395eGb line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-WRREHdqUx1395eGb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 进程替换为 runc newNVIDIAContainerRuntime(logger, driver, cfg, argv)NewLowLevelRuntime(logger, "runc","crun")findRuntime → 搜索PATHNewRuntimeForPath → 验证+创建pathRuntimepathRuntimeHasCreateSubcommand(argv)trueNewSpec(argv, WithLogger, WithAllowUnknownFields)GetBundleDir(argv) → "/tmp/bundle"GetSpecFilePath → "/tmp/bundle/config.json"NewFileSpec(path, isStrict)fileSpecnewSpecModifier(logger, driver, cfg, ociSpec)specModifierNewModifyingRuntimeWrapper(logger, pathRuntime, fileSpec, specModifier)modifyingRuntimeWrapperRuntimeruntime.Exec(argv)HasCreateSubcommand → truemodify(): Load → Modify → FlushpathRuntime.Exec(argv)syscall.Exec(runc, args, environ)

图9: OCI 与 Modifier 交互图

#mermaid-svg-rlTBvb6CPYI6prTS{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-rlTBvb6CPYI6prTS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rlTBvb6CPYI6prTS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rlTBvb6CPYI6prTS .error-icon{fill:#552222;}#mermaid-svg-rlTBvb6CPYI6prTS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rlTBvb6CPYI6prTS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rlTBvb6CPYI6prTS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rlTBvb6CPYI6prTS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rlTBvb6CPYI6prTS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rlTBvb6CPYI6prTS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rlTBvb6CPYI6prTS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rlTBvb6CPYI6prTS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rlTBvb6CPYI6prTS .marker.cross{stroke:#333333;}#mermaid-svg-rlTBvb6CPYI6prTS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rlTBvb6CPYI6prTS p{margin:0;}#mermaid-svg-rlTBvb6CPYI6prTS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-rlTBvb6CPYI6prTS .cluster-label text{fill:#333;}#mermaid-svg-rlTBvb6CPYI6prTS .cluster-label span{color:#333;}#mermaid-svg-rlTBvb6CPYI6prTS .cluster-label span p{background-color:transparent;}#mermaid-svg-rlTBvb6CPYI6prTS .label text,#mermaid-svg-rlTBvb6CPYI6prTS span{fill:#333;color:#333;}#mermaid-svg-rlTBvb6CPYI6prTS .node rect,#mermaid-svg-rlTBvb6CPYI6prTS .node circle,#mermaid-svg-rlTBvb6CPYI6prTS .node ellipse,#mermaid-svg-rlTBvb6CPYI6prTS .node polygon,#mermaid-svg-rlTBvb6CPYI6prTS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rlTBvb6CPYI6prTS .rough-node .label text,#mermaid-svg-rlTBvb6CPYI6prTS .node .label text,#mermaid-svg-rlTBvb6CPYI6prTS .image-shape .label,#mermaid-svg-rlTBvb6CPYI6prTS .icon-shape .label{text-anchor:middle;}#mermaid-svg-rlTBvb6CPYI6prTS .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-rlTBvb6CPYI6prTS .rough-node .label,#mermaid-svg-rlTBvb6CPYI6prTS .node .label,#mermaid-svg-rlTBvb6CPYI6prTS .image-shape .label,#mermaid-svg-rlTBvb6CPYI6prTS .icon-shape .label{text-align:center;}#mermaid-svg-rlTBvb6CPYI6prTS .node.clickable{cursor:pointer;}#mermaid-svg-rlTBvb6CPYI6prTS .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-rlTBvb6CPYI6prTS .arrowheadPath{fill:#333333;}#mermaid-svg-rlTBvb6CPYI6prTS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-rlTBvb6CPYI6prTS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-rlTBvb6CPYI6prTS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rlTBvb6CPYI6prTS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rlTBvb6CPYI6prTS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rlTBvb6CPYI6prTS .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-rlTBvb6CPYI6prTS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-rlTBvb6CPYI6prTS .cluster text{fill:#333;}#mermaid-svg-rlTBvb6CPYI6prTS .cluster span{color:#333;}#mermaid-svg-rlTBvb6CPYI6prTS 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-rlTBvb6CPYI6prTS .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-rlTBvb6CPYI6prTS rect.text{fill:none;stroke-width:0;}#mermaid-svg-rlTBvb6CPYI6prTS .icon-shape,#mermaid-svg-rlTBvb6CPYI6prTS .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rlTBvb6CPYI6prTS .icon-shape p,#mermaid-svg-rlTBvb6CPYI6prTS .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-rlTBvb6CPYI6prTS .icon-shape .label rect,#mermaid-svg-rlTBvb6CPYI6prTS .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rlTBvb6CPYI6prTS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-rlTBvb6CPYI6prTS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-rlTBvb6CPYI6prTS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Modifier 模块
OCI 模块
modifyingRuntimeWrapper.modify()
fileSpec.Load()
memorySpec.Modify(modifier)
fileSpec.Flush()
SpecModifier.Modify(spec)
stableRuntimeModifier

注入 prestart hook
csvModifier

基于 CSV 生成 CDI spec
cdiModifier

注入 CDI 设备
hookRemover

移除旧 hook
graphicsModifier

注入图形库
discoverModifier

基于发现器注入
list

遍历多个修改器

图10: 模块依赖图

#mermaid-svg-fLSnRPu4XoE1OcBE{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-fLSnRPu4XoE1OcBE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fLSnRPu4XoE1OcBE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fLSnRPu4XoE1OcBE .error-icon{fill:#552222;}#mermaid-svg-fLSnRPu4XoE1OcBE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fLSnRPu4XoE1OcBE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fLSnRPu4XoE1OcBE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fLSnRPu4XoE1OcBE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fLSnRPu4XoE1OcBE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fLSnRPu4XoE1OcBE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fLSnRPu4XoE1OcBE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fLSnRPu4XoE1OcBE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fLSnRPu4XoE1OcBE .marker.cross{stroke:#333333;}#mermaid-svg-fLSnRPu4XoE1OcBE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fLSnRPu4XoE1OcBE p{margin:0;}#mermaid-svg-fLSnRPu4XoE1OcBE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-fLSnRPu4XoE1OcBE .cluster-label text{fill:#333;}#mermaid-svg-fLSnRPu4XoE1OcBE .cluster-label span{color:#333;}#mermaid-svg-fLSnRPu4XoE1OcBE .cluster-label span p{background-color:transparent;}#mermaid-svg-fLSnRPu4XoE1OcBE .label text,#mermaid-svg-fLSnRPu4XoE1OcBE span{fill:#333;color:#333;}#mermaid-svg-fLSnRPu4XoE1OcBE .node rect,#mermaid-svg-fLSnRPu4XoE1OcBE .node circle,#mermaid-svg-fLSnRPu4XoE1OcBE .node ellipse,#mermaid-svg-fLSnRPu4XoE1OcBE .node polygon,#mermaid-svg-fLSnRPu4XoE1OcBE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-fLSnRPu4XoE1OcBE .rough-node .label text,#mermaid-svg-fLSnRPu4XoE1OcBE .node .label text,#mermaid-svg-fLSnRPu4XoE1OcBE .image-shape .label,#mermaid-svg-fLSnRPu4XoE1OcBE .icon-shape .label{text-anchor:middle;}#mermaid-svg-fLSnRPu4XoE1OcBE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-fLSnRPu4XoE1OcBE .rough-node .label,#mermaid-svg-fLSnRPu4XoE1OcBE .node .label,#mermaid-svg-fLSnRPu4XoE1OcBE .image-shape .label,#mermaid-svg-fLSnRPu4XoE1OcBE .icon-shape .label{text-align:center;}#mermaid-svg-fLSnRPu4XoE1OcBE .node.clickable{cursor:pointer;}#mermaid-svg-fLSnRPu4XoE1OcBE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-fLSnRPu4XoE1OcBE .arrowheadPath{fill:#333333;}#mermaid-svg-fLSnRPu4XoE1OcBE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-fLSnRPu4XoE1OcBE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-fLSnRPu4XoE1OcBE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fLSnRPu4XoE1OcBE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-fLSnRPu4XoE1OcBE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fLSnRPu4XoE1OcBE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-fLSnRPu4XoE1OcBE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-fLSnRPu4XoE1OcBE .cluster text{fill:#333;}#mermaid-svg-fLSnRPu4XoE1OcBE .cluster span{color:#333;}#mermaid-svg-fLSnRPu4XoE1OcBE 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-fLSnRPu4XoE1OcBE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-fLSnRPu4XoE1OcBE rect.text{fill:none;stroke-width:0;}#mermaid-svg-fLSnRPu4XoE1OcBE .icon-shape,#mermaid-svg-fLSnRPu4XoE1OcBE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fLSnRPu4XoE1OcBE .icon-shape p,#mermaid-svg-fLSnRPu4XoE1OcBE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-fLSnRPu4XoE1OcBE .icon-shape .label rect,#mermaid-svg-fLSnRPu4XoE1OcBE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fLSnRPu4XoE1OcBE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-fLSnRPu4XoE1OcBE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-fLSnRPu4XoE1OcBE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 外部依赖
internal/oci 模块
spec.go

Spec, SpecModifier, SpecModifiers
spec_file.go

fileSpec, loader
spec_memory.go

memorySpec
runtime.go

Runtime接口
runtime_modifier.go

modifyingRuntimeWrapper
runtime_low_level.go

NewLowLevelRuntime, findRuntime
runtime_path.go

pathRuntime
runtime_syscall_exec.go

syscallExec
args.go

GetBundleDir, HasCreateSubcommand
state.go

State
options.go

Option, WithLogger
specs-go

OCI规范类型
internal/logger

日志接口
pkg/lookup

可执行文件查找
logrus

日志库


五、测试覆盖分析

5.1 args_test.go --- 参数解析测试

TestGetBundleDir
测试用例 argv 预期 bundle 预期 error
空参数 \[\] "" nil
只有 create "create" "" nil
--bundle 无值 "--bundle" "" error
-b 无值 "-b" "" error
--bundle /path "--bundle", "/foo/bar" "/foo/bar" nil
--not-bundle /path "--not-bundle", "/foo/bar" "" nil
只有 -- "--" "" nil
-bundle /path "-bundle", "/foo/bar" "/foo/bar" nil
--bundle=/path "--bundle=/foo/bar" "/foo/bar" nil
-b=/path "-b=/foo/bar" "/foo/bar" nil
-b=/foo/=bar "-b=/foo/=bar" "/foo/=bar" nil
-b /path "-b", "/foo/bar" "/foo/bar" nil
create -b /path "create", "-b", "/foo/bar" "/foo/bar" nil
-b create create "-b", "create", "create" "create" nil
-b=create create "-b=create", "create" "create" nil
-b create "-b", "create" "create" nil
TestHasCreateSubcommand
测试用例 args 预期
\[\] false
create "create" true
--bundle=create "--bundle=create" false
--bundle create "--bundle", "create" false
create "create" true

5.2 spec_file_test.go --- fileSpec 测试

TestLoadFrom
输入 预期
空字节 \[\] error
"{}" 成功, spec = &specs.Spec{}
TestFlushTo
spec 预期输出
nil 不写入
&specs.Spec{} {"ociVersion":""}\n
errorWriter error

5.3 runtime_modifier_test.go --- modifyingRuntimeWrapper 测试

TestExec
场景 args modifier 应Load 应Modify 应Flush 应Forward
无参数 \[\] mock
create "create" mock
modify错误 "create" mock(返回错误)
write错误 "create" mock(Flush错误)
nil modifier "create" nil
TestNilModiferReturnsRuntime

验证 nil modifier 时直接返回原始 runtime(不包装)。

5.4 spec_memory_test.go --- memorySpec 测试

TestLookupEnv
环境变量 预期值 预期存在
nil spec "" false
nil Process "" false
空 Env "" false
不同变量 "" false
前缀匹配但非完全 "" false
TEST_ENV= "" true
TEST_ENV (无=) "" true
TEST_ENV=something "something" true
TEST_ENV=a=b "a=b" true
TestModify
spec modifier错误 预期
nil - error
&specs.Spec{} nil 成功, Version="updated"
&specs.Spec{} error 返回错误, spec不变

5.5 runtime_path_test.go --- pathRuntime 测试

TestPathRuntimeConstructor
路径 预期
////an/invalid/path error
/tmp (目录) error
/dev/null error
/bin/sh 成功
TestPathRuntimeForwardsArgs

验证参数转发逻辑:

  • args0 被替换为 runtime path
  • args1: 保留

六、关键设计模式分析

6.1 装饰器模式(Decorator Pattern)

modifyingRuntimeWrapper 是经典的装饰器:

  • 包装另一个 Runtime
  • 在调用前后添加额外行为(修改 spec)
  • 保持接口不变(仍然是 Runtime
go 复制代码
type modifyingRuntimeWrapper struct {
    runtime  Runtime  // 被包装的对象
    modifier SpecModifier
    // ...
}

6.2 策略模式(Strategy Pattern)

SpecModifier 接口允许不同的修改策略:

  • stableRuntimeModifier --- 注入 hook 策略
  • csvModifier --- CSV 文件策略
  • cdiModifier --- CDI 设备策略
  • hookRemover --- 移除 hook 策略

6.3 工厂方法模式

NewLowLevelRuntimeNewRuntimeForPath 都是工厂方法:

  • 封装创建逻辑
  • 返回接口类型
  • 包含验证逻辑

6.4 嵌入/组合模式

fileSpec 嵌入 memorySpec,复用方法:

go 复制代码
type fileSpec struct {
    memorySpec  // 嵌入,复用 Modify 和 LookupEnv
    path string
    loader loader
}

6.5 Option 模式

options.go 使用函数式选项:

go 复制代码
type Option func(*options)
func WithLogger(logger Interface) Option { ... }
func WithAllowUnknownFields(bool) Option { ... }

七、数据流分析

7.1 完整数据流

复制代码
命令行: nvidia-container-runtime --bundle /tmp/bundle create

1. args.go: GetBundleDir(args) → "/tmp/bundle"
2. args.go: GetSpecFilePath("/tmp/bundle") → "/tmp/bundle/config.json"
3. spec_file.go: NewFileSpec("/tmp/bundle/config.json", true) → fileSpec
4. spec_file.go: fileSpec.Load()
   → os.Open("/tmp/bundle/config.json")
   → json.Decode → specs.Spec{...}
5. memorySpec.Modify(specModifier)
   → specModifier.Modify(&spec)  // 注入 GPU 设备/hook
6. spec_file.go: fileSpec.Flush()
   → os.Create("/tmp/bundle/config.json")  // 截断
   → json.Encode(spec)  // 写入修改后的 spec
7. runtime_path.go: pathRuntime.Exec(argv)
   → syscall.Exec("/usr/bin/runc", ["/usr/bin/runc", "create", ...], environ)
   → 进程替换为 runc
8. runc 读取修改后的 config.json,创建容器

7.2 Spec 修改数据流

复制代码
原始 config.json:
{
  "ociVersion": "1.0.2",
  "process": {
    "env": ["NVIDIA_VISIBLE_DEVICES=all"]
  }
}

     ↓ Load() --- JSON 解析

内存中的 specs.Spec:
{
  Version: "",
  Process: &Process{
    Env: ["NVIDIA_VISIBLE_DEVICES=all"]
  }
}

     ↓ Modify(stableRuntimeModifier)

修改后的 specs.Spec:
{
  Version: "",
  Process: &Process{
    Env: ["NVIDIA_VISIBLE_DEVICES=all"]
  },
  Hooks: &Hooks{
    Prestart: [
      {Path: "/usr/bin/nvidia-container-runtime-hook", Args: ["nvidia-container-runtime-hook", "prestart"]}
    ]
  }
}

     ↓ Flush() --- JSON 编码

修改后的 config.json:
{
  "ociVersion": "",
  "process": {
    "env": ["NVIDIA_VISIBLE_DEVICES=all"]
  },
  "hooks": {
    "prestart": [
      {
        "path": "/usr/bin/nvidia-container-runtime-hook",
        "args": ["nvidia-container-runtime-hook", "prestart"]
      }
    ]
  }
}

八、性能分析

操作 预计耗时 说明
NewFileSpec <1μs 仅创建结构体
Load() ~1-5ms 文件 I/O + JSON 解析
Modify() ~1-50ms 取决于修改器复杂度
Flush() ~1-5ms 文件 I/O + JSON 编码
HasCreateSubcommand <1μs 字符串遍历
GetBundleDir <1μs 字符串遍历
NewLowLevelRuntime ~5-20ms PATH 搜索
syscall.Exec 内核级 进程替换

九、安全分析

9.1 JSON 解析安全

  • strictLoader 使用 DisallowUnknownFields() 防止未知字段注入
  • 但可通过配置 AllowUnknownOCISpecFields 关闭

9.2 路径验证

  • NewRuntimeForPath 验证路径是可执行文件
  • 检查文件存在性、非目录、有执行权限位

9.3 进程替换安全

  • syscall.Exec 使用 //nolint:gosec 标注
  • 参数来自受信任的配置和命令行
  • 环境变量通过 os.Environ() 继承

9.4 文件操作安全

  • createLogFile 创建目录权限 0755,文件权限 0644
  • os.Create 截断文件,不是追加
  • os.Open 只读模式打开 spec 文件

十、总结

10.1 模块核心价值

internal/oci 模块通过定义清晰的接口体系(Spec、Runtime、SpecModifier),实现了:

  1. 抽象解耦:上层不需要知道 spec 是文件还是内存
  2. 修改器可插拔:通过 SpecModifier 接口支持多种修改策略
  3. 运行时可替换:通过 Runtime 接口支持不同的低层运行时
  4. 进程替换:通过 syscall.Exec 实现高效的无 fork 执行

10.2 设计亮点

  1. fileSpec 嵌入 memorySpec 复用代码,避免重复
  2. modifyingRuntimeWrapper 装饰器模式,透明地添加修改能力
  3. nil modifier 优化 直接返回原 runtime,避免不必要包装
  4. minimalSpec 在 hook 中只解析需要的字段,提高性能
  5. HasCreateSubcommandpreviousWasBundle 逻辑避免误判

10.3 与其他模块的协作

OCI 模块是连接 runtime 模块和实际运行时的桥梁:

  • runtime 模块通过 OCI 模块创建和执行运行时
  • modifier 模块通过 SpecModifier 接口修改 spec
  • 所有修改最终通过 fileSpec.Flush() 写回文件,供 runc 读取
相关推荐
阿里云云原生2 小时前
让 AI Agent 看见正在发生的业务,阿里云 EventHouse 正式商业化
云原生
XUHUOJUN3 小时前
Azure Local VM 部署第 1 篇:Hyperconverged 路径完整实战
架构·azure local
Elastic 中国社区官方博客3 小时前
将你的 Grafana Kubernetes 仪表板迁移到 Elastic Observability:相同的 PromQL,30 倍更快的查询
大数据·人工智能·elasticsearch·搜索引擎·容器·kubernetes·grafana
AOwhisky4 小时前
Python 学习笔记(第十五期)——运维自动化(下·后篇):堡垒机实战——paramiko高阶篇
运维·python·学习·云原生·自动化·运维开发
风曦Kisaki5 小时前
#企业级docker私有仓库构建:harbor仓库与阿里云镜像仓库
阿里云·docker·容器
期待着20136 小时前
docker 安装 ,在centos7.9
运维·docker·容器
微三云 - 廖会灵 (私域系统开发)6 小时前
电商系统从单体到微服务拆分实践:拆什么、怎么拆、拆完后怎么办
微服务·云原生·架构
在水一缸6 小时前
苹果AI国行版过审背后的技术架构深度解析:端侧模型与私有云计算的融合实践
人工智能·架构·云计算·技术架构·苹果ai·端侧模型·私有云计算
爱上纯净的蓝天6 小时前
具身智能架构演进:从“纸上大脑“到“落地干活“的三层架构设计
架构