【NPU】Ascend Docker Runtime v26.0.1 之二 runtime/process/process.go — 超深度逐行分析
runtime/process/process.go — 超深度逐行分析
文件行数:807行 | 核心职责:拦截create命令、修改OCI Spec、注入NPU设备与Hook
这是整个项目最核心、最复杂的文件
一、模块定位
1.1 业务职责
runtime/process/process.go 是 ascend-docker-runtime 的核心处理引擎。当 Docker 调用 ascend-docker-runtime 时,此文件负责:
- 解析命令行参数 — 提取
--bundle路径和create命令 - 读取OCI Spec — 解析容器的 config.json
- 检测NPU需求 — 解析
ASCEND_VISIBLE_DEVICES环境变量 - 选择NPU驱动 — V1(旧版910/310)或V2(新版910B/A5)
- 注入Hook — 向Spec添加Prestart和Poststop Hook
- 创建虚拟设备 — 如果是VIRTUAL模式,切分vNPU
- 挂载设备 — 向Spec添加davinci设备+管理设备+UB设备
- 写回Spec — 将修改后的config.json写回磁盘
- 执行runc — 透传给runc完成容器创建
1.2 在系统中的位置
二、常量与变量定义(第28-110行)
2.1 常量定义
const (
runLogPath = api.RunTimeRunLogPath
hookDefaultFilePath = "/usr/local/bin/ascend-docker-hook"
MaxCommandLength = 65535
hookCli = "ascend-docker-hook"
destroyHookCli = "ascend-docker-destroy"
dockerRuncFile = "docker-runc"
runcFile = "runc"
envLength = 2
kvPairSize = 2
borderNum = 2
useAscendDocker = api.AscendDockerRuntimeEnv + "=True"
ascendVisibleDevices = api.AscendVisibleDevicesEnv
ascendRuntimeOptions = api.AscendRuntimeOptionsEnv
void = "void"
)
| 常量 | 值 | 用途 |
|---|---|---|
runLogPath |
api定义的路径 | 日志文件路径 |
hookDefaultFilePath |
/usr/local/bin/ascend-docker-hook |
Hook默认路径(未使用,仅声明) |
MaxCommandLength |
65535 | 命令行参数最大长度,用于安全校验 |
hookCli |
"ascend-docker-hook" |
Hook程序文件名 |
destroyHookCli |
"ascend-docker-destroy" |
Destroy程序文件名 |
dockerRuncFile |
"docker-runc" |
Docker版runc文件名 |
runcFile |
"runc" |
标准runc文件名 |
envLength |
2 | 环境变量key=value对的长度 |
kvPairSize |
2 | SplitN的分段数 |
borderNum |
2 | 设备范围"0-3"的边界数(左+右) |
useAscendDocker |
"ASCEND_DOCKER_RUNTIME=True" |
标识容器使用ascend-docker-runtime |
ascendVisibleDevices |
"ASCEND_VISIBLE_DEVICES" |
NPU设备可见性环境变量名 |
ascendRuntimeOptions |
"ASCEND_RUNTIME_OPTIONS" |
运行时选项环境变量名 |
void |
"void" |
表示不需要挂载NPU |
2.2 变量定义
var (
hookCliPath = hookCli
hookDefaultFile = hookDefaultFilePath
dockerRuncName = dockerRuncFile
runcName = runcFile
deviceRegx = fmt.Sprintf(`^(?:%s(%s|%s|%s|%s)|%s)-(\d+)$`, api.Ascend, api.Ascend910No,
api.Ascend310BNo, api.Ascend310PNo, api.Ascend310No, api.NPULowerCase)
ascend910A5ManagerDevices = []string{hisiHdc}
defaultManagerDevices = []string{devmmSvm, hisiHdc}
managerDevicesMap = map[string][]string{
Ascend910A5: ascend910A5ManagerDevices,
}
)
deviceRegx 正则表达式解析:
正则 ^(?:Ascend(910|310B|310P|310)|npu)-(\d+)$ 用于匹配设备名格式:
| 匹配格式 | 示例 | 说明 |
|---|---|---|
Ascend910-N |
Ascend910-0 |
910芯片第N个设备 |
Ascend310B-N |
Ascend310B-1 |
310B芯片 |
Ascend310P-N |
Ascend310P-0 |
310P芯片 |
Ascend310-N |
Ascend310-2 |
310芯片 |
npu-N |
npu-0 |
通用NPU命名 |
设计意图:使用变量(非常量)是因为这些值可能在测试中被覆盖。
2.3 设备类型常量(第90-130行)
const (
Atlas200ISoc = "Atlas 200I SoC A1"
Atlas200 = "Atlas 200 Model 3000"
Ascend310 = api.Ascend310
Ascend310P = api.Ascend310P
Ascend310B = api.Ascend310B
Ascend910 = api.Ascend910
Ascend910A5 = api.Ascend910A5
ascend = api.Ascend
npu = api.NPULowerCase
devicePath = "/dev/"
davinciName = "davinci"
virtualDavinciName = "vdavinci"
davinciManager = "davinci_manager"
davinciManagerDocker = "davinci_manager_docker"
devmmSvm = "devmm_svm"
hisiHdc = "hisi_hdc"
// ... 310B管理设备列表
svm0, tsAisle, upgrade, sys, vdec, vpc, pngd, venc, logDrv, acodec, ai, ao, vo, hdmi
uburma, ummu
)
设备文件说明:
| 设备文件 | 用途 |
|---|---|
/dev/davinciN |
NPU计算设备,N是设备ID |
/dev/vdavinciN |
虚拟NPU设备(vNPU切分后) |
/dev/davinci_manager |
NPU管理设备 |
/dev/davinci_manager_docker |
Docker专用管理设备(容器内重映射) |
/dev/devmm_svm |
设备内存管理(SVM=Shared Virtual Memory) |
/dev/hisi_hdc |
华为HDC(Host Device Channel)通信通道 |
/dev/uburma |
UB(Ultra-Bandwidth)内存访问 |
/dev/ummu |
UB内存管理单元 |
三、核心类型定义
3.1 args结构体
type args struct {
bundleDirPath string
cmd string
}
bundleDirPath:OCI bundle目录路径,包含config.jsoncmd:命令类型,只有"create"需要处理,其他直接透传给runc
四、核心函数逐行解析
4.1 GetDeviceTypeByChipName(第113-127行)
func GetDeviceTypeByChipName(chipName string) string {
if strings.Contains(chipName, api.Ascend310BNo) {
return Ascend310B
}
if strings.Contains(chipName, api.Ascend310PNo) {
return Ascend310P
}
if strings.HasPrefix(chipName, api.Ascend910A5Prefix) {
return Ascend910A5
}
if strings.Contains(chipName, api.Ascend310No) {
return Ascend310
}
if strings.Contains(chipName, api.Ascend910No) {
return Ascend910
}
return ""
}
逐行解析:
- 如果芯片名包含"310B"字符串 → 返回
Ascend310B - 如果芯片名包含"310P" → 返回
Ascend310P - 注意:A5用
HasPrefix而非Contains,因为A5芯片名有特定前缀模式Ascend950XX - 如果包含"310" → 返回
Ascend310 - 如果包含"910" → 返回
Ascend910 - 都不匹配 → 返回空字符串
匹配优先级设计意图:310B和310P必须在310之前检查,因为"310B"也包含"310"子串。这是字符串匹配的经典陷阱。
4.2 getArgs(第129-144行)
func getArgs() (*args, error) {
args := &args{}
for i, param := range os.Args {
if param == "--bundle" || param == "-b" {
if len(os.Args)-i <= 1 {
return nil, fmt.Errorf("bundle option needs an argument")
}
args.bundleDirPath = os.Args[i+1]
} else if param == "create" {
args.cmd = param
}
}
return args, nil
}
逐行解析:
- 创建空
args结构体 - 遍历
os.Args(命令行参数数组) - 遇到
--bundle或-b:检查下一个参数是否存在,存在则设为bundleDirPath - 遇到
create:设为cmd - 返回解析结果
设计意图:OCI Runtime调用格式为 ascend-docker-runtime create --bundle /path,需要从中提取bundle路径和命令类型。
4.3 InitLogModule(第147-160行)
func InitLogModule(ctx context.Context) error {
const backups = 2
const logMaxAge = 365
const fileMaxSize = 2
runLogConfig := hwlog.LogConfig{
LogFileName: runLogPath,
LogLevel: 0, // DEBUG级别
MaxBackups: backups, // 最多2个备份
MaxAge: logMaxAge, // 保留365天
OnlyToFile: true, // 仅写文件不输出stdout
FileMaxSize: fileMaxSize, // 单文件2MB
}
if err := hwlog.InitRunLogger(&runLogConfig, ctx); err != nil {
fmt.Printf("hwlog init failed, error is %v", err)
return err
}
return nil
}
日志配置详解:
| 配置项 | 值 | 含义 |
|---|---|---|
| LogFileName | api.RunTimeRunLogPath | 日志文件路径 |
| LogLevel | 0 | DEBUG级别(最详细) |
| MaxBackups | 2 | 最多保留2个轮转备份 |
| MaxAge | 365 | 日志最大保留天数 |
| OnlyToFile | true | 仅写文件,不输出到stdout |
| FileMaxSize | 2 | 单个日志文件最大2MB |
设计意图:OnlyToFile=true 防止日志干扰stdout,因为OCI Runtime的stdout会被runc捕获。
4.4 execRunc(第162-181行)
var execRunc = func() error {
tempRuncPath, err := exec.LookPath(dockerRuncName)
if err != nil {
tempRuncPath, err = exec.LookPath(runcName)
if err != nil {
return fmt.Errorf("failed to find the path of runc: %v", err)
}
}
runcPath, err := filepath.EvalSymlinks(tempRuncPath)
if err != nil {
return fmt.Errorf("failed to find realpath of runc %v", err)
}
if _, err := mindxcheckutils.RealFileChecker(runcPath, true, false, mindxcheckutils.DefaultSize); err != nil {
return err
}
if err := mindxcheckutils.ChangeRuntimeLogMode("runtime-run-"); err != nil {
return err
}
if err = syscall.Exec(runcPath, append([]string{runcPath}, os.Args[1:]...), os.Environ()); err != nil {
return fmt.Errorf("failed to exec runc: %v", err)
}
return nil
}
逐行解析:
exec.LookPath("docker-runc")— 在PATH中查找docker版runc- 如果找不到,降级查找
runc filepath.EvalSymlinks— 解析符号链接获取真实路径RealFileChecker— 安全校验runc文件(防篡改)ChangeRuntimeLogMode— 收紧日志文件权限syscall.Exec— 替换当前进程为runc(不是fork/exec,是execve)
关键设计:syscall.Exec 会将当前进程的内存镜像替换为runc,PID不变。这意味着ascend-docker-runtime进程"变成"了runc进程。传递的参数是 os.Args[1:](去掉第一个参数,即程序名本身),环境变量原样传递。
4.5 addAscendDockerEnv(第183-188行)
func addAscendDockerEnv(spec *specs.Spec) {
if spec == nil || spec.Process == nil || spec.Process.Env == nil {
return
}
spec.Process.Env = append(spec.Process.Env, useAscendDocker)
}
- 向容器的环境变量列表追加
ASCEND_DOCKER_RUNTIME=True - 三重nil检查防止空指针panic
- 设计意图:让容器内的应用能检测到自己运行在ascend-docker-runtime环境中
4.6 addHook(第190-228行)
func addHook(w dcmi.WorkerInterface, spec *specs.Spec, deviceIdList *[]int) error {
这是最复杂的函数之一,负责注入Prestart Hook和创建虚拟设备。
if deviceIdList == nil {
return nil
}
currentExecPath, err := os.Executable()
if err != nil {
return fmt.Errorf("cannot get the path of docker-runtime: %v", err)
}
- nil检查保护
os.Executable()获取当前二进制文件路径(如/usr/local/bin/ascend-docker-runtime)
hookCliPath = path.Join(path.Dir(currentExecPath), hookCli)
if _, err := mindxcheckutils.RealFileChecker(hookCliPath, true, false, mindxcheckutils.DefaultSize); err != nil {
return err
}
if _, err = os.Stat(hookCliPath); err != nil {
return fmt.Errorf("cannot find docker-hook executable file at %s: %v", hookCliPath, err)
}
- 构造hook路径:与runtime同目录下的
ascend-docker-hook - 双重校验:
RealFileChecker(安全校验)+os.Stat(存在性检查)
if spec.Hooks == nil {
spec.Hooks = &specs.Hooks{}
}
needUpdate := true
if len(spec.Hooks.Prestart) > MaxCommandLength {
return fmt.Errorf("too many items in Prestart ")
}
for _, hook := range spec.Hooks.Prestart {
if strings.Contains(hook.Path, hookCli) {
needUpdate = false
break
}
}
if needUpdate {
spec.Hooks.Prestart = append(spec.Hooks.Prestart, specs.Hook{
Path: hookCliPath,
Args: []string{hookCliPath},
})
}
- 初始化Hooks结构(如果不存在)
- 安全限制:Prestart数量不超过65535
- 幂等性检查:遍历已有Prestart Hook,如果已包含ascend-docker-hook则不重复添加
- 添加Hook:Path为hook二进制路径,Args仅包含路径本身
if len(spec.Process.Env) > MaxCommandLength {
return fmt.Errorf("too many items in Env ")
}
if strings.Contains(getValueByKey(spec.Process.Env, ascendRuntimeOptions), "VIRTUAL") {
return nil
}
vdevice, err := dcmi.CreateVDevice(w, spec, *deviceIdList)
if err != nil {
return err
}
hwlog.RunLog.Infof("vnpu split done: vdevice: %v", vdevice.VdeviceID)
if vdevice.VdeviceID != -1 {
if err = updateEnvAndPostHook(spec, vdevice, deviceIdList); err != nil {
return fmt.Errorf("update evn and post hook failed: %v ", err)
}
}
return nil
}
- 如果环境变量包含"VIRTUAL" → 已是虚拟模式,跳过vNPU创建
- 调用
dcmi.CreateVDevice创建虚拟设备 - 如果创建成功(VdeviceID != -1),更新环境变量并注入Poststop Hook
4.7 parseDevices / getDeviceListFromVisibleValue(第248-291行)
func parseDevices(visibleDevices string) ([]int, error) {
devices := make([]int, 0)
for _, value := range strings.Split(visibleDevices, ",") {
deviceFromValue, err := getDeviceListFromVisibleValue(value)
if err != nil {
return nil, err
}
devices = append(devices, deviceFromValue...)
}
sort.Slice(devices, func(i, j int) bool { return i < j })
return removeDuplication(devices), nil
}
- 按逗号分割设备列表(如 “0,1,2” → [“0”,“1”,“2”])
- 对每个值调用
getDeviceListFromVisibleValue解析(支持范围"0-3") - 排序后去重
func getDeviceListFromVisibleValue(visibleValue string) ([]int, error) {
maxDevice := 128
devices := make([]int, 0)
visibleValue = strings.TrimSpace(visibleValue)
if strings.Contains(visibleValue, "-") {
// 范围解析: "0-3" → [0,1,2,3]
borders := strings.Split(visibleValue, "-")
if len(borders) != borderNum {
return nil, fmt.Errorf("invalid device range: %s", visibleValue)
}
left, err := strconv.Atoi(borders[0])
if err != nil || left < 0 {
return nil, fmt.Errorf("invalid left boarder range parameter: %s", borders[0])
}
right, err := strconv.Atoi(borders[1])
if err != nil || right > maxDevice {
return nil, fmt.Errorf("invalid right boarder range parameter: %s", borders[1])
}
if left > right {
return nil, fmt.Errorf("left boarder (%d) should not be larger than the right one(%d)", left, right)
}
for n := left; n <= right; n++ {
devices = append(devices, n)
}
return devices, nil
}
// 单个设备: "5" → [5]
n, err := strconv.Atoi(visibleValue)
if err != nil {
return nil, fmt.Errorf("invalid single device parameter: %s", visibleValue)
}
devices = append(devices, n)
return devices, nil
}
支持的格式:
| 输入格式 | 输出 | 说明 |
|---|---|---|
"0" |
[0] |
单个设备 |
"0,1,2" |
[0,1,2] |
多个设备 |
"0-3" |
[0,1,2,3] |
范围设备 |
"0-3,7,9-11" |
[0,1,2,3,7,9,10,11] |
混合格式 |
边界校验:左边界≥0,右边界≤128,左≤右。
4.8 parseAscendDevices(第293-311行)
func parseAscendDevices(visibleDevices string) ([]int, error) {
devicesList := strings.Split(visibleDevices, ",")
devices := make([]int, 0, len(devicesList))
for _, d := range devicesList {
matchGroups := regexp.MustCompile(deviceRegx).FindStringSubmatch(strings.TrimSpace(d))
if matchGroups == nil {
return nil, fmt.Errorf("invalid device format: %s", d)
}
n, err := strconv.Atoi(matchGroups[2])
if err != nil {
return nil, fmt.Errorf("invalid device id: %s", d)
}
devices = append(devices, n)
}
sort.Slice(devices, func(i, j int) bool { return i < j })
return removeDuplication(devices), nil
}
- 使用正则
^(?:Ascend(910|310B|310P|310)|npu)-(\d+)$匹配 matchGroups[2]是第二个捕获组(设备ID数字)- 排序+去重
与parseDevices的区别:parseDevices 处理纯数字格式(“0,1,2”),parseAscendDevices 处理命名格式(“Ascend910-0,npu-1”)。
4.9 getValueByKey / getValueByDeviceKey(第313-345行)
func getValueByKey(data []string, name string) string {
for _, envLine := range data {
words := strings.SplitN(envLine, "=", kvPairSize)
if len(words) != kvPairSize {
hwlog.RunLog.Error("environment error")
return ""
}
if words[0] == name {
return words[1]
}
}
return ""
}
- 从
["KEY1=val1", "KEY2=val2"]格式的数组中查找指定key的值 - SplitN限制分割数为2,防止值中包含"="时出错
func getValueByDeviceKey(data []string) string {
res := ""
for i := len(data) - 1; i >= 0; i-- {
words := strings.SplitN(data[i], "=", kvPairSize)
if len(words) != kvPairSize {
hwlog.RunLog.Error("environment error")
return ""
}
if words[0] == ascendVisibleDevices {
res = words[1]
break
}
}
// ...
return res
}
关键差异:getValueByDeviceKey 从后往前遍历。设计意图:当环境变量中存在多个 ASCEND_VISIBLE_DEVICES 时,取最后一个生效的值(后写覆盖前写,模拟Docker环境变量的覆盖语义)。
4.10 addDeviceToSpec(第360-375行)
func addDeviceToSpec(spec *specs.Spec, dHostPath string, dContainerPath string) error {
device, err := oci.DeviceFromPath(dHostPath)
if err != nil {
return fmt.Errorf("failed to get %s info : %v", dHostPath, err)
}
device.Path = dContainerPath
spec.Linux.Devices = append(spec.Linux.Devices, *device)
newDeviceCgroup := specs.LinuxDeviceCgroup{
Allow: true,
Type: device.Type,
Major: &device.Major,
Minor: &device.Minor,
Access: "rwm",
}
spec.Linux.Resources.Devices = append(spec.Linux.Resources.Devices, newDeviceCgroup)
return nil
}
逐行解析:
oci.DeviceFromPath(dHostPath)— 获取Host路径对应的设备信息(类型/主设备号/次设备号)device.Path = dContainerPath— 设置容器内路径(可能与Host路径不同,如vdavinci重映射)- 追加到
Spec.Linux.Devices— runc会根据此列表在容器namespace中创建设备节点 - 创建cgroup规则 — 允许容器访问该设备的 主:次 设备号,权限"rwm"(读写+mknod)
设计意图:OCI Spec的设备注入需要两步:1)声明设备(Linux.Devices),2)允许访问(Linux.Resources.Devices cgroup规则)。缺少任何一步都无法正常访问设备。
4.11 addManagerDevice(第408-455行)
func addManagerDevice(w dcmi.WorkerInterface, spec *specs.Spec) error {
chipName, err := w.GetChipName()
// ...
devType := GetDeviceTypeByChipName(chipName)
// ...
if devType != "" && devType != Ascend910A5 {
dPath := devicePath + dvppCmdList
if err := addDeviceToSpec(spec, dPath, dPath); err != nil {
hwlog.RunLog.Warnf("failed to add dvpp_cmdlist to spec : %v", err)
}
}
if devType == Ascend310B {
return addAscend310BManagerDevice(spec)
}
// 添加 davinci_manager
dPath := devicePath + davinciManager
if err := addDeviceToSpec(spec, dPath, dPath); err != nil {
return fmt.Errorf("add davinci_manager to spec error: %v", err)
}
productType, err := w.GetProductType()
// ...
switch productType {
case Atlas200ISoc, Atlas200:
// 这两种产品不需要额外管理设备
default:
if err = addCommonManagerDevice(spec, devType); err != nil {
return fmt.Errorf("add common manage device error: %v", err)
}
}
return nil
}
逐行解析:
- 获取芯片名称 → 确定设备类型
- 如果非空且非A5 → 添加
dvpp_cmdlist(数字视觉预处理命令列表设备) - 如果是310B → 调用专门的310B管理设备添加函数(14个设备)
- 添加
davinci_manager(所有芯片都需要) - 获取产品类型
- Atlas 200I SoC / Atlas 200 → 不添加额外管理设备
- 其他产品 → 添加通用管理设备(devmm_svm + hisi_hdc,A5仅hisi_hdc)
4.12 checkVisibleDevice(第500-522行)
func checkVisibleDevice(spec *specs.Spec) ([]int, error) {
if spec.Process == nil {
return nil, errors.New("empty process info")
}
visibleDevices := getValueByDeviceKey(spec.Process.Env)
if visibleDevices == "" || visibleDevices == void {
return nil, nil
}
if strings.Contains(visibleDevices, ascend) || strings.Contains(visibleDevices, npu) {
devices, err := parseAscendDevices(visibleDevices)
// ...
return devices, err
}
devices, err := parseDevices(visibleDevices)
// ...
return devices, err
}
逐行解析:
- nil检查
spec.Process - 从环境变量从后往前查找
ASCEND_VISIBLE_DEVICES - 空或"void" → 返回空列表(不挂载NPU)
- 如果包含"Ascend"或"npu" → 使用命名格式解析(
parseAscendDevices) - 否则 → 使用纯数字格式解析(
parseDevices)
4.13 addDevice(第524-545行)
func addDevice(w dcmi.WorkerInterface, spec *specs.Spec, deviceIdList []int) error {
deviceName := davinciName
if strings.Contains(getValueByKey(spec.Process.Env, ascendRuntimeOptions), "VIRTUAL") {
deviceName = virtualDavinciName
}
for _, deviceId := range deviceIdList {
dPath := devicePath + deviceName + strconv.Itoa(deviceId)
dContainerPath, err := getMountPath(dPath, deviceName)
// ...
if err := addDeviceToSpec(spec, dPath, dContainerPath); err != nil {
return fmt.Errorf("failed to add davinci device to spec: %v", err)
}
}
if err := addUBDevice(spec); err != nil {
// ...
}
if err := addManagerDevice(w, spec); err != nil {
// ...
}
return nil
}
逐行解析:
- 默认设备名为"davinci",如果VIRTUAL模式则改为"vdavinci"
- 遍历设备ID列表,对每个设备:
- 构造Host路径:
/dev/davinci0或/dev/vdavinci0 getMountPath获取容器内路径(vdavinci会重映射为davinci+N)- 添加到Spec
- 构造Host路径:
- 添加UB设备(uburma/ummu目录下的设备)
- 添加管理设备
4.14 updateEnvAndPostHook(第547-581行)
func updateEnvAndPostHook(spec *specs.Spec, vdevice dcmi.VDeviceInfo, deviceIdList *[]int) error {
此函数在vNPU创建成功后调用,负责更新环境变量和注入Poststop Hook。
newEnv := make([]string, 0, len(spec.Process.Env)+1)
needAddVirtualFlag := true
*deviceIdList = []int{int(vdevice.VdeviceID)}
- 关键操作:将设备ID列表替换为虚拟设备ID(
vdevice.VdeviceID) - 后续
addDevice会使用这个新的ID来挂载/dev/vdavinci{VdeviceID}
for _, line := range spec.Process.Env {
words := strings.Split(line, "=")
if len(words) == envLength && strings.TrimSpace(words[0]) == ascendRuntimeOptions {
needAddVirtualFlag = false
if strings.Contains(words[1], "VIRTUAL") {
newEnv = append(newEnv, line)
continue
} else {
newEnv = append(newEnv, strings.TrimSpace(line)+",VIRTUAL")
continue
}
}
newEnv = append(newEnv, line)
}
if needAddVirtualFlag {
newEnv = append(newEnv, fmt.Sprintf(api.AscendRuntimeOptionsEnv+"=VIRTUAL"))
}
spec.Process.Env = newEnv
- 遍历环境变量,找到
ASCEND_RUNTIME_OPTIONS - 如果已有VIRTUAL → 保持不变
- 如果没有VIRTUAL → 追加",VIRTUAL"
- 如果完全不存在 → 添加
ASCEND_RUNTIME_OPTIONS=VIRTUAL - 设计意图:确保Hook进程知道这是虚拟设备模式
currentExecPath, err := os.Executable()
// ...
postHookCliPath := path.Join(path.Dir(currentExecPath), destroyHookCli)
// ...安全校验...
spec.Hooks.Poststop = append(spec.Hooks.Poststop, specs.Hook{
Path: postHookCliPath,
Args: []string{postHookCliPath, fmt.Sprintf("%d", vdevice.CardID), fmt.Sprintf("%d", vdevice.DeviceID),
fmt.Sprintf("%d", vdevice.VdeviceID)},
})
return nil
}
- 构造destroy程序路径(与runtime同目录下的
ascend-docker-destroy) - 安全校验
- 注入Poststop Hook,参数为 cardID、deviceID、vdeviceID
- 设计意图:容器停止时自动销毁虚拟设备,防止资源泄漏
4.15 modifySpecFile / readSpecFile / writeSpecFile(第583-640行)
func modifySpecFile(path string) error {
if err := mindxcheckutils.CheckPath(path, true); err != nil {
return err
}
spec, err := readSpecFile(path)
if err != nil {
return err
}
if err := processDevicesAndHooks(spec); err != nil {
return err
}
return writeSpecFile(path, spec)
}
三步操作:安全校验路径 → 读取并修改 → 写回。
func readSpecFile(path string) (*specs.Spec, error) {
stat, err := os.Stat(path)
// ...
jsonFile, err := os.OpenFile(path, os.O_RDWR, stat.Mode())
// ...
defer jsonFile.Close()
if err = mindxcheckutils.CheckFileInfo(jsonFile, mindxcheckutils.DefaultSize); err != nil {
return nil, err
}
jsonContent, err := ioutil.ReadAll(jsonFile)
// ...
var spec specs.Spec
if err = json.Unmarshal(jsonContent, &spec); err != nil {
return nil, err
}
return &spec, nil
}
CheckPath— 路径安全校验(防../穿越、防软链接)CheckFileInfo— 文件安全校验(大小限制、权限检查、UID检查、防setuid/setgid)json.Unmarshal— JSON反序列化为specs.Spec结构体
func writeSpecFile(path string, spec *specs.Spec) error {
jsonFile, err := os.OpenFile(path, os.O_RDWR, 0)
// ...
jsonOutput, err := json.Marshal(spec)
// ...
if err = jsonFile.Truncate(0); err != nil {
return err
}
if _, err = jsonFile.WriteAt(jsonOutput, 0); err != nil {
return err
}
return nil
}
Truncate(0)— 清空文件内容(因为修改后的JSON可能比原来短)WriteAt(jsonOutput, 0)— 从文件开头写入- 设计意图:Truncate+WriteAt确保文件不会残留旧数据
4.16 processDevicesAndHooks(第642-658行)
func processDevicesAndHooks(spec *specs.Spec) error {
devices, err := checkVisibleDevice(spec)
if err != nil {
return fmt.Errorf("failed to check %v parameter, err: %v", api.AscendVisibleDevicesEnv, err)
}
if len(devices) != 0 {
npuWorker, err := dcmi.GetMatchingNpuWorker()
if err != nil {
return err
}
if err = addHook(npuWorker, spec, &devices); err != nil {
return fmt.Errorf("failed to inject hook, err: %v", err)
}
if err = addDevice(npuWorker, spec, devices); err != nil {
return fmt.Errorf("failed to add device to env: %v", err)
}
}
addAscendDockerEnv(spec)
return nil
}
核心编排函数,执行顺序:
checkVisibleDevice— 检测是否需要NPU- 如果需要 →
GetMatchingNpuWorker选择驱动 addHook— 注入Hook + 创建vNPUaddDevice— 挂载设备addAscendDockerEnv— 添加标识环境变量(无论是否需要NPU都添加)
4.17 DoProcess(第795-807行)
func DoProcess() error {
args, err := getArgs()
if err != nil {
return fmt.Errorf("failed to get args: %v", err)
}
if args.cmd != "create" {
return execRunc()
}
if args.bundleDirPath == "" {
hwlog.RunLog.Warn("get bundleDirPath is empty,try get current working dir from pwd ")
args.bundleDirPath, err = os.Getwd()
if err != nil {
return fmt.Errorf("failed to get current working dir: %v", err)
}
}
specFilePath := args.bundleDirPath + "/config.json"
if err = modifySpecFile(specFilePath); err != nil {
return fmt.Errorf("failed to modify spec file %s: %v", specFilePath, err)
}
return execRunc()
}
逐行解析:
getArgs()— 解析命令行参数- 如果不是"create"命令 → 直接execRunc透传(如delete/kill/start等命令)
- 如果bundle路径为空 → 尝试用当前工作目录
- 构造config.json路径
modifySpecFile— 读取→修改→写回execRunc— 执行runc
五、完整调用关系图
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐

所有评论(0)