自己动手从0到1学写RTOS学习指南

  1. 主页
  2. 自己动手从0到1学写RTOS学习指南
  3. 常见问题FAQ汇总
  4. PendSV_Handler代码详解

PendSV_Handler代码详解

内容纲要

感谢同学 @勇敢的心,@捕硅锗 的提问

有同学在看完视频后,对PendSV_Handler中的实现代码仍有很多不解,所以这里用文档再详细标注。如果你在学习视频时遇到同样的问题,请先参考此文档。

该课时中的PendSV_Handler实现,主要用于模拟任务切换时的CPU内核寄存器的保存与恢复。这段代码非常重要,请务必弄懂。主要流程如下:

进入异常硬件自动保存部分寄存器到当前堆栈中
执行STM指令将其余未自动保存的寄存器R4~R11保存到自定义的缓冲区中
执行LDM指令将从自定义缓冲区中恢复之前保存的寄存器值到R4~R11
执行BX LR,硬件自动从当前堆栈中恢复之前保存的寄存器值
源码详解:

{% highlight c %}
__asm void PendSV_Handler ()
{
// blockPtr在main.c中定义,定义为:BlockType_t * blockPtr。
// 在汇编代码中,如要引用C中的符号,必 须先用IMPORT导入。有些类似于在C语言中的extern
IMPORT blockPtr

// 加载寄存器存储地址
// LDR R0, =blockPtr ; 将blockPtr变量的地址加载到R0中
// LDR R0, [R0] ; 再从该地址加载32位数据值,也就是将blockPtr的值加载到R0中。在main()中,我们已经设置:
// BlockType_t block;
// block.stackPtr = &stackBuffer[1024];
// blockPtr = █
// 所以,此时R0的值将会是block结构的起始地址。

// LDR R0, [R0]  ; 再次加载32位数据,显示此次就是从block结构开始处加载32位值,根据block结构的定义:
// typedef struct _BlockType_t {
 //    unsigned long * stackPtr;
// }BlockType_t
// 显然,此时R0的值就是stackPtr的值,也就是&stackBuffer[1024];
LDR     R0, =blockPtr
LDR     R0, [R0]
LDR     R0, [R0]

// 保存寄存器

// 此时R0的值就是&stackBuffer[1024]。然后,将R4, R5, .. R11写入到stackBuff缓冲区中,顺序不重要,你只需知道其与下面LDMIA的加载次序正好相反即可。写入前,以R0中地址为基准,每写入一个寄存器前,先对地址减去4,然后再写入寄存器值。完成所有写入之后,将最后的地址覆盖到R0寄存器中
STMDB R0!, {R4-R11}

// 将最后的地址写入到blockPtr中
// LDR R1, =blockPtr ; 将blockPtr变量的地址加载到R1中
// LDR R1, [R1] ; 再从该地址加载32位数据值,也就是将blockPtr的值加载到R1中。在main()中,我们已经设置:
// BlockType_t block;
// block.stackPtr = &stackBuffer[1024];
// blockPtr = █
// 所以,此时R1的值将会是block结构的起始地址。
// STR R0, [R1]  ; 将前面执行STM执行后,R0的值也就是最后写入的地址写入R1中地址指向的位置。
// typedef struct _BlockType_t {
 //    unsigned long * stackPtr;
// }BlockType_t
// 显然,此时R1的值就是block结构的地址,也就是向将R0的值写入到stackPtr。最终实现将最后的写stackBuff的 地址保存到block.stackPtr
LDR     R1, =blockPtr
LDR     R1, [R1]
STR     R0, [R1]

// 修改部分寄存器,用于测试

// 测试代码。用于检查前面的保存(STMDB),以及后面的恢复(LDMIA)是否正确。如果保存和恢复正确,那么执行LDMIA后,R4,R5的值应为恢复之前的值。
// 如果需要,可在此添加对其它寄存器的修改测试代码。

ADD R4, R4, #1
ADD R5, R5, #1

// 恢复寄存器
// 与STMDB正好相反,从R0地址对应的存储单元中读取多个32位的单元,恢复到R4~R11寄存器。
// 注意到,此时,R0为之前向存储单元写R4~R11的最后单元的地址,所以此时可以正确恢复。
// 具体执行时,首先从当前地址对应的单元处加载数据恢复到寄存器,再将地址+4,如此反复,直到所有寄存器恢复完毕。

LDMIA   R0!, {R4-R11}

// 异常返回
// 异常返回指令,不同于从函数调用。此时LR的值不是函数的返回地址,而是一串特定的值,如0xFFFF_FFFx,见《芯片内核简介》中退出异常小节。

BX      LR

}
{% endhighlight %}

这篇文章对您有用吗?

我们要如何帮助您?

发表评论

电子邮件地址不会被公开。 必填项已用*标注