前言

在现代人工智能基础设施中,计算设备的高效利用是核心诉求。单个高性能AI加速器往往需要同时服务于多个用户、多个进程甚至多个容器,这就对底层驱动提出了严峻挑战:如何在保障隔离性与安全性的前提下,实现计算、内存、通信等硬件资源的公平、高效、低开销共享?CANN(Compute Architecture for Neural Networks)的 driver 仓库(截至2026年初的最新代码)通过一套精密的设备资源调度器(Task Resource Scheduler, TRS)与多进程共享机制,为这一问题提供了系统级的解决方案。

本文将深入剖析 CANN driver 的源码实现,从内核态的资源抽象、调度策略到用户态的共享接口,层层递进地揭示其如何支撑起复杂的多租户AI工作负载。

一、整体架构:资源调度与共享的分层设计

CANN driver 采用清晰的分层架构来管理设备资源,主要涉及三个关键层次:

  1. HAL 层 (Hardware Abstraction Layer):位于 src/ascend_hal,直接与硬件寄存器交互,提供最底层的资源操作原语,如任务提交、内存分配、中断处理。
  2. SDK-driver 层:位于 src/sdk_driver,是 HAL 能力的封装与扩展,面向用户态应用提供稳定、安全的API。资源调度器(TRS)和共享机制的核心逻辑主要集中在此层
  3. DCMI 层 (DaVinci Card Management Interface):作为用户态与内核态的桥梁,通过 ioctl 和字符设备(如 /dev/davinciX)暴露标准化接口。

这种分层确保了硬件细节被有效屏蔽,上层应用无需关心具体的芯片实现,即可通过统一的接口进行资源申请与任务提交。


二、核心组件:任务资源调度器(TRS)

任务资源调度器(TRS)是 driver 中负责协调所有计算任务执行的核心引擎。它不仅要保证任务按序、正确地完成,还要在多进程并发场景下,公平地分配硬件计算单元(AICore)、内存带宽等稀缺资源。

2.1 TRS 的数据结构与状态机

TRS 的核心是一个任务队列(Task Queue),每个提交到设备的任务都会被封装成一个 trs_task 结构体,并进入队列等待调度。

// src/sdk_driver/trs/trs_task.h
struct trs_task {
    uint64_t task_id;           // 全局唯一任务ID
    pid_t submit_pid;           // 提交此任务的进程PID
    struct file *submit_file;   // 提交此任务的文件描述符(用于权限校验)
    void *sq_vaddr;             // 提交队列(Submit Queue)虚拟地址
    uint32_t sq_size;           // 提交队列大小
    enum trs_task_state state;  // 任务状态:PENDING, RUNNING, COMPLETED, ERROR
    struct list_head list;      // 链入全局任务队列
    // ... 其他字段
};

关键点在于 submit_pidsubmit_file 字段。它们记录了任务的“归属”,是后续实现进程级资源隔离与回收的关键。

2.2 调度流程:从用户提交到硬件执行

  1. 用户提交:用户态应用(如 PyTorch)通过 CANN Runtime 调用 aclrtEnqueue 等API,最终会通过 DCMI 接口向内核 driver 发送一个包含任务描述符的 ioctl 命令。
  2. 内核接收与封装:driver 的 sdk_driver/ts_agent 模块接收到请求后,会创建一个 trs_task 对象,填充 submit_pid(来自 current->pid)和 submit_file(来自 ioctl 的 struct file * 参数),并将其加入 TRS 的全局待处理队列。
  3. 调度器轮询:TRS 的主调度线程(通常是一个内核线程)会周期性地扫描任务队列。对于 PENDING 状态的任务,它会检查硬件资源是否可用。
  4. 硬件派发:一旦资源就绪,调度器会将任务描述符写入硬件的命令队列(Command Queue),并触发硬件开始执行。
  5. 完成通知:硬件执行完毕后,会触发中断。driver 的中断处理程序会将对应 trs_task 的状态更新为 COMPLETED,并通过 eventfd 或其他机制通知用户态。

这个流程确保了所有任务都经过一个中心化的调度点,为实现公平性和优先级调度奠定了基础。

2.3 公平性与优先级

虽然开源代码中未完全暴露复杂的调度策略,但从 trs_task 结构和相关代码可以推断,TRS 支持基于进程或用户的简单公平调度。例如,调度器可以维护一个 per-PID 的任务计数器,防止单个进程独占所有硬件资源。


三、多进程共享机制:从设备文件到虚拟化

多进程共享是 CANN driver 的另一大核心能力。它允许多个独立的进程(甚至来自不同用户)安全地访问同一物理设备。

3.1 设备文件 (/dev/davinciX) 的角色

每个物理计算设备在 Linux 系统中被注册为一个字符设备,例如 /dev/davinci0。这是多进程共享的入口点。

  • 打开 (open):当一个进程首次打开 /dev/davinci0 时,driver 会为其在内核中创建一个设备上下文(Device Context),记录该进程与此设备的关联关系。
  • 权限控制:Linux 的标准文件权限(chmod, chown)可以直接应用于 /dev/davinciX,从而控制哪些用户或组可以访问设备,实现了基础的安全隔离。

3.2 进程级资源追踪与自动回收

这是多进程共享中最关键也最易被忽视的一环:当一个进程异常退出(crash)或被杀死(kill)时,它所占用的所有设备资源(如分配的内存、提交但未完成的任务)必须被自动、彻底地回收,否则会导致资源泄漏,最终使整个设备不可用

CANN driver 通过 Linux 内核的 mmu_notifier 机制和 file_operationsrelease 回调 来实现这一点。

关键机制一:file_operations->release

每个打开 /dev/davinciX 的进程都有一个对应的 struct file。当进程退出时,内核会自动调用该 filerelease 回调函数。

// src/sdk_driver/driver_main.c
static int davinci_release(struct inode *inode, struct file *file)
{
    struct ascend_client *client = file->private_data;
    
    if (!client) return 0;
    
    // 1. 清理该client提交的所有未完成任务
    trs_cleanup_tasks_by_client(client);
    
    // 2. 释放该client分配的所有设备内存
    svm_cleanup_all_by_client(client);
    
    // 3. 销毁client上下文
    kfree(client);
    file->private_data = NULL;
    
    return 0;
}

trs_cleanup_tasks_by_client 函数会遍历 TRS 的全局任务队列,找到所有 submit_file == file 的任务,并强制将其标记为 ERROR 状态,然后释放相关资源。这确保了“孤儿”任务不会永远阻塞调度器。

关键机制二:共享虚拟内存(SVM)的进程绑定

CANN driver 使用 SVM(Shared Virtual Memory)技术,允许 CPU 和 NPU 直接共享同一块虚拟地址空间,极大简化了数据搬运。SVM 内存的分配同样与进程绑定。

// src/sdk_driver/svm/svm_manager.c
struct svm_region {
    uint64_t vaddr;
    size_t size;
    struct pid *owner_pid; // 记录内存所有者的PID
    struct list_head list;
};

int svm_alloc(struct file *file, uint64_t *vaddr, size_t size)
{
    struct svm_region *region = kzalloc(sizeof(*region), GFP_KERNEL);
    region->vaddr = *vaddr;
    region->size = size;
    region->owner_pid = get_pid(task_tgid(current)); // 绑定到当前进程
    // ... 添加到全局SVM列表
}

davinci_release 中调用的 svm_cleanup_all_by_client 会遍历所有 SVM 区域,释放 owner_pid 与当前退出进程 PID 匹配的内存块。

3.3 容器化支持:device-share 特性

driver 仓库在近期(PR !35)引入了对容器环境的深度优化——device-share 特性。它允许在 Docker 或 Kubernetes 环境中,通过挂载设备文件的方式,让多个容器共享同一个物理设备。

其核心在于,driver 能够正确识别容器内的进程(通过 cgroup 信息),并将资源回收逻辑正确应用到容器粒度。虽然具体实现细节较为复杂,但其思想与上述的进程级回收一脉相承,只是将“进程”概念扩展到了“容器沙箱”。

用户可以通过以下命令验证:

$ npu-smi info -t device-share -i 0 -c 0
Device-share Status : True

这表明 driver 已成功识别并启用了设备共享模式。


四、通信与同步:HDC 与 Event 机制

在多进程共享场景下,进程间可能需要协同工作,这就需要高效的通信与同步机制。CANN driver 提供了 HDC(Host-Device Communication)。

  • Mailbox:一种轻量级的、基于内存的消息传递机制,用于在 Host CPU 和 Device 之间交换控制信息。
  • Event:一种异步通知机制。用户态可以创建一个 event 对象,并将其与一个任务关联。当任务完成时,driver 会自动触发该 event,用户态进程可以通过 pollepoll 等待事件发生,而无需忙询。

这些机制由 src/ascend_hal/hdcsrc/sdk_driver/hdc 模块实现,为上层构建复杂的分布式训练或推理流水线提供了基础。


结语

CANN driver 中的设备资源调度器与多进程共享机制,是一套融合了操作系统原理、硬件架构知识和软件工程实践的精密系统。它通过 TRS 实现了对计算任务的有序、公平调度;通过深度集成 Linux 内核的 file 生命周期管理和 mmu_notifier 机制,实现了进程级资源的自动追踪与安全回收;并通过 device-share 等特性,无缝适配了现代云原生容器化环境。这套机制不仅是 CANN 软件栈稳定、高效运行的基石,也为构建大规模、多租户的 AI 计算平台提供了坚实的底层保障。


cann组织链接:https://atomgit.com/cann
driver仓库链接:https://atomgit.com/cann/driver

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐