SysTick_Handler作为一个正常功能工作,但不在SysTick中断(在QEMU上)上



我正在尝试用循环调度实现一个简单的RTOS。由于我还没有物理板,所以我正在QEMU(QEMU系统gnuarmlinux)上运行ELF文件。为了进行开发,我使用EclipseCDT。我使用以下命令在QEMU上运行代码:

/opt/xpack-qemu-arm-7.0.0-1/bin/qemu-system-gnuarmeclipse -M STM32F4-Discovery -kernel /mnt/d/eclipse-workspace/rtos/Debug/rtos.elf

每个任务都有一个关联的结构:

struct TCB {
int32_t *stackPt;
struct TCB *nextPt;
};

在初始化时,结构通过nextPt被链接到一个循环链表中,它们的堆栈(stackPt)被设置为TCB_STACK[threadNumber][STACK_SIZE-16];,堆栈的程序计数器被设置为TCB_STACK[0][STACK_SIZE - 2] = (int32_t)(taskA);。当前线程的指针保持为:currentTcbPt
然后系统设置为每隔10ms中断一次。程序集设置函数设置指向currentTcbPt所指向的线程堆栈的初始堆栈指针。此功能如下:

osSchedulerLaunch:          // This routine loads up the first thread's stack pointer into SP
CPSID I
LDR R0,=currentTcbPt
LDR R2,[R0]             // R2 = address of current TCB
LDR SP,[R2]
POP {R4-R11}
POP {R0-R3}
POP {R12}
ADD SP,SP,#4            // Skip 4 bytes to discard LR
POP {LR}
ADD SP,SP,#4            // Skip 4 bytes to discard PSR
CPSIE I
BX LR

现在,我的SysTick_Handler看起来是这样的:

__attribute__( ( naked ) ) void SysTick_Handler(void) {
__asm(
"CPSID I                    n"
"PUSH {R0-R12}              n"
"LDR R0,=currentTcbPt       n"
"LDR R1,[R0]                n"
"STR SP,[R1]                n"
"LDR R1,[R1,#4]             n"
"STR R1,[R0]                n"
"LDR SP,[R1]                n"
"POP {R4-R11}               n"
"POP {R0-R3}                n"
"POP {R12}                  n"
"ADD SP,SP,#4               n"
"POP {LR}                   n"
"ADD SP,SP,#4               n"
"CPSIE I                    n"
"BX LR                      n"
:[currentTcbPt] "=&r" (currentTcbPt)
);
}

我添加了额外的寄存器操作,这样我就可以将其用作普通函数。

问题

**首先**,我在"onSchedulerLaunch"函数(注释为"CPSIE I")和systick处理程序中禁用中断。还将"SysTick_Handler"重命名为随机函数名(如"Foo")
然后,我在每个任务的末尾调用这个"Foo"函数(任务没有无限循环)。这绝对行得通。任务按计划一次又一次地切换。

**第二个**,我启用中断,将函数名称设置回"SysTick_Handler",重新启用中断和"extern"C",并从任务结束时删除调用。现在,SysTick异常一发生,函数get就被执行了,但我在终端上打印了一个堆栈寄存器的Usage Fault
OS init
Launching scheduler
t2
t2
[UsageFault]
Stack frame:
R0 =  00000003
R1 =  2000008C
R2 =  00000000
R3 =  000004B8
R12 = 00000000
LR =  0800148D
PC =  000004B8
PSR = 20000000
FSR/FAR:
CFSR =  00000000
HFSR =  00000000
DFSR =  00000000
AFSR =  00000000
Misc
LR/EXC_RETURN= FFFFFFF9

在使用QEMU中的-d in_asm选项和远程gdb检查asm代码时,问题似乎发生在下一个任务的第一行(与上面PC中的地址相同)

问题

这个问题的原因可能是什么?它可能是特定于QEMU的,还是汇编代码有问题?编辑:查看完整代码以进行复制https://gist.github.com/shivangsgangadia/b78c7c66492d5332c7b4d1806be9c5f6
函数的执行顺序如下:

RTOS rtos();
rtos.addThreads(&task_a, &task_b, &task_c);
rtos.osKernelLaunch();

您的SysTick_Handler代码似乎扰乱了寄存器的顺序。指令PUSH{R0-R12}按R0、r1、r2、…的顺序将寄存器推送到堆栈。。。其中r0在最低地址并且r12在最高地址。但当你按顺序执行这些指令时:

"POP {R4-R11}               n"
"POP {R0-R3}                n"
"POP {R12}                  n"
"ADD SP,SP,#4               n"
"POP {LR}                   n"
"ADD SP,SP,#4               n"

它将从最低地址加载r4.r11并向上移动SP。然后,它从接下来的4个地址加载r0.r3,然后加载r12,然后跳过一个插槽,然后加载lr并跳过另一个插槽。所以你不会回到以前的状态。

其次,当异常处理程序返回时,寄存器的这种谨慎设置将被部分覆盖。Arm M-配置文件异常处理程序的工作方式是让CPU在进入时将状态推送到堆栈,并在异常返回时将其弹出。(这样做是为了在理论上可以将异常处理程序编写为C函数,因为堆栈推送和弹出与保存寄存器的C调用约定相匹配。);BX LR";在SysTick_Handler()结束时,这将导致硬件从堆栈中重新加载R0-R3、R12、PC和PSR。如果您没有更改SP,这将使用异常条目中的值重新加载它们;因为您已经更改了SP,所以它们将从新堆栈中提取,除非您将堆栈的这一部分设置为看起来像异常入口帧,否则这将是垃圾。作为异常返回的一部分,还进行了一些完整性检查,如果您将SP设置为指向其他对象,而没有正确设置堆栈,则可能会失败。

最后,在定时器中断处理程序中禁用中断看起来很奇怪——硬件会阻止您接受与您已经使用的中断处理程序相同或更低优先级的中断或异常。

总的来说,我建议您阅读M-配置文件体系结构手册中关于M-配置文件中断和异常处理如何工作的描述,因为您的代码看起来可能会像A-配置文件中断一样工作,其中PC设置为入口点,中断处理程序负责保存和恢复所有寄存器。

对于调试这种事情,我建议启用一些QEMU的"-d"调试标志。这些解释可能有点棘手,但特别是"-d int"会告诉您QEMU在中断入口和出口中做了什么,以及它为什么决定引发UsageFault。(您可能还想添加一些其他-d选项,比如"cpu,exec",为int日志记录提供一些上下文。)

问题是为单个任务堆栈在Execution PSR寄存器中设置T位。我所学习的课程内容忽略了PSR由4个字节组成,T位位于最高有效字节的事实
最初,我将其设置为:TCB_STACK[threadNumber][STACK_SIZE-1] = (1U << 6);。这导致BX LR无法正确获取返回地址
设置第4字节中的第6位,即TCB_STACK[threadNumber][STACK_SIZE-1] = (1U << 24);解决了问题,现在调度器工作完美。