第七周开发日志(3.8-3.14)

  • 工作进展:本周主要做的是规划,无实习任务相关的实质性代码工作

  • 本周做出了对下周工作的详细规划以及个人对aarch64的详细学习

-规划如下:

NPU 协处理器化架构设计 (NPU as FPU)

1. 概述

本项目提出并实现一种创新的异构计算架构,旨在将 NPU 从传统的、基于 VFS/ioctl 的“设备”模型,提升为与 CPU 核心紧耦合的“协处理器”。其设计目标是通过极致的软硬件协同,实现 NPU 算力调度

2. 核心设计哲学

彻底废弃传统基于 VFS(虚拟文件系统)和 ioctl 的松耦合设备驱动模型。将 NPU 提升为与 CPU 平级的计算协处理器(类比 FPU 浮点运算单元)。通过共享 DMA 内存、定制化系统调用(Syscall)与“懒汉式”上下文切换(Lazy Context Switch)实现算力调度。

3. 内核数据结构扩展 (TCB 升级)

为了让操作系统的调度器(Scheduler)能够接管 NPU 资源,必须扩展内核的任务控制块(TCB / task_struct):

  • npu_isdirty: bool

    • 含义:NPU 硬件上下文“脏”标识位。
    • true:表示当前物理 NPU 硬件的内部 SRAM 和寄存器中,依然保留着该任务的数据和状态。
    • false:表示该任务的 NPU 状态已被换出,或者尚未使用过 NPU。
  • npu_context: struct

    • 含义:NPU 硬件上下文结构体。追加在原生 CPU 任务上下文之后。
    • 作用:用于存储 NPU 被强行打断或切换时的完整硬件状态机。需查阅具体 NPU 芯片的闭源驱动源码及手册,提取并保存完整的寄存器集合(如命令队列指针、当前算子偏移量、中断屏蔽字等)。

4. 用户态基础框架 (libnpu)

在用户层封装底层细节,向应用程序开发者提供极简的 C/C++ 接口。

  • 专属内存分配 (dma_malloc 系统调用)

    • 抛弃标准 malloc。用户通过 dma_malloc 直接向内核申请一段物理地址连续的 DMA 内存。
    • 目的:绕过 IOMMU 的复杂页表配置,让 NPU 硬件的 DMA 控制器可以直接通过物理地址(PA)高速拉取数据。
  • 强制中断注入

    • 用户将计算参数、模型权重写入申请好的 DMA 内存区域。
    • 在构建 NPU 任务描述符(Task Descriptor)链表时,libnpu 会在底层介入,将每个微小可配置算子任务结构体中的中断控制位(IRQ Enable)强制设为开启。这是后续内核能够精准捕获算子边界、实现状态保存的关键。
  • 内联汇编触发 (npu_work API)

    • 利用 AArch64 调用约定,将装载了任务描述符链表的 DMA 内存物理地址指针塞入约定的寄存器(如 X0)。
    • 执行定制的内联汇编(包含特定的 SVC 陷入指令),直接触发定制的 resolve_npu 系统调用。

5. 内核态调度核心 (resolve_npu 系统调用)

这是整个架构的调度心脏。当 CPU 通过 SVC 陷入内核态后,严格按照以下流水线执行:

阶段 1:内存转换与前置检查

  1. 地址翻译:将 X0 寄存器传入的用户态虚拟地址(VA),通过查询页表或 DMA 分配记录,转换为 NPU 硬件所需的物理地址(PA)。
  2. 硬件状态机检查:检查全局 NPU 硬件状态(IDLEBUSY)。

阶段 2:懒汉式上下文切换 (Lazy Context Switch) 调度器需要仲裁当前物理 NPU 硬件的归属权。

  1. 碰撞检测:检查系统中是否存在一个“前任任务”,其 npu_isdirty == true,且该任务并非当前请求任务。
  2. 等待安全边界:如果存在这样的“前任任务”(说明 NPU 硬件中存有他人数据),内核绝不能强行复位硬件。必须检查 NPU 当前是否为 IDLE。如果是 BUSY,当前 CPU 线程必须挂起睡眠(加入等待队列),直到 NPU 算完当前算子并触发硬件中断。
  3. 保存前任状态:当 NPU 确认处于 IDLE 后,内核立刻将 NPU 硬件内的全量寄存器状态抽取出来,保存到那个“前任任务”的 npu_context 中。
  4. 剥夺前任所有权:将“前任任务”的 npu_isdirty 标志位修改为 false

阶段 3:恢复当前状态并启动硬件

  1. 检查当前记录:检查当前请求任务的 TCB 中,是否存有尚未跑完的 npu_context(即之前被切换出去的状态)。
  2. 状态恢复:如果存在,则将 npu_context 里的数据原封不动地写回 NPU 的物理控制寄存器。
  3. 宣誓主权:将当前任务的 npu_isdirty 标志位置为 true
  4. 硬件启动 (Kick the Doorbell):将阶段1中转换好的任务描述符物理地址指针,写入 NPU 的“任务执行寄存器”,并写入特定的启动命令(“踢门铃”)。
  5. 状态标记与返回:将全局 NPU 状态标记为 BUSY,CPU 退出系统调用,返回用户态继续执行或调度其他普通线程。

6. 硬件中断处理程序 (NPU IRQ Handler)

由于 libnpu 强制开启了每个算子的中断,NPU 每算完一个微小算子就会发出硬件 IRQ。

  1. 进入 ISR:CPU 陷入内核中断服务例程。
  2. 确认完成:读取 NPU 硬件状态寄存器,确认当前算子计算完成。
  3. 释放路权:将全局 NPU 状态强制标记为 IDLE(空闲)。
  4. 唤醒等待者唤醒resolve_npu 系统调用中,因为 NPU BUSY 而挂起睡眠的其他任务线程,让它们得以重新参与 NPU 的抢占与上下文切换流程。