之前的文章RISC-V FreeRTOS异常处理及任务切换分析(基于qemu+gdb跟踪调试)_Dingjun798077632的博客-CSDN博客中,有提到FreeRTOS\Source\portable\GCC\RISC-V\portASM.S文件中通过宏portcontextSAVE_CONTEXT_INTERNAL保存上下文代码有点漏洞:portcontextSAVE_CONTEXT_INTERNAL最后将sp保存到pxCurrentTCB->pxTopOfStack中,在创建任务和调用vTaskStartScheduler()之前pxCurrentTCB必然是个空指针,此时触发异常处理,跳转到异常函数处理入口freertos_risc_v_trap_handler处执行,通过宏portcontextSAVE_CONTEXT_INTERNAL保存上下文又会触发异常,如此反复。这样上一篇文章RISC-V架构栈帧分析与栈回溯实现_Dingjun798077632的博客-CSDN博客中增加的栈回溯等相关代码在启动过程前半部分不起效,但实际开项目中,启动过程需要做大量初始化动作,启动过程是相对比较复杂,比较容易出错的,为方便以后问题调试,最好是能够解决该问题,下面就该问题分析。
首先,我们先验证上面提出的问题是否存在,还是基于之前的文件FreeRTOS\Demo\RISC-V-Qemu-virt_GCC\main_blinky.c,在main_blinky函数中xTaskCreate之前调用test_fun_c,从而触发非法指令异常,代码如下:
还是用asm volatile(".word 0x1234567");触发异常
编译后启用gdb调试,在freertos_risc_v_trap_handler和synchronous_exception函数入口处设置断点,运行结果如下:
对照反汇编代码可以看到,第一次触发异常时mepc为0x80000660刚好为test_fun_b中的非法指令:
继续运行到portcontextSAVE_CONTEXT_INTERNAL保存上下文时又触发异常,触发异常时的mepc为0x8000398c(实际上mepc并不一定指向对应的sw指令位置,Load和Store一般属于非精确异常,上一篇文章中已解释),对照反汇编和代码,刚好是想pxCurrentTCB指针保存数据时:
如上所示,在异常处理保存上下文过程中,又触发异常,导致再次跳回到异常入口处,又保存上下文,如此不断循环,无法正在运行到接下来的synchronous_exception异常处理函数处。整个现象和之前的推测完全一直。
portcontextSAVE_CONTEXT_INTERNAL保存上下文数据基本都是放入触发异常时的栈中,只有最好需要把sp指针,保存到另一个位置,之后切换到中断栈(前面的文章中已经分析过了),这样也就是只要在启动过程中准备一个位置存放sp指针就可以。
之前文章中分析启动过程中,有提到FreeRTOS\Demo\RISC-V-Qemu-virt_GCC\start.S中设置的初始化sp指针为_stack_top,_stack_top在fake_rom.lds链接脚本中定义的。
链接脚本FreeRTOS\Demo\RISC-V-Qemu-virt_GCC\fake_rom.lds
_stack_top为栈的高地址,我们增加一行_stack_end = .;用于标记栈的低地址,之后用该地址做为上文的启动过程的异常处理portcontextSAVE_CONTEXT_INTERNAL保存sp指针的地址(栈向下增长,也就是从_stack_top位置递减,_stack_end位置不会被用的,除非设置的栈太小,栈要溢出了)。通过tp寄存器(之前分析过,riscv64-unknown-elf-gcc编译器中没使用tp寄存器),刚启动初始化把tp初始化为_stack_end的地址,在运行到xPortStartFirstTask(启动第一个任务,也就是开启任务调度)时把tp设置为pxCurrentTCB。portcontextSAVE_CONTEXT_INTERNAL保存上下文时,时直接将sp值保存到tp指针指向的内存。同时在函数vTaskSwitchContext()切换pxCurrentTCB指针时,更新tp指针。
代码修改后代码如下,宏RISC_V_START_TRAP_HANDLER中为新增的代码:
1. 修改FreeRTOS\Demo\RISC-V-Qemu-virt_GCC\start.S文件,将pxCurrentTCB指向_stack_end地址:
2. FreeRTOS\Source\portable\GCC\RISC-V\portContext.h修改,用tp代替pxCurrentTCB:
3. FreeRTOS\Source\portable\GCC\RISC-V\portASM.S修改,启动调度时,设置tp=pxCurrentTCB,增加一个汇编函数processed_switchcontext,vTaskSwitchContext中调用该函数更新tp指针
4. FreeRTOS\Source\tasks.c中,vTaskSwitchContext调用processed_switchcontext更新tp指针:
接着修改上一篇文章中增加的freertos_risc_v_application_exception_handler函数,在if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED )的else中增加如下代码:
void freertos_risc_v_application_exception_handler(UBaseType_t mcause, UBaseType_t mepc,
UBaseType_t mtval, UBaseType_t mstatus, StackType_t * pxTopOfStack)
{
... ...
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED )
{
... ...
}
else
{
xprintf("The start bactrace:\n");
extern UBaseType_t _stack_end[];
extern UBaseType_t _stack_top[];
xprintf("Ther startup stack_end:%p, StackTop:%p\n", _stack_end, _stack_top);
#if 0
while ((fp > _stack_end) && fp < _stack_top) {
xprintf("fun%d: FramePointer:0x%lx\tReturnAddr:0x%lx\n", i++, fp, *(fp-1));
fp = (UBaseType_t*)*(fp-2);
}
#endif
xprintf("Ther startup Stack:\n");
i = 0;
for (pStack = pxTopOfStack + portCONTEXT_COUNT; pStack < _stack_top; pStack++)
{
reg = *(UBaseType_t*)pStack;
if (i++ % 8 == 0) {
xprintf("\n%p: 0x%lx ", pStack, reg);
} else{
xprintf("0x%lx ", reg);
}
}
xprintf("\n");
}
loop:
xprintf("Freertos risc-v application exception handler end\n");
//while(1);
}
修测试改代码,在函数main_blinky()和,xTimerCreateTimerTask主动触发两个异常。上面freertos_risc_v_application_exception_handler() 函数最后的while(1)去掉了,也就是退出异常时,回到触发异常时的pc+4位置继续执行。
FreeRTOS\Source\timers.c
编译运行,
可以看到第一次触发异常在main_blinky中,推出异常处理后打印了“Hello main_blinky”,接着第二次触发异常,
测试结果符合预期,可以根据栈数据对照汇编找出函数调用关系。退出异常处理后,创建的两个任务正常运行。
修改后的代码发生中断和异常时,portcontextSAVE_CONTEXT_INTERNAL处理,保存sp指针时,相对于先原来的代码少了一条load指令,也就是保存上下文和恢复上下文处理更快了。