ARM内联汇编实现自定义上下文切换遇阻,求技术支持
ARM Cortex-M3上下文切换:内联汇编逻辑验证与调试指南
我正在开发基于STM32F10x的上下文切换项目,目前卡在ARM内联汇编环节。作为新手,我搞不懂怎么用调试器单步跟踪每个汇编上下文的执行,也不确定自己写的内联汇编逻辑是否正确,求技术帮助。
项目代码
#include "stm32f10x.h" #include "stm32f10x_rcc.h" #include "stm32f10x_gpio.h" #include "stm32f10x_exti.h" volatile int32_t TimeDelay = 0; void Delay(uint32_t nTime); uint32_t stack_Blink_1[9]= {0}; uint32_t *sp_Blink_1 = &stack_Blink_1[8]; uint32_t stack_Blink_2[9]= {0}; uint32_t *sp_Blink_2 = &stack_Blink_2[8]; uint32_t curr_Task = 0; uint32_t next_Task = 0; uint32_t *PSP_Array[2]={0}; void GPIO_SETUP(void); void EXTI_SETUP(void); void Blink_1(void); void Blink_2(void); int main(void){ SysTick_Config(7200000); GPIO_SETUP(); EXTI_SETUP(); NVIC_EnableIRQ(EXTI2_IRQn); SCB->CCR |= SCB_CCR_STKALIGN_Msk; sp_Blink_1--; *(sp_Blink_1--) = (1U << 24); //xPSR sp_Blink_1--; sp_Blink_1--; *sp_Blink_1 = (uint32_t)&Blink_1; //PC sp_Blink_1-= 4; sp_Blink_2--; *(sp_Blink_2--) = (1U << 24); //xPSR sp_Blink_2--; sp_Blink_2--; *sp_Blink_2 = (uint32_t)&Blink_2; //PC sp_Blink_2-= 4; PSP_Array[0]=sp_Blink_1; PSP_Array[1]=sp_Blink_2; Blink_1(); while(1){ } } void PendSV_Handler(void){ __ASM volatile( //save current context "MRS R0, PSP\n\t" // Get current process stack pointer value "ISB\n\t" "STMDB R0!, {R4-R11}\n\t" // Save R4 to R11 in task stack (8 regs) "LDR R1, %[curr_Task]\n\t" "LDR R2, [R1]\n\t" // Get current task ID "LDR R3, %[psp_array]\n\t" "STR R0,[R3, R2, LSL #2]\n\t" // Save PSP value into PSP_array //----------------------------------------------------------------- //load next context "LDR R4, %[next_task]\n\t" "LDR R4, [R4]\n\t" // Get next task ID "STR R4, [R1]\n\t" // Set curr_task = next_task "LDR R0,[R3, R4, LSL #2]\n\t" // Load PSP value from PSP_array "LDMIA R0!, {R4-R11}\n\t" // Load R4 to R11 from task "MSR PSP, R0\n\t" // Set PSP to next task "ISB\n\t" "BX LR\n\t" // Return //"ALIGN 4\n\t" : // Output Operands : [curr_Task] "m" (curr_Task), [psp_array] "m" (PSP_Array), [next_task] "m" (next_Task) // Input Operands ); } void SysTick_Handler(void){ if(TimeDelay > 0) TimeDelay--; } void EXTI2_IRQHandler(void){ if(curr_Task == 0){ EXTI_ClearITPendingBit(EXTI_Line2); curr_Task = 1; }else{ EXTI_ClearITPendingBit(EXTI_Line2); curr_Task =0; } SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk; } void Delay(uint32_t nTime){ TimeDelay = nTime; while(TimeDelay != 0); } void Blink_1(void){ while(1){ GPIO_ResetBits(GPIOB,GPIO_Pin_1); Delay(10); GPIO_SetBits(GPIOB,GPIO_Pin_1); Delay(10); } } void Blink_2(void){ while(1){ GPIO_ResetBits(GPIOB,GPIO_Pin_0); Delay(10); GPIO_SetBits(GPIOB,GPIO_Pin_0); Delay(10); } } void GPIO_SETUP(void){ GPIO_InitTypeDef GPIO_InitStruct_A2; GPIO_InitStruct_A2.GPIO_Pin = GPIO_Pin_2; GPIO_InitStruct_A2.GPIO_Speed = GPIO_Speed_2MHz; GPIO_InitStruct_A2.GPIO_Mode = GPIO_Mode_IPD; GPIO_InitTypeDef GPIO_InitStruct_B0; GPIO_InitStruct_B0.GPIO_Pin = GPIO_Pin_0; GPIO_InitStruct_B0.GPIO_Speed = GPIO_Speed_2MHz; GPIO_InitStruct_B0.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitTypeDef GPIO_InitStruct_B1; GPIO_InitStruct_B1.GPIO_Pin = GPIO_Pin_1; GPIO_InitStruct_B1.GPIO_Speed = GPIO_Speed_2MHz; GPIO_InitStruct_B1.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitTypeDef GPIO_InitStruct_C13; GPIO_InitStruct_C13.GPIO_Pin = GPIO_Pin_13; GPIO_InitStruct_C13.GPIO_Speed = GPIO_Speed_2MHz; GPIO_InitStruct_C13.GPIO_Mode = GPIO_Mode_Out_PP; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); GPIO_Init(GPIOA,&GPIO_InitStruct_A2); GPIO_Init(GPIOB,&GPIO_InitStruct_B0); GPIO_Init(GPIOB,&GPIO_InitStruct_B1); GPIO_Init(GPIOC,&GPIO_InitStruct_C13); GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, 2); GPIO_SetBits(GPIOB,GPIO_Pin_0); GPIO_SetBits(GPIOB,GPIO_Pin_1); GPIO_SetBits(GPIOC,GPIO_Pin_13); } void EXTI_SETUP(void){ EXTI_InitTypeDef EXTI_InitStruct_2; EXTI_InitStruct_2.EXTI_Line = EXTI_Line2; EXTI_InitStruct_2.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStruct_2.EXTI_Trigger = EXTI_Trigger_Rising; EXTI_InitStruct_2.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStruct_2); }
一、内联汇编逻辑问题分析
1. 任务切换核心逻辑矛盾
EXTI2_IRQHandler中直接修改curr_Task,但PendSV_Handler的汇编逻辑是从next_Task读取目标任务ID,再将curr_Task覆盖为next_Task的值。这会导致:触发中断后curr_Task被修改,但PendSV执行时又会把curr_Task改回next_Task的初始值(0),完全无法完成切换。
修复方案:
修改EXTI2_IRQHandler,切换的是next_Task而非curr_Task:
void EXTI2_IRQHandler(void){ EXTI_ClearITPendingBit(EXTI_Line2); next_Task = curr_Task == 0 ? 1 : 0; SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk; }
2. 任务栈初始化错误
Cortex-M3的任务栈需要符合异常返回栈帧结构,才能保证切换后正确恢复执行。当前代码存在两个问题:
- 栈空间不足:
stack_Blink_1/2仅定义了9个uint32_t,但任务栈需要容纳:8个手动保存的寄存器(R4-R11) + 8个自动压栈的寄存器(xPSR、PC、LR、R12、R3-R0),至少需要16个uint32_t空间,当前栈会出现越界访问。 - 栈帧结构不完整:初始化时仅设置了xPSR和PC,缺少LR、R12、R3-R0的占位,第一次切换任务时会读取非法内存值。
修复方案:
// 扩大栈空间到16个uint32_t uint32_t stack_Blink_1[16]= {0}; uint32_t *sp_Blink_1 = &stack_Blink_1[15]; uint32_t stack_Blink_2[16]= {0}; uint32_t *sp_Blink_2 = &stack_Blink_2[15]; // 正确初始化任务栈帧(模拟异常压栈结构) void init_task_stack(uint32_t **sp, void (*task_func)(void)){ // 依次压入:xPSR, PC, LR, R12, R3, R2, R1, R0 (*sp)--; **sp = (1U << 24); // xPSR(必须设置T位为1,Thumb模式) (*sp)--; **sp = (uint32_t)task_func; // PC (*sp)--; **sp = (uint32_t)0xFFFFFFFD; // LR(异常返回时返回线程模式,使用PSP) (*sp)--; **sp = 0; // R12 (*sp)--; **sp = 0; // R3 (*sp)--; **sp = 0; // R2 (*sp)--; **sp = 0; // R1 (*sp)--; **sp = 0; // R0 // 预留R4-R11的位置(8个寄存器,初始化时无需赋值,第一次切换时会保存) *sp -= 8; } // main中调用初始化 int main(void){ // ... 其他初始化代码 ... init_task_stack(&sp_Blink_1, Blink_1); init_task_stack(&sp_Blink_2, Blink_2); PSP_Array[0]=sp_Blink_1; PSP_Array[1]=sp_Blink_2; // ... }
3. PendSV汇编逻辑细节优化
- 读取
curr_Task和next_Task时,直接用LDR R1, =curr_Task会更直观,但当前用%[curr_Task]的约束是正确的,编译器会自动处理地址加载。 - 保存PSP后,无需额外
ISB指令,MRS和STMDB后已经保证指令同步。
二、汇编上下文调试指南
1. 调试环境准备
使用Keil MDK或STM32CubeIDE,开启调试会话后:
- 打开Disassembly视图:查看编译后的汇编代码,定位到
PendSV_Handler的内联汇编部分。 - 打开寄存器视图:重点关注
PSP、R0-R11、LR、xPSR的值。 - 打开内存视图:输入
sp_Blink_1或sp_Blink_2的地址,实时查看任务栈的内容变化。
2. 单步调试步骤
- 在
EXTI2_IRQHandler的SCB->ICSR |= ...行设置断点,触发外部中断后暂停。 - 查看
curr_Task和next_Task的值,确认切换目标正确。 - 单步进入
PendSV_Handler(F11),逐行执行内联汇编:- 执行
MRS R0, PSP后,检查R0的值是否等于当前任务的栈指针(比如sp_Blink_1)。 - 执行
STMDB R0!, {R4-R11}后,查看内存视图中R0指向的位置,确认R4-R11的值已保存到栈中。 - 执行到
STR R0,[R3, R2, LSL #2]后,检查PSP_Array[curr_Task]的值是否更新为新的栈指针。 - 加载下一个任务的PSP后,执行
LDMIA R0!, {R4-R11},检查寄存器视图中R4-R11的值是否恢复为目标任务的保存值。 - 执行
BX LR后,程序会跳转到目标任务的执行点,查看GPIO状态是否切换,确认上下文切换成功。
- 执行
3. 关键验证点
- 任务栈的xPSR值必须为
0x01000000(T位为1,Thumb模式),否则会触发硬件错误。 - 异常返回时LR的值必须为
0xFFFFFFFD,确保返回线程模式并使用PSP。 - 每次切换后,
PSP的值必须对应目标任务的栈指针,curr_Task必须等于next_Task。
内容的提问来源于stack exchange,提问作者Clyde Xander
相关产品推荐
相关产品推荐

