RISC-V架构与FreeRTOS任务栈变化
- RISC-V
- 2024-05-10
- 299热度
- 0评论
栈的基本概念
在FreeRTOS中,每个任务有一个全局的tskTCB实例,pxCurrentTCB指针指向的是正在运行的任务实例,有三个和栈相关的变量pxTopOfStack和pxStack,pxEndOfStack。
- pxTopOfStack指向当前堆栈栈顶,随着进栈出栈,pxTopOfStack指向的位置是会变化的;
-
pxStack指向当前堆栈的起始位置,一经分配后,堆栈起始位置就固定了,不会被改变了。
-
pxEndOfStack 标记着栈结束位置,任务创建完成后就固定了
adress:0+ulStackDepth ------------------- pxEndOfStack(栈底)
|XXXXXXXXXXXXXXXXXX|
adress:0+x --------------------pxTopOfStack(栈顶)
| |
| |
| |
adress:0 ------------------- pxStack
任务栈空间开辟
xTaskCreate
1. 分配一个栈空间
/* Allocate space for the stack used by the task being created. */
pxStack = pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
pxNewTCB->pxStack = pxStack;
2. 初始化新任务
prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
2.1 更新pxTopOfStack
pxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] );
pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
2.2 初始化栈
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
初始化栈后,pxNewTCB->pxTopOfStack = pxTopOfStack - Context Size
初始化栈
pxPortInitialiseStack:
#if defined(CONFIG_ARCH_RISCV_FPU) && !defined(CONFIG_SAVE_RISCV_FPU_CONTEXT_IN_TCB)
addi a0, a0, -(FPU_CONTEXT_SIZE)
/* set fpu rounding mode field to 0(Round to Nearest, ties to Even) when create new thread,
* otherwise it\'s possible that the rounding mode filed is invalid and
* cause the illegal instruction exception when restore the fcsr.
*/
store_x x0, FPU_CTX_FCSR_OFFSET(a0)
#endif
addi a0, a0, -portWORD_SIZE
addi a0, a0, -portWORD_SIZE
addi a0, a0, -portWORD_SIZE
addi a0, a0, -portWORD_SIZE
csrr t0, mstatus /* Obtain current mstatus value. */
andi t0, t0, ~0x8 /* Ensure interrupts are disabled when the stack is restored within an ISR. Required when a task is created after the schedulre has been started, otherwise interrupts would be disabled anyway. */
addi t1, x0, 0x188 /* Generate the value 0x1880, which are the MPIE and MPP bits to set in mstatus. */
slli t1, t1, 4
or t0, t0, t1 /* Set MPIE and MPP bits in mstatus value. */
#ifdef CONFIG_SAVE_RISCV_FPU_CONTEXT_IN_TCB
/* set FS filed(in thread stack) to initial state for skip save/restore FPU context operation */
li t1, SR_FS
li t2, SR_FS_INITIAL
not t3, t1
and t0, t0, t3
or t0, t0, t2
#endif
addi a0, a0, -portWORD_SIZE
store_x t0, 0(a0) /* mstatus onto the stack. */
addi a0, a0, -(22 * portWORD_SIZE) /* Space for registers x11-x31. */
store_x a2, 0(a0) /* Task parameters (pvParameters parameter) goes into register X10/a0 on the stack. */
addi a0, a0, -(6 * portWORD_SIZE) /* Space for registers x5-x9. */
la t0, portTASK_RETURN_ADDRESS
store_x t0, 0(a0) /* Return address onto the stack, could be portTASK_RETURN_ADDRESS */
addi t0, x0, portasmADDITIONAL_CONTEXT_SIZE /* The number of chip specific additional registers. */
chip_specific_stack_frame: /* First add any chip specific registers to the stack frame being created. */
beq t0, x0, 1f /* No more chip specific registers to save. */
addi a0, a0, -portWORD_SIZE /* Make space for chip specific register. */
store_x x0, 0(a0) /* Give the chip specific register an initial value of zero. */
addi t0, t0, -1 /* Decrement the count of chip specific registers remaining. */
j chip_specific_stack_frame /* Until no more chip specific registers. */
1:
addi a0, a0, -portWORD_SIZE
store_x a1, 0(a0) /* mret value (pxCode parameter) onto the stack. */
ret
.endfunc
经过pxPortInitialiseStack初始化后存储布局如下:
第一个任务栈恢复
xPortStartFirstTask:
#if( portasmHAS_SIFIVE_CLINT != 0 )
/* If there is a clint then interrupts can branch directly to the FreeRTOS
trap handler. Otherwise the interrupt controller will need to be configured
outside of this file. */
la t0, freertos_risc_v_trap_handler
csrw mtvec, t0
#endif /* portasmHAS_CLILNT */
load_x t0, pxCurrentTCB /* 加载当前TCB */
load_x sp, 0 * portWORD_SIZE( t0 ) /* 获取当前TCB的栈空间*/
load_x x1, 0 * portWORD_SIZE( sp ) /* 加载返回地址,即任务入口函数 */
load_x x6, 3 * portWORD_SIZE( sp ) /* t1 */
load_x x7, 4 * portWORD_SIZE( sp ) /* t2 */
load_x x8, 5 * portWORD_SIZE( sp ) /* s0/fp */
load_x x9, 6 * portWORD_SIZE( sp ) /* s1 */
load_x x10, 7 * portWORD_SIZE( sp ) /* a0 */
load_x x11, 8 * portWORD_SIZE( sp ) /* a1 */
load_x x12, 9 * portWORD_SIZE( sp ) /* a2 */
load_x x13, 10 * portWORD_SIZE( sp ) /* a3 */
load_x x14, 11 * portWORD_SIZE( sp ) /* a4 */
load_x x15, 12 * portWORD_SIZE( sp ) /* a5 */
load_x x16, 13 * portWORD_SIZE( sp ) /* a6 */
load_x x17, 14 * portWORD_SIZE( sp ) /* a7 */
load_x x18, 15 * portWORD_SIZE( sp ) /* s2 */
load_x x19, 16 * portWORD_SIZE( sp ) /* s3 */
load_x x20, 17 * portWORD_SIZE( sp ) /* s4 */
load_x x21, 18 * portWORD_SIZE( sp ) /* s5 */
load_x x22, 19 * portWORD_SIZE( sp ) /* s6 */
load_x x23, 20 * portWORD_SIZE( sp ) /* s7 */
load_x x24, 21 * portWORD_SIZE( sp ) /* s8 */
load_x x25, 22 * portWORD_SIZE( sp ) /* s9 */
load_x x26, 23 * portWORD_SIZE( sp ) /* s10 */
load_x x27, 24 * portWORD_SIZE( sp ) /* s11 */
load_x x28, 25 * portWORD_SIZE( sp ) /* t3 */
load_x x29, 26 * portWORD_SIZE( sp ) /* t4 */
load_x x30, 27 * portWORD_SIZE( sp ) /* t5 */
load_x x31, 28 * portWORD_SIZE( sp ) /* t6 */
load_x x5, 29 * portWORD_SIZE( sp ) /* Initial mstatus into x5 (t0) */
addi x5, x5, 0x08 /* Set MIE bit so the first task starts with interrupts enabled - required as returns with ret not eret. */
csrrw x0, mstatus, x5 /* Interrupts enabled from here! */
load_x x5, 2 * portWORD_SIZE( sp ) /* Initial x5 (t0) value. */
addi sp, sp, portCONTEXT_SIZE /*销毁栈空间,SP已经更新*/
#if 0
load_x t0, pxCurrentTCB /* Load pxCurrentTCB. */
store_x sp, 0( t0 ) /* Write sp to first TCB member. */
#endif
更新栈顶指针,上面两句不执行也行,因为任务执行时,sp的值已经更新了,任务用的是SP,
而不是pxCurrentTCB->pxTopOfStack。
ret
.endfunc
/*-----------------------------------------------------------*/
任务切换栈变化
clic_interrupt_handler:
1. 任务在运行过程中,被中断打断,先分配一块栈帧,保存现场。
addi sp, sp, -portCONTEXT_SIZE
store_x x1, 1 * portWORD_SIZE( sp )
store_x x5, 2 * portWORD_SIZE( sp )
store_x x6, 3 * portWORD_SIZE( sp )
store_x x7, 4 * portWORD_SIZE( sp )
store_x x8, 5 * portWORD_SIZE( sp )
store_x x9, 6 * portWORD_SIZE( sp )
store_x x10, 7 * portWORD_SIZE( sp )
store_x x11, 8 * portWORD_SIZE( sp )
store_x x12, 9 * portWORD_SIZE( sp )
store_x x13, 10 * portWORD_SIZE( sp )
store_x x14, 11 * portWORD_SIZE( sp )
store_x x15, 12 * portWORD_SIZE( sp )
store_x x16, 13 * portWORD_SIZE( sp )
store_x x17, 14 * portWORD_SIZE( sp )
store_x x18, 15 * portWORD_SIZE( sp )
store_x x19, 16 * portWORD_SIZE( sp )
store_x x20, 17 * portWORD_SIZE( sp )
store_x x21, 18 * portWORD_SIZE( sp )
store_x x22, 19 * portWORD_SIZE( sp )
store_x x23, 20 * portWORD_SIZE( sp )
store_x x24, 21 * portWORD_SIZE( sp )
store_x x25, 22 * portWORD_SIZE( sp )
store_x x26, 23 * portWORD_SIZE( sp )
store_x x27, 24 * portWORD_SIZE( sp )
store_x x28, 25 * portWORD_SIZE( sp )
store_x x29, 26 * portWORD_SIZE( sp )
store_x x30, 27 * portWORD_SIZE( sp )
store_x x31, 28 * portWORD_SIZE( sp )
store_x x3, 31 * portWORD_SIZE( sp )
store_x x4, 32 * portWORD_SIZE( sp )
addi a0, sp, portCONTEXT_SIZE
store_x a0, 30 * portWORD_SIZE( sp )
csrr a0, mepc
store_x a0, 0 * portWORD_SIZE( sp )
csrr t0, mscratch
store_x t0, 33 * portWORD_SIZE( sp )
csrr t0, mstatus
store_x t0, 29 * portWORD_SIZE( sp )
2. 因为分配了一段栈空间,所以需要将栈顶指针更新到pxCurrentTCB->pxTopOfStack
到这里,这样任务的上下文就保存好了,可以运行中断函数,或切换到其他任务。
lw t0, pxCurrentTCB /* Load pxCurrentTCB. */
sw sp, 0( t0 ) /* Write sp to first TCB member. */
3. 加载中断的栈空间,执行中断处理函数。
load_x sp, xISRStackTop
call enter_interrupt_handler
csrr a0, mcause
andi a0, a0, 0x7FF /* If the CLIC support more than 2048 interrupts, we need to use the mask value saved in register */
call irq_core_handle_root_ic_irq
call exit_interrupt_handler
4. 获取任务的控制块,这里的任务可能是原来的任务,也可能是新的任务
在中断处理函数中,会进行调度,更新pxCurrentTCB,之后获取pxCurrentTCB->pxTopOfStack
这样就找到了新任务的栈空间
lw t1, pxCurrentTCB /* Load pxCurrentTCB. */
lw sp, 0( t1 ) /* Read sp from first TCB member. */
5. 栈空间的第一个保存的开始要运行的地址,加载出来写入到mepc中
load_x t0, 0 * portWORD_SIZE( sp )
csrw mepc, t0
/* Load mstatus with the interrupt enable bits used by the task. */
load_x t0, 29 * portWORD_SIZE( sp )
csrw mstatus, t0
load_x t0, 33 * portWORD_SIZE( sp )
csrw mscratch, t0
load_x x1 , 1 * portWORD_SIZE( sp )
load_x x5 , 2 * portWORD_SIZE( sp ) /* t0 */
load_x x6 , 3 * portWORD_SIZE( sp ) /* t1 */
load_x x7 , 4 * portWORD_SIZE( sp ) /* t2 */
load_x x8 , 5 * portWORD_SIZE( sp ) /* s0/fp */
load_x x9 , 6 * portWORD_SIZE( sp ) /* s1 */
load_x x10, 7 * portWORD_SIZE( sp ) /* a0 */
load_x x11, 8 * portWORD_SIZE( sp ) /* a1 */
load_x x12, 9 * portWORD_SIZE( sp ) /* a2 */
load_x x13, 10 * portWORD_SIZE( sp ) /* a3 */
load_x x14, 11 * portWORD_SIZE( sp ) /* a4 */
load_x x15, 12 * portWORD_SIZE( sp ) /* a5 */
load_x x16, 13 * portWORD_SIZE( sp ) /* a6 */
load_x x17, 14 * portWORD_SIZE( sp ) /* a7 */
load_x x18, 15 * portWORD_SIZE( sp ) /* s2 */
load_x x19, 16 * portWORD_SIZE( sp ) /* s3 */
load_x x20, 17 * portWORD_SIZE( sp ) /* s4 */
load_x x21, 18 * portWORD_SIZE( sp ) /* s5 */
load_x x22, 19 * portWORD_SIZE( sp ) /* s6 */
load_x x23, 20 * portWORD_SIZE( sp ) /* s7 */
load_x x24, 21 * portWORD_SIZE( sp ) /* s8 */
load_x x25, 22 * portWORD_SIZE( sp ) /* s9 */
load_x x26, 23 * portWORD_SIZE( sp ) /* s10 */
load_x x27, 24 * portWORD_SIZE( sp ) /* s11 */
load_x x28, 25 * portWORD_SIZE( sp ) /* t3 */
load_x x29, 26 * portWORD_SIZE( sp ) /* t4 */
load_x x30, 27 * portWORD_SIZE( sp ) /* t5 */
load_x x31, 28 * portWORD_SIZE( sp ) /* t6 */
6.上下文已经恢复,销毁掉栈空间
addi sp, sp, portCONTEXT_SIZE
7. 下面两句,可以不执行,因为mret之后就切到任务中运行了,sp已经更新。
#if 0
load_x t0, pxCurrentTCB /* Load pxCurrentTCB. */
store_x sp, 0( t0 ) /* Write sp to first TCB member. */
#endif
8. 返回到任务中运行,
mret
pxCurrentTCB->pxTopOfStack的变化
- pxPortInitialiseStack初始化栈,分配一段栈空间,设置pxCurrentTCB->pxTopOfStack初值。这段栈空间在任务得到运行时会将pxCurrentTCB->pxTopOfStack赋值为sp,接着恢复上下文运行。任务得到运行有两种场景
- xPortStartFirstTask:这种任务是优先级比较高,系统初始化完成后会挑选第一个任务进行运行,在该函数中会恢复上下文,接着更新sp的值进而销毁栈空间,但是没有更新pxCurrentTCB->pxTopOfStack的值(该值是否更新不影响,sp已经更新了)。
- 其他任务让出得到调度:这种场景是得到调度机会,在中断中进行恢复上下文(上面代码的第4点点开始),
- 运行过程中被中断打断(可能是中断,也有可能是时间片用完),分配一段栈空间用于保存上下文信息。上面代码第1点就是开辟一段栈空间,进行保存上下文,保存上下文后,因为可能会让出调度,因此需要更新pxCurrentTCB->pxTopOfStack,上述代码的第二点。
为什么只看到第1点中SP开辟了,更新到pxCurrentTCB->pxTopOfStack后,没有见哪里归还,是否有栈泄露?
答:在任务再次得到运行的时候,会进行归还,上面第6点就是,SP进行的增加,释放的栈空间,只是没有写到pxCurrentTCB->pxTopOfStack,而当任务在运行过程中被打断进入中断后,sp是已经释放后的sp,而即使在第1点中开辟空间,也是归还后,再开辟的,所以不存在泄露。
跟踪一个任务运行栈的变化实例
初始化栈
初始化时开辟了一个栈空间,栈帧指向0x6007af78位置。