从裸机到 RTOS:用 SEGGER SystemView 定位任务切换抖动与优先级反转
裸机时代,一个 while(1) 加中断,时序基本可预测。引入 RTOS 后,调度器、临界区、阻塞唤醒都会带来任务切换抖动(jitter),而共享资源竞争又可能引发优先级反转。这两个问题用 printf 或 GPIO 翻转很难看清,SEGGER SystemView 能把内核事件画成时间轴,是定位利器。
一、原理:抖动与优先级反转从哪来
-
切换抖动:任务本应每 10ms 运行一次,实际唤醒时刻却在 9.7~10.4ms 间波动。来源包括:SysTick 与高优先级中断抢占、临界区关中断过长、
taskENTER_CRITICAL()嵌套、内存分配(pvPortMalloc)耗时、以及同优先级任务时间片轮转。 - 优先级反转:低优先级任务 L 持有互斥量,高优先级任务 H 等待该互斥量而阻塞,中优先级任务 M 就绪后抢占 L,导致 H 被 M 间接拖延。若用二值信号量而非互斥量,FreeRTOS 不会启用优先级继承,反转会非常明显。
SystemView 通过 SEGGER RTT 把 FreeRTOS 的 traceTASK_SWITCHED_IN/OUT、traceISR_ENTER/EXIT 等钩子事件实时上传到 PC,时间精度达微秒级,抖动一目了然。
二、配置步骤(STM32 + FreeRTOS + SystemView)
- 在工程中加入 SEGGER 的
SEGGER_RTT.c/.h、SEGGER_SYSVIEW.c/.h、SEGGER_SYSVIEW_Config_FreeRTOS.c、SEGGER_SYSVIEW_FreeRTOS.c。 - 在
FreeRTOSConfig.h中开启跟踪:
#define configUSE_TRACE_FACILITY 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 1
#define INCLUDE_xTaskGetCurrentTaskHandle 1
#define INCLUDE_pxTaskGetStackStart 1
- 在
main()初始化硬件后、创建任务前调用:
#include "SEGGER_SYSVIEW.h"
SEGGER_SYSVIEW_Conf();
SEGGER_SYSVIEW_Start();
- PC 端打开 SystemView,Target 选 RTT,连接后即可看到时间轴。
三、复现优先级反转的完整代码
下面代码故意用二值信号量保护共享资源,制造反转。
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "SEGGER_SYSVIEW.h"
SemaphoreHandle_t xSem; /* 二值信号量,无优先级继承 */
static void LowTask(void *p){
for(;;){
xSemaphoreTake(xSem, portMAX_DELAY);
/* 模拟长临界区:占用 30ms */
vTaskDelay(pdMS_TO_TICKS(30));
xSemaphoreGive(xSem);
vTaskDelay(pdMS_TO_TICKS(50));
}
}
static void MidTask(void *p){
for(;;){
/* 纯计算,抢占 Low */
volatile uint32_t i; for(i=0;i<2000000;i++);
vTaskDelay(pdMS_TO_TICKS(10));
}
}
static void HighTask(void *p){
for(;;){
xSemaphoreTake(xSem, portMAX_DELAY); /* 等待 Low 释放 */
xSemaphoreGive(xSem);
vTaskDelay(pdMS_TO_TICKS(20));
}
}
int main(void){
HAL_Init(); SystemClock_Config();
SEGGER_SYSVIEW_Conf();
SEGGER_SYSVIEW_Start();
xSem = xSemaphoreCreateBinary();
xSemaphoreGive(xSem);
xTaskCreate(LowTask, "Low", 256, NULL, 1, NULL);
xTaskCreate(MidTask, "Mid", 256, NULL, 2, NULL);
xTaskCreate(HighTask, "High", 256, NULL, 3, NULL);
vTaskStartScheduler();
for(;;);
}
在 SystemView 时间轴上会看到:High 就绪后长时间处于 Ready 而非 Running,Mid 却持续运行——这就是反转。
四、用 SystemView 定位抖动
- 打开 Events 视图,按
Task switched in统计各任务唤醒间隔,观察最大/最小偏差。 - 若发现某任务唤醒被推迟,检查其前面是否有长临界区:在时间轴上找
ISR enter与taskENTER_CRITICAL区间。 - 常见修复:缩短临界区、把耗时操作移出临界区、提高 SysTick 与 PendSV 优先级一致性、避免在中断里调用非 FromISR 的 API。
五、修复优先级反转
把二值信号量换成互斥量,FreeRTOS 自动启用优先级继承:
xSem = xSemaphoreCreateMutex();
此时 Low 持有互斥量期间会被临时提升到 High 的优先级,Mid 无法抢占,High 得以尽快获得资源。SystemView 上可看到 Low 的优先级被动态提升的事件。
六、注意事项
- SystemView 依赖 RTT,需保证调试器连接且 RTT 缓冲区足够大,否则丢事件。
- 跟踪本身有开销,量产固件应关闭
configUSE_TRACE_FACILITY。 - 互斥量只能在任务中使用,中断里用
xSemaphoreGiveFromISR配二值信号量。 - 优先级继承只解决反转,不解决死锁;仍要避免嵌套持锁。
- 抖动分析要结合示波器测 GPIO,交叉验证 SystemView 时间轴。
掌握 SystemView 后,RTOS 的“黑盒”调度变得可见,抖动与反转不再是玄学。