面向使用抢占式 RTOS 的 MCU 项目。本文介绍如何设计、创建、配置和验证线程;具体 API 与优先级数值以所用规格文档为准。

1. 线程设计原则

线程应围绕一个清晰职责建立,例如读取传感器、处理通信帧、执行控制周期或处理后台任务。建立线程前先明确:唤醒事件、输入缓冲区、响应截止时间、共享资源、阻塞时间,以及停止或出错时的资源释放责任。

中断服务程序只做必须立即完成的工作。解析、内存分配、日志输出和设备访问等较重操作应放到线程中,通过队列、信号量或事件通知线程处理。

2. 从数据风险确定优先级

2.1 缓冲区填满时间

持续到达的数据可用下式估算线程最多能被延迟多久:

T_fill = B / R

B:可用缓冲区容量(字节、帧或消息数)
R:峰值数据到达速率(字节/秒、帧/秒或消息数/秒)

T_fill 越短,越不能被其他线程长时间抢占。优先处理距离硬件最近、最容易溢出的 FIFO、DMA 缓冲区或环形缓冲区,并扣除已占用容量。

2.2 截止时间

没有显式缓冲区的线程,用截止时间代替 T_fill:控制回路必须在下个采样周期前完成,周期任务必须在下个周期到来前完成,心跳必须早于协议超时发送。截止时间越短,通常越需要更高优先级;同时还要检查最坏执行时间、阻塞时间和中断延迟。

2.3 流控和生产者—消费者

带流控或可重传的连接通常只会增加延迟而不会立即丢数据,这类任务可放在较低优先级,但不能无限占用 CPU。生产者、消费者的优先级不能仅凭“谁产生数据”决定,应综合考虑硬件溢出风险、消费者截止时间、队列容量和批量处理能力。

2.4 优先级反转

低优先级线程持锁时,高优先级线程可能被阻塞,而中优先级线程继续运行。跨优先级共享资源应使用支持优先级继承或优先级天花板的互斥锁,不要用普通信号量冒充互斥锁;并缩短锁的持有时间。

3. 优先级分层

先按响应约束分层,再在同一层共享优先级或使用时间片:

层级典型约束典型任务
紧急微秒至毫秒,缓冲区很快溢出高速接口搬运、DMA 完成处理
实时毫秒至百毫秒,周期或协议截止时间明确控制回路、音频和通信处理
交互百毫秒至数秒,影响用户或连接状态命令处理、状态更新、心跳
后台可延迟,具备流控或可重试文件传输、日志、维护任务
空闲无实时约束低功耗准备、统计和清理

确认 RTOS 的数值方向(数字越小或越大代表高优先级),并为中断、系统服务和空闲线程预留范围。

4. 创建线程

4.1 配置属性

至少明确入口函数和参数、优先级与时间片、栈大小和保护方式、线程名称、静态或动态分配方式、启动时机,以及是否允许挂起、删除和重启。静态分配适合内存受限或强调确定性的产品;动态创建需处理堆碎片、分配失败和删除后的资源回收。

4.2 入口函数

线程入口通常应永久等待事件,避免无条件轮询:

static void worker_entry(void *arg)
{
    worker_t *worker = (worker_t *)arg;

    for (;;) {
        event_t event;
        if (event_wait(worker->queue, &event, WAIT_FOREVER) != OK) {
            continue;
        }
        handle_event(worker, &event);
    }
}

若线程需要退出,应先停止接收新任务,再释放其拥有的队列、设备句柄和同步对象。若 RTOS 不允许入口返回,应进入明确的删除或安全停机流程。

4.3 启动顺序

先初始化驱动、队列和互斥锁,再创建工作线程;用设备就绪事件通知消费者;对单次初始化使用初始化状态或一次性同步原语;明确哪些线程必须等待网络、存储或传感器就绪。

5. 通信与同步

优先使用消息队列、环形缓冲区、事件标志和线程通知传递数据或状态,减少共享变量。共享变量要使用原子访问或锁保护,并规定所有权和生命周期。

需求合适工具
传递有边界的数据消息队列、环形缓冲区
通知一次事件线程通知、二值信号量
计数资源或事件次数计数信号量
保护共享资源支持优先级继承的互斥锁
等待多个条件事件标志或条件变量

队列满时必须规定策略:阻塞、丢弃最旧消息、丢弃最新消息或触发告警。不要在高优先级线程中无限等待低优先级线程,也不要在持锁期间等待可能由同一把锁保护的事件。

6. 栈、内存和执行时间

栈大小应根据最深调用链、局部数组、中断嵌套和库函数开销估算,再用运行时高水位或栈哨兵验证。不要把大数组、格式化缓冲区和网络报文直接放在线程栈上。

记录最坏执行时间和最长阻塞时间,检查:

执行时间 + 最长阻塞时间 < 任务周期或协议截止时间

实时线程应尽量使用对象池、预分配缓冲区和非阻塞接口,谨慎调用动态内存分配、日志格式化和文件系统接口。

7. 生命周期与故障处理

为线程定义创建、就绪、运行、等待、停止和故障状态。停止线程时发送退出事件,让线程自行释放资源,再执行删除;不要强制删除正在持锁或访问硬件的线程。

看门狗应监测“线程是否取得进展”,不能只在循环末尾无条件喂狗。线程超时后应保留最近事件、阻塞对象、执行时间和错误码,再执行重启、降级或安全停机。

8. 验证与调优

  1. 在峰值输入和最坏并发条件下测量队列峰值、缓冲区余量和响应延迟。
  2. 注入长时间阻塞、突发流量、设备断开和内存不足,确认不会死锁或静默丢数据。
  3. 检查栈高水位、CPU 占用、上下文切换次数和最长临界区。
  4. 暂停单个线程,观察上游缓冲区何时溢出,校准 T_fill。
  5. 用跟踪工具或时间戳验证优先级关系,而不是只依据配置文件推断。

如果调低优先级后数据仍不丢失,可能是实际缓冲区更大、输入速率更低或存在流控;如果调高优先级仍超时,应检查执行时间、锁竞争、禁中断区和中断优先级。

9. 发布前检查表

  • 线程职责、输入事件和退出条件已定义
  • 优先级依据缓冲区填满时间或截止时间,并记录了假设
  • 队列容量按峰值速率和最坏阻塞时间计算
  • 共享资源使用正确同步原语,确认无优先级反转
  • 栈大小经过高水位验证,未在栈上放置大对象
  • 启动顺序和依赖项就绪状态明确
  • 阻塞、超时、队列满和内存不足都有处理策略
  • 看门狗、日志和故障恢复不会破坏实时线程时序
  • 已在目标硬件和峰值负载下完成运行时验证
Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐