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 来保证数据可见性。
四、任务与线程的关系
在 Hexagon 编程模型中,“任务”通常指一个逻辑上独立的工作单元,它可以由一个或多个线程协作完成。例如,一个音频处理任务可能包含:
- 一个高优先级线程负责从共享内存读取 PCM 数据;
- 一个中优先级线程负责执行 HVX 滤波运算;
- 一个低优先级线程负责将结果写回 DDR 并通知 CPU。
这三个线程通过信号量和共享内存协同,构成一个完整的“任务”。QuRT 不直接提供“任务”抽象,任务是由开发者根据业务逻辑组织的线程集合。理解这一点,有助于在设计阶段合理划分线程职责,避免过度创建线程导致调度开销上升。
五、线程、任务、共享内存与 QuRT 的协作关系
下图展示了四者之间的结构关系:QuRT 作为调度核心,管理多个硬件线程;每个线程拥有独立栈和寄存器上下文;线程之间通过共享内存和信号量通信;任务则是线程的逻辑分组。
六、实践中的关键约束
在实际工程中,有几个约束必须牢记:
- 线程栈必须静态分配:QuRT 不提供动态栈分配,栈大小需根据函数调用深度和局部变量估算,通常 4KB~16KB;
- 共享内存访问需处理缓存一致性:DSP 写入 DDR 后,CPU 读取前需 clean cache;CPU 写入后,DSP 读取前需 invalidate cache;
- 信号量操作不可在中断上下文中阻塞:中断处理程序应使用
qurt_sem_up等非阻塞 API; - 线程优先级需合理规划:音频等硬实时任务应设为高优先级,后台计算任务设为低优先级,避免优先级反转。
总结来说,Hexagon 编程模型的核心是:QuRT 调度硬件线程,线程通过共享内存和信号量协作,任务则是线程的逻辑组织单元。掌握这四者的关系,才能在设计阶段做出合理的线程划分和内存布局,写出既满足实时性又易于维护的 Hexagon 代码。