FreeRTOS
riscv 通用寄存器
CPU中包含32个通用寄存器,有时候也会被称为通用寄存器文件,如图1所示。通用寄存器的命名方式为X0-X31。其中第一个寄存器X0的值,被硬连线到0,因此值永远是0。其他寄存器X1-X31都是可读可写的。0-31也叫做索引号,索引号也可以理解为寄存器的地址,当指令需要调用通用寄存器时可以通过索引号查找。 对于32位系统,所有通用寄存器的宽度都是32bit,寄存器总个数也是32个。
PC(program counter)是程序计数器,也是一个寄存器。在CPU中PC寄存器并不和上述32个通用寄存器在一起,寄存器文件中不包含PC。PC的宽度和通用寄存器的宽度一样。XLEN的值一般跟RISC-V CPU架构有关系。 如果是32位架构的CPU,那么XLEN的值就是32。图1中XLEN-1 = 32-1 =31,即在一个通用寄存器中的最高位为31。在64位CPU中通用寄存器的宽度是64,同时PC宽度也是64位,最高位为64-1 =63。
寄存器别名
mtime的初始化
#define configCLINT_BASE_ADDRESS CLINT_CTRL_ADDR #define configMTIME_BASE_ADDRESS (CLINT_CTRL_ADDR + CLINT_MTIME) #define configMTIMECMP_BASE_ADDRESS (CLINT_CTRL_ADDR + CLINT_MTIMECMP)
在使用时基之前要先定义,而这个初始化应该是在FreeRTOS的任务调度之前完成,而实际,这件事做的确实很晚,是在vTaskStartScheduler();
函数里执行的,具体的调用路径:
vTaskStartScheduler(); //./FreeRTOS/Source/task.c | xPortStartScheduler(); //./FreeRTOS/Source/portable/GCC/RISC-V/port.c | vPortSetupTimerInterrupt(); //./FreeRTOS/Source/portable/GCC/RISC-V/port.c #if( configMTIME_BASE_ADDRESS != 0 ) && ( configMTIMECMP_BASE_ADDRESS != 0 ) void vPortSetupTimerInterrupt( void ) { uint32_t ulCurrentTimeHigh, ulCurrentTimeLow; volatile uint32_t * const pulTimeHigh = ( volatile uint32_t * const ) ( ( configMTIME_BASE_ADDRESS ) + 4UL ); /* 8-byte typer so high 32-bit word is 4 bytes up. */ volatile uint32_t * const pulTimeLow = ( volatile uint32_t * const ) ( configMTIME_BASE_ADDRESS ); volatile uint32_t ulHartId; __asm volatile( "csrr %0, mhartid" : "=r"( ulHartId ) ); pullMachineTimerCompareRegister = ( volatile uint64_t * ) ( ullMachineTimerCompareRegisterBase + ( ulHartId * sizeof( uint64_t ) ) ); do { ulCurrentTimeHigh = *pulTimeHigh; ulCurrentTimeLow = *pulTimeLow; } while( ulCurrentTimeHigh != *pulTimeHigh ); ullNextTime = ( uint64_t ) ulCurrentTimeHigh; ullNextTime <<= 32ULL; /* High 4-byte word is 32-bits up. */ ullNextTime |= ( uint64_t ) ulCurrentTimeLow; ullNextTime += ( uint64_t ) uxTimerIncrementsForOneTick; *pullMachineTimerCompareRegister = ullNextTime; /* Prepare the time to use after the next tick interrupt. */ ullNextTime += ( uint64_t ) uxTimerIncrementsForOneTick; } #endif /* ( configMTIME_BASE_ADDRESS != 0 ) && ( configMTIME_BASE_ADDRESS != 0 ) */
task管理
portable/GCC/RISC-V/portASM.S
.global xPortStartFirstTask .global freertos_risc_v_trap_handler .global pxPortInitialiseStack .extern pxCurrentTCB .extern ulPortTrapHandler .extern vTaskSwitchContext .extern xTaskIncrementTick .extern Timer_IRQHandler .extern pullMachineTimerCompareRegister .extern pullNextTime .extern uxTimerIncrementsForOneTick /* size_t type so 32-bit on 32-bit core and 64-bits on 64-bit core. */ .extern xISRStackTop .extern portasmHANDLE_INTERRUPT
xPortStartFirstTask
portable/IAR/RISC-V/chip_specific_extensions/RV32I_CLINT_no_extensions/freertos_risc_v_chip_specific_extensions.h:57:
#define portasmHAS_SIFIVE_CLINT 1
1 #if( portasmHAS_SIFIVE_CLINT != 0 ) 2 /* If there is a clint then interrupts can branch directly to the FreeRTOS 3 trap handler. Otherwise the interrupt controller will need to be configured 4 outside of this file. */ 5 la t0, freertos_risc_v_trap_handler 6 csrw mtvec, t0 7 #endif /* portasmHAS_CLILNT */ 8 9 load_x sp, pxCurrentTCB /* Load pxCurrentTCB. */ 10 load_x sp, 0( sp ) /* Read sp from first TCB member. */ 11 12 load_x x1, 0( sp ) /* Note for starting the scheduler the exception return address is used as the function return address. */ 13 14 portasmRESTORE_ADDITIONAL_REGISTERS /* Defined in freertos_risc_v_chip_specific_extensions.h to restore any registers unique to the RISC-V implementation. */ 15 16 load_x x6, 3 * portWORD_SIZE( sp ) /* t1 */ 17 load_x x7, 4 * portWORD_SIZE( sp ) /* t2 */ 18 load_x x8, 5 * portWORD_SIZE( sp ) /* s0/fp */ 19 load_x x9, 6 * portWORD_SIZE( sp ) /* s1 */ 20 load_x x10, 7 * portWORD_SIZE( sp ) /* a0 */ 21 load_x x11, 8 * portWORD_SIZE( sp ) /* a1 */ 22 load_x x12, 9 * portWORD_SIZE( sp ) /* a2 */ 23 load_x x13, 10 * portWORD_SIZE( sp ) /* a3 */ 24 load_x x14, 11 * portWORD_SIZE( sp ) /* a4 */ 25 load_x x15, 12 * portWORD_SIZE( sp ) /* a5 */ 26 load_x x16, 13 * portWORD_SIZE( sp ) /* a6 */ 27 load_x x17, 14 * portWORD_SIZE( sp ) /* a7 */ 28 load_x x18, 15 * portWORD_SIZE( sp ) /* s2 */ 29 load_x x19, 16 * portWORD_SIZE( sp ) /* s3 */ 30 load_x x20, 17 * portWORD_SIZE( sp ) /* s4 */ 31 load_x x21, 18 * portWORD_SIZE( sp ) /* s5 */ 32 load_x x22, 19 * portWORD_SIZE( sp ) /* s6 */ 33 load_x x23, 20 * portWORD_SIZE( sp ) /* s7 */ 34 load_x x24, 21 * portWORD_SIZE( sp ) /* s8 */ 35 load_x x25, 22 * portWORD_SIZE( sp ) /* s9 */ 36 load_x x26, 23 * portWORD_SIZE( sp ) /* s10 */ 37 load_x x27, 24 * portWORD_SIZE( sp ) /* s11 */ 38 load_x x28, 25 * portWORD_SIZE( sp ) /* t3 */ 39 load_x x29, 26 * portWORD_SIZE( sp ) /* t4 */ 40 load_x x30, 27 * portWORD_SIZE( sp ) /* t5 */ 41 load_x x31, 28 * portWORD_SIZE( sp ) /* t6 */ 42 43 load_x x5, 29 * portWORD_SIZE( sp ) /* Initial mstatus into x5 (t0) */ 44 addi x5, x5, 0x08 /* Set MIE bit so the first task starts with interrupts enabled - required as returns with ret not eret. */ 45 csrrw x0, mstatus, x5 /* Interrupts enabled from here! */ 46 load_x x5, 2 * portWORD_SIZE( sp ) /* Initial x5 (t0) value. */ 47 48 addi sp, sp, portCONTEXT_SIZE 49 ret 50 .endfunc 51 /*-----------------------------------------------------------*/
第8~9行,将mtvec的值设置为freertos_risc_v_trap_handler()函数的地址,即中断(异常)入口函数为freertos_risc_v_trap_handler()。
后面的代码会将当前TCB里保存的寄存器值恢复到对应的寄存器,当xPortStartFirstTask()函数返回后就会执行当前(pxCurrentTCB所指的)任务。
到这里,我们就可以知道接下来的重点就是freertos_risc_v_trap_handler()函
freertos_risc_v_trap_handle
1 .func 2 freertos_risc_v_trap_handler: 3 addi sp, sp, -portCONTEXT_SIZE 4 store_x x1, 1 * portWORD_SIZE( sp ) 5 store_x x5, 2 * portWORD_SIZE( sp ) 6 store_x x6, 3 * portWORD_SIZE( sp ) 7 store_x x7, 4 * portWORD_SIZE( sp ) 8 store_x x8, 5 * portWORD_SIZE( sp ) 9 store_x x9, 6 * portWORD_SIZE( sp ) 10 store_x x10, 7 * portWORD_SIZE( sp ) 11 store_x x11, 8 * portWORD_SIZE( sp ) 12 store_x x12, 9 * portWORD_SIZE( sp ) 13 store_x x13, 10 * portWORD_SIZE( sp ) 14 store_x x14, 11 * portWORD_SIZE( sp ) 15 store_x x15, 12 * portWORD_SIZE( sp ) 16 store_x x16, 13 * portWORD_SIZE( sp ) 17 store_x x17, 14 * portWORD_SIZE( sp ) 18 store_x x18, 15 * portWORD_SIZE( sp ) 19 store_x x19, 16 * portWORD_SIZE( sp ) 20 store_x x20, 17 * portWORD_SIZE( sp ) 21 store_x x21, 18 * portWORD_SIZE( sp ) 22 store_x x22, 19 * portWORD_SIZE( sp ) 23 store_x x23, 20 * portWORD_SIZE( sp ) 24 store_x x24, 21 * portWORD_SIZE( sp ) 25 store_x x25, 22 * portWORD_SIZE( sp ) 26 store_x x26, 23 * portWORD_SIZE( sp ) 27 store_x x27, 24 * portWORD_SIZE( sp ) 28 store_x x28, 25 * portWORD_SIZE( sp ) 29 store_x x29, 26 * portWORD_SIZE( sp ) 30 store_x x30, 27 * portWORD_SIZE( sp ) 31 store_x x31, 28 * portWORD_SIZE( sp ) 32 33 csrr t0, mstatus /* Required for MPIE bit. */ 34 store_x t0, 29 * portWORD_SIZE( sp ) 35 36 portasmSAVE_ADDITIONAL_REGISTERS /* Defined in freertos_risc_v_chip_specific_extensions.h to save any registers unique to the RISC-V implementation. */ 37 38 load_x t0, pxCurrentTCB /* Load pxCurrentTCB. */ 39 store_x sp, 0( t0 ) /* Write sp to first TCB member. */ 40 41 csrr a0, mcause 42 csrr a1, mepc 43 44 test_if_asynchronous: 45 srli a2, a0, __riscv_xlen - 1 /* MSB of mcause is 1 if handing an asynchronous interrupt - shift to LSB to clear other bits. */ 46 beq a2, x0, handle_synchronous /* Branch past interrupt handing if not asynchronous. */ 47 store_x a1, 0( sp ) /* Asynch so save unmodified exception return address. */ 48 49 handle_asynchronous: 50 /* TODO: 判断是定时器中断还是其他(外部)中断 */ 51 load_x sp, xISRStackTop /* Switch to ISR stack before function call. */ 52 call xPortClearTimerIntPending 53 jal xTaskIncrementTick 54 beqz a0, processed_source /* Don't switch context if incrementing tick didn't unblock a task. */ 55 jal vTaskSwitchContext 56 #jal portasmHANDLE_INTERRUPT /* Jump to the interrupt handler if there is no CLINT or if there is a CLINT and it has been determined that an external interrupt is pending. */ 57 j processed_source 58 59 handle_synchronous: 60 addi a1, a1, 4 /* Synchronous so updated exception return address to the instruction after the instruction that generated the exeption. */ 61 store_x a1, 0( sp ) /* Save updated exception return address. */ 62 63 test_if_environment_call: 64 li t0, 11 /* 11 == environment call. */ 65 bne a0, t0, is_exception /* Not an M environment call, so some other exception. */ 66 load_x sp, xISRStackTop /* Switch to ISR stack before function call. */ 67 jal vTaskSwitchContext 68 j processed_source 69 70 is_exception: 71 csrr t0, mcause /* For viewing in the debugger only. */ 72 csrr t1, mepc /* For viewing in the debugger only */ 73 csrr t2, mstatus 74 j is_exception /* No other exceptions handled yet. */ 75 76 as_yet_unhandled: 77 csrr t0, mcause /* For viewing in the debugger only. */ 78 j as_yet_unhandled 79 80 processed_source: 81 load_x t1, pxCurrentTCB /* Load pxCurrentTCB. */ 82 load_x sp, 0( t1 ) /* Read sp from first TCB member. */ 83 84 /* Load mret with the address of the next instruction in the task to run next. */ 85 load_x t0, 0( sp ) 86 csrw mepc, t0 87 88 portasmRESTORE_ADDITIONAL_REGISTERS /* Defined in freertos_risc_v_chip_specific_extensions.h to restore any registers unique to the RISC-V implementation. */ 89 90 /* Load mstatus with the interrupt enable bits used by the task. */ 91 load_x t0, 29 * portWORD_SIZE( sp ) 92 csrw mstatus, t0 /* Required for MPIE bit. */ 93 94 load_x x1, 1 * portWORD_SIZE( sp ) 95 load_x x5, 2 * portWORD_SIZE( sp ) /* t0 */ 96 load_x x6, 3 * portWORD_SIZE( sp ) /* t1 */ 97 load_x x7, 4 * portWORD_SIZE( sp ) /* t2 */ 98 load_x x8, 5 * portWORD_SIZE( sp ) /* s0/fp */ 99 load_x x9, 6 * portWORD_SIZE( sp ) /* s1 */ 100 load_x x10, 7 * portWORD_SIZE( sp ) /* a0 */ 101 load_x x11, 8 * portWORD_SIZE( sp ) /* a1 */ 102 load_x x12, 9 * portWORD_SIZE( sp ) /* a2 */ 103 load_x x13, 10 * portWORD_SIZE( sp ) /* a3 */ 104 load_x x14, 11 * portWORD_SIZE( sp ) /* a4 */ 105 load_x x15, 12 * portWORD_SIZE( sp ) /* a5 */ 106 load_x x16, 13 * portWORD_SIZE( sp ) /* a6 */ 107 load_x x17, 14 * portWORD_SIZE( sp ) /* a7 */ 108 load_x x18, 15 * portWORD_SIZE( sp ) /* s2 */ 109 load_x x19, 16 * portWORD_SIZE( sp ) /* s3 */ 110 load_x x20, 17 * portWORD_SIZE( sp ) /* s4 */ 111 load_x x21, 18 * portWORD_SIZE( sp ) /* s5 */ 112 load_x x22, 19 * portWORD_SIZE( sp ) /* s6 */ 113 load_x x23, 20 * portWORD_SIZE( sp ) /* s7 */ 114 load_x x24, 21 * portWORD_SIZE( sp ) /* s8 */ 115 load_x x25, 22 * portWORD_SIZE( sp ) /* s9 */ 116 load_x x26, 23 * portWORD_SIZE( sp ) /* s10 */ 117 load_x x27, 24 * portWORD_SIZE( sp ) /* s11 */ 118 load_x x28, 25 * portWORD_SIZE( sp ) /* t3 */ 119 load_x x29, 26 * portWORD_SIZE( sp ) /* t4 */ 120 load_x x30, 27 * portWORD_SIZE( sp ) /* t5 */ 121 load_x x31, 28 * portWORD_SIZE( sp ) /* t6 */ 122 addi sp, sp, portCONTEXT_SIZE 123 124 mret 125 .endfunc
第3~34行,保护现场,即将寄存器压栈。
第36行,保存额外的寄存器,这里什么都不做。
第38~39行,将sp的值保存在当前TCB的起始地址处。
第41行,读取mcause的值到a0寄存器。
第42行,读取mepc的值到a1寄存器。
第45行,将a0寄存器的值右移31位,将移位后的值写入a2寄存器。
第46行,判断a2寄存器的值是否等于0,即判断是中断(异步中断)还是异常(同步中断),如果等于0则跳转到第59行。这里假设a2的值不等于0,因此继续往下看。
第47行,将中断返回地址保存在栈顶。
第51行,使用中断栈,即在中断里有专门的栈空间来进行函数调用。
第52行,调用xPortClearTimerIntPending()函数,在这里该函数的作用是清定时器中断pending。目前在freertos这个demo里只有定时器中断,因此就没有判断是否是其他外部中断了。
第53行,调用xTaskIncrementTick()函数,这个函数的返回值决定了是否需要切换到其他任务。如果返回值为0,表示不需要切换,否则需要任务切换。
第54行,判断xTaskIncrementTick()函数的返回值是否等于0,如果是则跳转到第80行,这里假设返回值不等于0。
第55行,调用vTaskSwitchContext()函数,这个函数会切换当前TCB到将要执行的任务上。
第57行,跳转到第80行。
第81~82行,使用当前TCB的sp。
第85~86行,将中断返回地址写入mepc寄存器。
第88行,恢复额外的寄存器,这里什么都没做。
第91~122行,从栈里恢复寄存器的值,这和前面进入中断时的保存现场操作是成对的,即恢复现场。
第124行,中断返回,从mepc的值所指的地址处开始执行代码。
接下来,看回第46行,如果a2寄存器的值为0,则跳转到第第59行。
第60行,将a1的值加4,即将中断返回地址的值加4,后面会用到。
第61行,将a1的值写入sp寄存器。
第64~65行,判断a0的值是否等于11,即mcause的值是否等于11,即是否是ecall指令异常。如果不是则跳转到第70行。
第70~74行是一段死循环代码。
第66~68行,前面已经分析过了。
xTaskCreate
(gdb) bt #0 pxPortInitialiseStack () at ../..//Source/portable/GCC/RISC-V/portASM.S:420 #1 0x00000000800011fa in prvInitialiseNewTask (pxTaskCode=0x80000284, pcName=0x80004828 "Rx", ulStackDepth=140, pvParameters=0x0, uxPriority=2, pxCreatedTask=0x0, pxNewTCB=0x80020930 1336>, xRegions=0x0) at ../..//Source/tasks.c:1022 #2 0x000000008000108e in xTaskCreate (pxTaskCode=0x80000284 , pcName=0x80004828 "Rx", usStackDepth=140, pvParameters=0x0, uxPriority=2, pxCreatedTask=0x0) at ../..//Source/tasks.c:814
堆栈初始化
/* * Unlike other ports pxPortInitialiseStack() is written in assembly code as it * needs access to the portasmADDITIONAL_CONTEXT_SIZE constant. The prototype * for the function is as per the other ports: * StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters ); * * As per the standard RISC-V ABI pxTopcOfStack is passed in in a0, pxCode in * a1, and pvParameters in a2. The new top of stack is passed out in a0. * * RISC-V maps registers to ABI names as follows (X1 to X31 integer registers * for the 'I' profile, X1 to X15 for the 'E' profile, currently I assumed). * * Register ABI Name Description Saver * x0 zero Hard-wired zero - * x1 ra Return address Caller * x2 sp Stack pointer Callee * x3 gp Global pointer - * x4 tp Thread pointer - * x5-7 t0-2 Temporaries Caller * x8 s0/fp Saved register/Frame pointer Callee * x9 s1 Saved register Callee * x10-11 a0-1 Function Arguments/return values Caller * x12-17 a2-7 Function arguments Caller * x18-27 s2-11 Saved registers Callee * x28-31 t3-6 Temporaries Caller * * The RISC-V context is saved t FreeRTOS tasks in the following stack frame, * where the global and thread pointers are currently assumed to be constant so * are not saved: * * mstatus * x31 * x30 * x29 * x28 * x27 * x26 * x25 * x24 * x23 * x22 * x21 * x20 * x19 * x18 * x17 * x16 * x15 * x14 * x13 * x12 * x11 * pvParameters * x9 * x8 * x7 * x6 * x5 * portTASK_RETURN_ADDRESS * [chip specific registers go here] * pxCode */ .align 8 .func pxPortInitialiseStack: csrr t0, mstatus /* Obtain current mstatus value. */ 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. */ 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. */ store_x x0, 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 /*跳转到label 1*/ /* 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 /*-----------------------------------------------------------*/
portasmADDITIONAL_CONTEXT_SIZE为目标芯片增加的寄存器数。如Vega 开发板的RI5CY核,包含6个额外的寄存器。因此#define portasmADDITIONAL_CONTEXT_SIZE 6
vTaskSwitchContext切换
FreeRTOS任务相关的代码大约占总代码的一半左右,这些代码都在为一件事情而努力,即找到优先级最高的就绪任务,并使之获得CPU运行权。任务切换是这一过程的直接实施者,为了更快的找到优先级最高的就绪任务,任务切换的代码通常都是精心设计的,甚至会用到汇编指令或者与硬件相关的特性,比如Cortex-M3的CLZ指令。因此任务切换的大部分代码是由硬件移植层提供的,不同的平台,实现发方法也可能不同,这篇文章以Cortex-M3为例,讲述FreeRTOS任务切换的过程。
FreeRTOS有两种方法触发任务切换:
- 执行系统调用,比如普通任务可以使用taskYIELD()强制任务切换,中断服务程序中使用portYIELD_FROM_ISR()强制任务切换;
- 系统节拍时钟中断
在ARM平台对于Cortex-M3平台,这两种方法的实质是一样的,都会使能一个PendSV中断,在PendSV中断服务程序中,找到最高优先级的就绪任务,然后让这个任务获得CPU运行权,从而完成任务切换。对于第一种任务切换方法,不管是使用taskYIELD()还是portYIELD_FROM_ISR(),最终都会执行宏portYIELD(),这个宏的定义如下:
timer interrupt
pullMachineTimerCompareRegister = ( volatile uint64_t * ) ( ullMachineTimerCompareRegisterBase + ( ulHartId * sizeof( uint64_t ) ) );
ecall
#define portYIELD() __asm volatile( "ecall" );
#define portYIELD_WITHIN_API portYIELD
指令
J型指令在RISC-V中代表的是jal指令
-
将当前PC+4的值存入返回地址
- 普通的j指令将x0存入返回地址(忽略返回地址)
-
然后将当前PC值加上19位的偏移值(相对跳转)
jalr的用法
属于I型指令
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 // 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 任务