4. Hexagon 编程模型

Hexagon DSP 的编程模型与通用 CPU 有本质差异。它并非“一个核跑一个主程序”的简单结构,而是围绕硬件线程(Hardware Thread)、QuRT 实时操作系统、共享内存(Shared Memory / VTCM)以及任务(Task)四层概念组织起来的。理解这四者的关系,是写出可预测、低延迟 Hexagon 代码的前提。

一、硬件线程:Hexagon 的执行单元

Hexagon 架构在每个 DSP 核内提供多个硬件线程(典型为 6 个,如 Hexagon 680/780 系列),它们共享同一套执行单元(标量、HVX 向量、标量/向量寄存器堆),但各自拥有独立的通用寄存器组和程序计数器。这意味着:

  • 每个硬件线程可以独立取指、独立执行,硬件层面实现真正的并行;
  • 多个线程共享执行单元,因此当两个线程同时争用同一类执行单元时会产生结构冒险(Structural Hazard),由硬件调度器仲裁;
  • 线程之间通过共享内存和信号量通信,而不是通过寄存器直接传递数据。

在 QuRT 中,硬件线程被抽象为“线程对象”。开发者通常不直接操作硬件线程号,而是通过 qurt_thread_create 创建软件线程,由 QuRT 调度器将其映射到可用的硬件线程上。线程优先级、时间片、亲和性(affinity)都可以通过 QuRT API 指定。

二、QuRT 实时操作系统:调度与同步的核心

QuRT 是运行在 Hexagon 上的轻量级 RTOS,提供线程管理、信号量、互斥锁、消息队列、定时器、中断处理等机制。它的关键特性包括:

  • 抢占式调度:高优先级线程可抢占低优先级线程,保证实时性;
  • 优先级继承:互斥锁支持优先级继承,缓解优先级反转;
  • 确定性:调度延迟和中断响应时间有上界,适合音频、传感器等硬实时场景;
  • 与 FastRPC 协同:QuRT 线程可被 FastRPC 调用唤醒,执行来自 CPU 侧的远程过程调用。

一个典型的 QuRT 线程创建示例如下:

#include <qurt.h>

#define THREAD_STACK_SIZE 4096
#define THREAD_PRIORITY   10

static char thread_stack[THREAD_STACK_SIZE] __attribute__((aligned(8)));
static qurt_thread_t thread_handle;

void worker_thread(void *arg) {
    while (1) {
        // 等待信号量
        qurt_sem_down(&sem);
        // 处理数据
        process_data();
    }
}

void create_worker(void) {
    qurt_thread_attr_t attr;
    qurt_thread_attr_init(&attr);
    qurt_thread_attr_set_name(&attr, "worker");
    qurt_thread_attr_set_priority(&attr, THREAD_PRIORITY);
    qurt_thread_attr_set_stack_size(&attr, THREAD_STACK_SIZE);
    qurt_thread_attr_set_stack_addr(&attr, thread_stack);
    qurt_thread_create(&thread_handle, &attr, worker_thread, NULL);
}

注意:栈空间必须由调用者提供并保证对齐,QuRT 不会动态分配线程栈。这是嵌入式实时系统的常见约束。

三、共享内存与 VTCM

Hexagon 的共享内存分为几个层次:

  • DDR 共享内存:CPU 与 DSP 均可访问,通过 ION 或 dma-buf 分配,适合大数据量传输;
  • VTCM(Vector Tightly Coupled Memory):紧耦合在 DSP 核内,延迟极低,适合 HVX 向量运算的中间数据;
  • L2 缓存:DSP 内部缓存,由硬件管理,对软件透明。

线程间通信通常采用“共享内存 + 信号量”的模式:生产者线程写入共享缓冲区,然后释放信号量;消费者线程等待信号量,获取后读取缓冲区。这种模式避免了数据拷贝,但要求开发者自行处理缓存一致性(cache coherency)。在 Hexagon 上,DDR 访问需要显式调用 qurt_mem_cache_clean 或 qurt_mem_cache_invalidate 来保证数据可见性。

提示:VTCM 的容量有限(典型为 256KB~1MB),应优先用于 HVX 向量运算的输入/输出缓冲区,而不是长期驻留的大数据结构。

四、任务与线程的关系

在 Hexagon 编程模型中,“任务”通常指一个逻辑上独立的工作单元,它可以由一个或多个线程协作完成。例如,一个音频处理任务可能包含:

  • 一个高优先级线程负责从共享内存读取 PCM 数据;
  • 一个中优先级线程负责执行 HVX 滤波运算;
  • 一个低优先级线程负责将结果写回 DDR 并通知 CPU。

这三个线程通过信号量和共享内存协同,构成一个完整的“任务”。QuRT 不直接提供“任务”抽象,任务是由开发者根据业务逻辑组织的线程集合。理解这一点,有助于在设计阶段合理划分线程职责,避免过度创建线程导致调度开销上升。

五、线程、任务、共享内存与 QuRT 的协作关系

下图展示了四者之间的结构关系:QuRT 作为调度核心,管理多个硬件线程;每个线程拥有独立栈和寄存器上下文;线程之间通过共享内存和信号量通信;任务则是线程的逻辑分组。

QuRT 实时操作系统 调度 · 信号量 · 互斥锁 · 定时器 · 中断 硬件线程(Hardware Threads) 线程 0 高优先级 线程 1 中优先级 线程 2 低优先级 线程 3 空闲/备用 线程 4 HVX 专用 调度/映射 共享内存(Shared Memory) DDR 共享缓冲区 ION / dma-buf · 大容量 VTCM 紧耦合 · 低延迟 · HVX 专用 L2 缓存 硬件管理 · 对软件透明 任务(Task)— 线程的逻辑分组 音频处理任务 传感器融合任务 FastRPC 服务任务 信号量 / 互斥锁 同步

六、实践中的关键约束

在实际工程中,有几个约束必须牢记:

  • 线程栈必须静态分配:QuRT 不提供动态栈分配,栈大小需根据函数调用深度和局部变量估算,通常 4KB~16KB;
  • 共享内存访问需处理缓存一致性:DSP 写入 DDR 后,CPU 读取前需 clean cache;CPU 写入后,DSP 读取前需 invalidate cache;
  • 信号量操作不可在中断上下文中阻塞:中断处理程序应使用 qurt_sem_up 等非阻塞 API;
  • 线程优先级需合理规划:音频等硬实时任务应设为高优先级,后台计算任务设为低优先级,避免优先级反转。
注意:VTCM 是稀缺资源,多个线程同时申请 VTCM 时需通过 QuRT 的内存管理 API 协调,避免冲突。典型做法是在初始化阶段一次性分配,运行期不再动态申请。

总结来说,Hexagon 编程模型的核心是:QuRT 调度硬件线程,线程通过共享内存和信号量协作,任务则是线程的逻辑组织单元。掌握这四者的关系,才能在设计阶段做出合理的线程划分和内存布局,写出既满足实时性又易于维护的 Hexagon 代码。