天天看点

FreeRTOS 任务挂起位置调查

在ARM平台上使用多任务操作系统时,因为程序设计存在缺陷,容易导致进入hard fault,网上对此有很多的资料介绍。在实际的程序调试中,我们也经常遇到另外一个问题。即想要执行的任务被挂起,CPU 在idle task 里面运行,这个问题同样也会困扰我们的功能调试。以下将结合一个简单的例子来分析如何找到任务被挂起的位置。

在freeRTOS 创建任务时,会创建一个任务控制块(TCB),并且该信息会通过指针进行返回。如下图所示

FreeRTOS 任务挂起位置调查

在本示例中,该参数传入了一个NULL值,因此该函数后不能直接得到这个“vLEDTask”的任务控制块信息。但也不需要着急,实际创建任务时,肯定会有相关的信息的存在。

继续进入该创建任务xTaskGenericCreate的函数内部,发现该函数动态创建了一个TCB的结构体,在本例中该结构体的地址为0x20000130。我们需要记录该地址,因为该结构体被一个局部变量保存,在退出该函数后,局部变量将被释放。

FreeRTOS 任务挂起位置调查

跳出该任务创建函数。将上述步骤中记录的0x20000130 添加到变量监视区。

FreeRTOS 任务挂起位置调查

如图中所示,我们在该地址前加上TCB 数据结构的类型,进行一个强制的类型转换,可以看到“vLEDTask”信息被记录和保存在结构体中,该结构体每个成员的含义,大家可以自行去查阅资料。在下面的调试过程中,我们重点关注第一个成员变量pxTopOfStack,该变量记录的是当前任务的栈顶地址。

全速运行程序,并停下程序执行。程序会停在idle task 任务里面执行(是因为CPU 绝大部分时间会执行idle task),下面我们开始分析“vLEDTask”的挂起位置。

在变量监视区查看“vLEDTask” TCB相关的信息,可以看到该任务的栈顶在0x20000330

FreeRTOS 任务挂起位置调查

查看0x20000330 相关的内存区,通过右键设置查看的数据类型为unsigned long。

FreeRTOS 任务挂起位置调查

如上图中标识的,程序被挂起位置的是0x08000all 。即栈顶指针开始的第14(8+6)个地址的位置。

为什么需要查看8+6 这个位置的地址呢,下面将展开描述。

查看freeRTOS 的移植文件中PendSV_Handler 函数,在该函数中,切出函数进行一系列的压栈操作。

FreeRTOS 任务挂起位置调查

如图中所示,R0 开始记录了被切出任务的栈指针。在执行 stmdb r0! ,r4-r11 指令后,R4~R11 共计8个字的内容被压入栈空间,并且栈指针减小了8*4字节。

在调用PendSV_Handler 函数时,中断的处理机制会导致R0~R3、R12、LR、PC、XPRS 顺序压入栈空间,其中的第6字的位置即是LR,即我们的程序被挂起的位置。

下面我们在keil 的命令行输入命令查看刚才程序的返回地址对应的位置

u 0x08000all

FreeRTOS 任务挂起位置调查

该地址对应于vTaskDelay 的末尾。即该函数的调用导致了“vLEDTask”

该函数在多任务系统中被大量调用,现在也不能确定唯一的调用位置,因此我们需要继续调查,在本例中,该函数有两个位置被调用。

查看vTaskDelay函数的汇编代码,如图所示,该函数会将4*4字节的内容压入堆栈,并且第4字即是vTaskDelay函数的返回地址LR。

FreeRTOS 任务挂起位置调查

继续查看堆栈的内容,从栈顶地址0x20000330开始查看,第20字(8+8+4)的位置既是我们查看的LR值 0x08000873.

FreeRTOS 任务挂起位置调查

在keil 命令行输入命令 u 0x08000873

FreeRTOS 任务挂起位置调查

该地址既是对应的“vLEDTask”任务被挂起后,下一步即将执行的地址。

至此,调查结束。

在本例中,PendSV_Handler 函数及vTaskDelay压栈的个数会因为不同的平台存在差异,需要根据具体的汇编代码进行核对。

继续阅读