上电能跑就行,可知程序从哪开始执行
讨论问题:
- CPU/MCU在设备开机后是如何执行到硬件初始化程序。
- 整个固件程序的入口在哪里,是main函数吗?
1.2. 程序从哪开始跑
1.2.1 异常向量表
如果有分析过u-boot 源码,应该对异常向量表不会陌生,但是如果没有分析过u-boot源码,那么51单片机和stm32有异常向量表的说法吗?
stm32是有异常向量表的,对于51单片机相对应的有中断向量。异常向量表就是将各种异常向量和中断向量按序排列组成的一张表。通过这张表找到对应异常或者中断的处理程序入口,每种芯片的异常向量表的异常和中断的类别和排序不一定相同,下图是exynos4412的异常向量表。
1.2.2 为什么需要预定义这些异常和中断向量呢?
试想一下,假如没有预定义这些向量,当CPU在执行的过程中有中断发生,那么CPU该如何去处理中断响应程序。这个中断响应程序位于哪,这都是未知的。而如果预定义了这些向量,那么CPU就可以根据异常或中断的类型,执行该向量地址的指令来响应中断。
1.2.3 程序入口
程序的入口是指程序的第一条指令的地址,而异常向量表一般存放在程序的入口地址。这样设备一上电就执行到复位异常指令,从而去执行编写好的初始化程序。
当然并不是每款芯片的异常向量表的0x00地址偏移量处存放的都是复位指令。就stm32F4xx而言,它的异常向量表的0x00地址偏移量存放的是栈初始化值的地址,而0x04地址偏移量存放的才是复位的入口地址。stm32F4xx在上电后,先从地址 0x00000000 地址取出 MSP 的初始值。再从地址0x00000004取出 PC 的初始值,取出的是复位的入口地址,从这个地址开始执行复位处理程序。
而对于51单片,它的入口地址处的第一条指令又是什么呢?
相信写过51单片机程序的都对主函数main不陌生,经常会听到入口函数就是main函数,那么main函数是否是位于程序的入口地址处呢?
答案是否定的,代码清单1是一段简短的51单片机程序的反汇编和C语言实现。通过汇编指令,不难发现这段程序的第一条指令是LJMP实现的转移指令,将0x0003地址装入PC寄存器;并且在执行主函数main之前还执行了其他指令,例如初始化堆栈寄存器。
/* 代码清单1 */
/* C语言程序 */
#include sbit LED1 = P1^0;
void main()
{
LED1 = 0;
while(1);
}
/* 反汇编程序 */
C:0x0000 020003 LJMP C:0003
C:0x0003 787F MOV R0,#0x7F
C:0x0005 E4 CLR A
C:0x0006 F6 MOV @R0,A
C:0x0007 D8FD DJNZ R0,C:0006
C:0x0009 758107 MOV SP(0x81),#0x07
C:0x000C 02000F LJMP main(C:000F)
void main():
C:0x000F C290 CLR LED1(0x90.0)
……
那么执行主函数之前的这些指令是怎么来的呢?是集成环境的编译器帮你做的。可能你还会说那主函数不还是main函数;其实不然,函数名main只是汇编语言编写的启动文件中定义的一个符号标签而已。
假如你自己重写底层的汇编启动文件,那么你可以将这个函数名设置成任何你喜欢的名称,这个可以结合前面讲的异常向量表来理解其中的实现原理。
虽然51单片机没有设置异常向量表,但是在启用定时器或者中断时,预定义的中断向量的地址一定是存放着中断响应处理程序的入口地址(代码清单2)。通过这些预定义的中断号,CPU可以很方便的找到中断处理响应程序。
/* 代码清单2 */
/* 中断程序--c语言程序 */
void main()
{
while(1) {
P1 = 0xff;
}
}
void timer0() interrupt 1
{
}
/* 定时器0中断程序--反汇编程序;中断号:0x000BH */
C:0x0000 02000E LJMP C:000E
C:0x0003 80FE SJMP main(C:0003)
……
C:0x000B 020005 LJMP timer0(C:0005)
C:0x000E 787F MOV R0,#0x7F
C:0x0010 E4 CLR A
C:0x0011 F6 MOV @R0,A
C:0x0012 D8FD DJNZ R0,C:0011
C:0x0014 758107 MOV SP(0x81),#0x07
C:0x0017 020003 LJMP main(C:0003)