该文章 中讲述了 FreeRTOS RISC-V-Qemu-virt_GCC 的 启动流程,本篇文章讲述一下 运行了流程
我将 RISC-V-Qemu-virt_GCC 做了一些改动
FreeRTOS/Demo/RISC-V-Qemu-virt_GCC/main_blinky.c 改为如下
static void prvQueueSendTask( void *pvParameters )
{
for( ;; )
{
vSendString( "1111" );
vTaskDelay(1000);
}
}
/*-----------------------------------------------------------*/
static void prvQueueReceiveTask( void *pvParameters )
{
for( ;; )
{
vSendString( "2222" );
vTaskDelay(1000);
}
}
/*-----------------------------------------------------------*/
int main_blinky( void )
{
vSendString( "Hello FreeRTOS!" );
/* Create the queue. */
/* Start the two tasks as described in the comments at the top of this
file. */
xTaskCreate( prvQueueReceiveTask, "Rx", configMINIMAL_STACK_SIZE * 2U, NULL,
mainQUEUE_RECEIVE_TASK_PRIORITY, NULL );
xTaskCreate( prvQueueSendTask, "Tx", configMINIMAL_STACK_SIZE * 2U, NULL,
mainQUEUE_SEND_TASK_PRIORITY, NULL );
vTaskStartScheduler();
return 0;
}
如何从代码编译角度查看系统中有多少任务
1. 系统创建的任务
grep portTASK_FUNCTION_PROTO * -nr
// 该实例中 系统创建了 两个 prvTimerTask 和 prvIdleTask
// prvTimerTask 在 freertos boot 时 被 用到 , 后来就没有进入该函数了,不知道该task是否被销毁
2. 用户 创建的任务
grep -w "xTaskCreate(" * -nr
// 该实例中 用户创建了 两个 prvQueueReceiveTask prvQueueSendTask
流程
我将按时间流走过的流程总结为如下
1. boot
2. fist task // prvTimerTask
3. mtimer interrupt // freertos_risc_v_trap_handler xTaskIncrementTick processed_source
4. fist task & ecall // prvTimerTask ecall freertos_risc_v_trap_handler vTaskSwitchContext processed_source
5. second task & ecall // prvQueueReceiveTask ecall freertos_risc_v_trap_handler vTaskSwitchContext processed_source
6. third task & ecall // prvQueueSendTask ecall freertos_risc_v_trap_handler vTaskSwitchContext processed_source
7. fourth task & ecall // prvIdleTask ??? freertos_risc_v_trap_handler vTaskSwitchContext
8. third task ...
当前配置里面 不会在 mtimer interrupt 中 调度, 只能用户任务主动调度
- 1.boot
_start
main
main_blinky
vTaskStartScheduler
xPortStartScheduler
xPortStartFirstTask
...
ret
- 2.fist task
任务1:prvTimerTask // 该任务开始执行后, 会时不时的进入 machine timer 中断
prvProcessTimerOrBlockTask
刚进入就产生 mtimer 中断
- 3.mtimer interrupt
freertos_risc_v_trap_handler // mtimer 中断导致进入
保存任务1
test_if_asynchronous
handle_asynchronous
test_if_mtimer
xTaskIncrementTick
processed_source
mret
- 4.fist task & ecall
任务1:prvTimerTask
prvProcessTimerOrBlockTask
.. 中间有很多 mtimer 中断
portYIELD_WITHIN_API/ecall
freertos_risc_v_trap_handler // ecall 中断导致进入
保存任务1
test_if_asynchronous
handle_synchronous
test_if_environment_call
vTaskSwitchContext
taskSELECT_HIGHEST_PRIORITY_TASK
// 这里切换 pxCurrentTCB 到 任务2
processed_source
mret
- 5.second task & ecall
任务2:prvQueueReceiveTask
vSendString("2222");
vTaskDelay/portYIELD_WITHIN_API/ecall
freertos_risc_v_trap_handler // ecall 中断导致进入
保存任务1
test_if_asynchronous
handle_synchronous
test_if_environment_call
vTaskSwitchContext
taskSELECT_HIGHEST_PRIORITY_TASK
// 这里切换 pxCurrentTCB 到 任务3
processed_source
mret
- 6.third task & ecall
任务3:prvQueueSendTask
vSendString("1111");
vTaskDelay/portYIELD_WITHIN_API/ecall
freertos_risc_v_trap_handler // ecall 中断导致进入
保存任务1
test_if_asynchronous
handle_synchronous
test_if_environment_call
vTaskSwitchContext
taskSELECT_HIGHEST_PRIORITY_TASK
// 这里切换 pxCurrentTCB 到 任务4
processed_source
mret
- 7.fourth task & ecall
任务4:prvIdleTask
// 进来了很多 mtimer 中断
// 一次 taskYIELD/ecall
freertos_risc_v_trap_handler // ecall 中断导致进入
保存任务1
test_if_asynchronous
handle_synchronous
test_if_environment_call
vTaskSwitchContext
taskSELECT_HIGHEST_PRIORITY_TASK
// 这里切换 pxCurrentTCB 到 任务3
processed_source
mret
- 8.third task …
任务3:prvQueueSendTask
vSendString("1111");
vTaskDelay/portYIELD_WITHIN_API/ecall
...
ecall 后的 freertos_risc_v_trap_handler
1. ecall // 此时的 寄存器状态为A(包括 ra sp gp tp t0 t1 t2 s0 s1 a0 a1 a2 a3 a4 a5 a6 a7 s2 s3 s4 s5 s6 s7 s8 s9 s10 s11 t3 t4 t5 t6 pc )
此时硬件做动作
mcause 被设置成 0xb
mepc 被设置成 0x80001d28
2. freertos_risc_v_trap_handler // 此时寄存器状态为B // A和B 除了pc,其他都一样
freertos_risc_v_trap_handler
// 1. 保存 寄存器信息 到 sp
addi sp, sp, -portCONTEXT_SIZE // sp = sp - 120 // 可以保存 30个 寄存器
store_x x1, 1 * portWORD_SIZE( sp ) // 保存x1(第一个寄存器) 到 sp , sp = sp +4 // sw ra,4(sp)
store_x x5, 2 * portWORD_SIZE( sp ) // 保存x2(第二个寄存器)
...
store_x x31, 28 * portWORD_SIZE( sp ) // 保存x31(第28个寄存器)
csrr t0, mstatus
store_x t0, 29 * portWORD_SIZE( sp ) // 保存 mstatus(第29个寄存器)到sp
portasmSAVE_ADDITIONAL_REGISTERS // 用户自定义的 保存寄存器 的 指令,一般为空
// 2. 保存sp 到 当前的 TCB
load_x t0, pxCurrentTCB
store_x sp, 0( t0 )
// 保存 sp 到 pxCurrentTCB的第一个成员 , 即
// volatile StackType_t * pxTopOfStack
// 32bit x/30xw pxCurrentTCB->pxTopOfStack
// 64bit x/30xg pxCurrentTCB->pxTopOfStack
// 3. 此时 保存完毕,开始处理异常
// 4. 读 mcause mepc
csrr a0, mcause
csrr a1, mepc
// 5. 判断 同步还是异步
test_if_asynchronous:
srli a2, a0, __riscv_xlen - 1 // 高 16 位存储到 a2
beq a2, x0, handle_synchronous // 如果 a2 等0 ,跳转到 handle_synchronous , 实际上ecall ,会走 handle_synchronous
// 6. 处理同步
handle_synchronous:
addi a1, a1, 4 // 处理返回地址 ,返回地址 = mepc +4
store_x a1, 0( sp ) // 填充到 返回地址 到 sp 指向的内存
// 7. 判断是不是 ecall
test_if_environment_call:
li t0, 11 // 将 11 放到 t0 // 11 表示 Environment call from M-mode
bne a0, t0, is_exception // 如果 a0(即mcause)等于 t0(即11) , 那么就 不跳到 is_exception ,而是继续往下
-------------------------------------------------------------------------------------------//此时切换了sp
// 从 0x80082cf8 -> 0x80091da0
// 为什么在此时才切换sp
// 往前, 刚保存完 返回地址的recipe , 又往后 做了 三个汇编指令(该三条汇编指令用不到sp,也不会影响sp)
// 往后, 要用sp 调用 C代码vTaskSwitchContext , vTaskSwitchContext 中会改动 sp
// 所以 在此时 切换sp
load_x sp, xISRStackTop // 设置 sp 为 xISRStackTop
-------------------------------------------------------------------------------------------
// 8. 选择下一个 TCB , 将其 赋值给 pxCurrentTCB
jal vTaskSwitchContext
taskSELECT_HIGHEST_PRIORITY_TASK/__clzSI2(UWtype x)
// __clzSI2 来自于crosstool-ng-crosstool-ng-1.24.0/.build/riscv64-unknown-elf/src/gcc/libgcc/libgcc2.c
// 参数x 为 7
count_leading_zeros (ret, x); // 这里切换了 pxCurrentTCB
// 9. load下个任务
j processed_source
// 获取(恢复) 下个任务 的 sp // 此时 恢复的 sp 中 保存的 是 寄存器状态 值
load_x t1, pxCurrentTCB // 保存 pxCurrentTCB 到 t1
load_x sp, 0( t1 ) // 从 TCB 中读取 sp
// 获取(恢复) 下个任务 的 mepc
load_x t0, 0( sp )
csrw mepc, t0
// 获取(恢复) 下个任务 的 mstatus
load_x t0, 29 * portWORD_SIZE( sp )
csrw mstatus, t0
// 恢复 各个寄存器
load_x x1, 1 * portWORD_SIZE( sp )
load_x x5, 2 * portWORD_SIZE( sp )
...
load_x x31, 28 * portWORD_SIZE( sp )
// 恢复 下个任务的 sp // 此时 恢复的 sp 中 保存的 是 运行状态的 函数堆栈 值
addi sp, sp, portCONTEXT_SIZE
// 返回任务
mret
// 该指令后,下一条指令就是 prvQueueReceiveTask 任务