- UID
- 1029342
- 性别
- 男
|
现象描述
我将一个具有实时任务切换功能的小型嵌入式操作系统内核成功的从具有ARM7内核的ADUC7024芯片移植到了具有cortex内核的LM3S8962芯片,然而在移植到同样具有cortex内核的STM32F103VB芯片上却出了问题,程序运行一段时间就跑飞,最终查明是任务切换过程破坏了cortex内核的中断机制所致,但为何同样采用了cortex内核的LM3S8962芯片却没有出现该问题?本文将向你讲述这其中的原因,同时你还可以了解到操作系统任务切换的基本原理以及cortex中断方面的一些知识。
背景知识介绍
在介绍问题定位过程前我们先了解一下实时嵌入式操作系统任务切换的基本原理。
对于嵌入式设备,程序一般存储在ROM中,有些芯片可以直接运行ROM中的指令,将变量中的数据存放在RAM中,有些芯片则可以将ROM中的指令搬移到RAM中执行,RAM中既存放指令又存放数据。不管哪种方式,程序执行过程中都需要使用芯片内核里的寄存器,这些寄存器用来存放程序运行过程中需要使用的数据,用来指示芯片的状态,这些寄存器距离芯片内核最近,速度最快,支持更多的寻址方式,因此程序执行过程大部分的操作都是使用这些寄存器完成的。
芯片通过寄存器执行程序空间的指令,不停的将临时数据从寄存器中存储到数据空间,将数据空间的临时数据恢复到寄存器中参与运算,这就是程序的运行过程。
图1 芯片内部结构(忽略cache)
程序的执行只与指令和数据相关,指令是不可修改的,编译后就确定了,能改变的只有数据,但指令需要对数据进行判断,走不同的指令分支,这些寄存器中保存的数据就控制了程序执行的分支,因此可以说操作系统任务切换的过程就是将任务A的寄存器数据存储到数据空间,然后再从数据空间将任务B数据恢复到寄存器中的过程,这样操作系统就完成了从任务A切换到任务B的上下文切换,有关操作系统切换过程更详细的介绍可以参考我写的《底层工作者手册之嵌入式操作系统内核》,可以在我的博客blog.sina.com.cn/ifreecoding下载相关的文档、源代码和演示视频。
图2 任务切换过程
实时嵌入式操作系统会采用一个硬件定时器以固定的频率产生任务调度,这就是我们所说的tick中断。当tick中断到来时操作系统就会产生调度,进行上面所说的寄存器备份、恢复操作,完成任务切换。对于其它非tick中断是不需要进行任务切换的,但当这些中断发生时,芯片会从一般的程序跳转到中断服务程序去执行,其实这也相当于是一种“任务切换”,也需要备份、恢复这些寄存器,这种情况对于没有使用操作系统的程序也是存在的。这些寄存器的备份、恢复过程有些是硬件自动完成的,有些则是由C编译器在编译代码时自动生成的代码来完成的,因此,中断过程中寄存器的备份、恢复过程对于软件程序员来说是透明的,我们在编写程序时看不到这个过程,可以不用关心这一过程。
接下来我们再了解一下STM32的中断机制。
Cortex内核发生中断时,硬件会自动将XPSR、PC、LR、R12、R3、R2、R1和R0这8个寄存器压入栈(详细介绍可以参考《底》2.1、2.3和5.1节),其余的R4~R11、LR、XPSR寄存器的备份则需要由C编译器去做。硬件为啥只将这8个寄存器入栈呢,因为AAPCS——Procedure Call Standard for the ARM Architecture对传递函数值所使用的寄存器做了规定,其中R0~R3和R12是接口寄存器,父函数可以通过这5个寄存器向其调用的子函数传递函数参数,子函数可以直接使用这5个寄存器,而子函数若需要使用其它的寄存器则需要由子函数先备份,以免破坏父函数保存在这些寄存器中的数值,然后才能使用。C编译器需要遵守这个规定,以保证不同的编译器编译出的二进制代码是兼容的。中断的产生具有随机性,中断服务程序无法知道被其打断的函数在哪些寄存器中保存了有用的数值,因此为了不破坏寄存器中的数据,C编译器需要在中断服务程序里对所有的寄存器进行备份,然后才能使用这些寄存器。LR寄存器保存了子函数/中断服务程序的返回地址,因此也需要C编译器来备份。PC寄存器保存的是发生子函数/中断服务程序调用那一时刻的程序指针,我认为没有备份的必要,但备份下来会看的更直观些。XPSR寄存器中保存的是子函数/中断服务程序调用那一时刻的芯片状态,因此这个需要备份。
Cortex内核在中断发生时硬件会自动入栈上述8个寄存器,因此C编译器就不需要再做重复的工作了,下面我们对比一下ARM7内核与cortex内核C编译器在子函数/中断服务程序调用时需要对寄存器做的备份工作,其中ARM7内核在中断发生时硬件不会自动将寄存器压入堆栈的:
| 子函数
| 中断服务程序
| ARM7
| R0~R3和R12可以直接使用。
若使用其它寄存器,需要先备份后使用。
| 所有寄存器都需要先备份后使用。
| cortex
| R0~R3和R12可以直接使用。
若使用其它寄存器,需要先备份后使用。
| R0~R3和R12可以直接使用。
若使用其它寄存器,需要先备份后使用。
|
表1 ARM7与cortex内核寄存器备份对比
可以看出C编译器在cortex内核上实现了统一,无论是发生子函数调用还是中断服务程序调用均只需要做相同的工作就可以了,cortex内核中断自动压入上述8个寄存器的设计简化了C编译器的设计,并且可以提高程序的执行效率(必须备份的寄存器由硬件完成,不需要由软件花费指令周期来完成了)。
你是否有这样的疑问:在cortex内核中,LR寄存器已经在中断发生时由硬件备份了,为什么在中断里还需要由软件再备份一次?这是因为cortex内核会将中断的返回地址LR压入栈,同时会将一个特殊的值“EXC_RETURN”存入LR寄存器中,有关EXC_RETURN的知识这里就不介绍了 |
|