原文在此:http://www.cnblogs.com/mddblog/p/4920063.htmlhtml
在嵌入式系统中,启动文件是整个系统很是关键的部分,它会进行一些底层的初始化,构建程序运行必要的环境,好比堆栈初始化,变量初始化等。若是启动文件出现错误,则整个系统就跑不起来,所以研究启动文件很是必要。函数
在keil中,启动文件由汇编代码编写,通常命名为startup_xxx.s,xxx为支持的某种芯片,好比能够是lpc15xx(NXP的LPC15xx系列)、MK60D10(飞思卡尔)、stm32f10x(意法半导体stm32f10x系列)等Cortext-M0/M3/M4内核芯片。它们的代码格式很是相近,根据启动文件代码由上到下的编写顺序.加密
能够将其分为如下5个典型部分:spa
1.堆栈空间定义;指针
2.存放中断向量表;htm
3.复位中断函数(Reset_Handler);blog
4.其它中断异常服务函数,以及弱[WEAK]声明;get
5.将堆栈地址传递给库函数,利用库函数初始化堆栈,和库函数自身初始化。编译器
5个部分具体说明以下:it
以下图所示,定义了栈大小Stack_Size = 0X200,即512字节;堆大小Heap_Size = 0X100,256字节。还定义了三个标号:__initial_sp(栈顶)、__heap_base(堆起始地址)和__heap_limit(堆终止地址),它们的空间由SPACE关键字来申请,并记做Stack_Mem和Heap_Mem。
经过这些咱们能够很容易的知道堆栈的大小,可是它们的绝对地址或者说基地址仅仅从这里是得不到的。编译器编译完工程后,根据生成.bss段(好比未初始化的全局变量)和.data段(好比初始化的全局变量)的大小以及RAM的起始地址,来计算堆栈的基地址。
举个例子:
一个芯片的RAM起始地址为0x0200_0000,RAM大小为0x500字节,程序编译后.bss段为0x100个字节,.data段为0x100个字节。堆栈大小定义如上图。则:
A:堆起始地址 __heap_base==Heap_Mem==0x0200_0200;
B:堆终止地址即栈底 __heap_limit==Stack_Mem==0x0200_0300;
C:栈顶地址 __initial_sp==0x0200_0500(栈是向下生长,栈顶处于RAM最大地址处)。
其实,我能够在.map文件中查看堆栈的大小和基地址,以下图所示:
在启动代码中,会见到许多由DCD申请空间存放的一个个函数入口,即中断向量表,以下图所示,只列出了部分。
关键字DCD表明申请一个字的空间,后面的函数名即为中断服务函数入口地址。另外中断向量表通常存放在Flash 0地址。
另外,对于NXP微控制器,均实现了芯片的加密,加密的设置在向量表的结尾处,具体地址为0x02FC处。经过在此地址存放不一样的值实现是否加密或者加密的等级。加密分为三个等级,CRP1:0x12345678;CRP2:0x87654321;CRP3:0x43218765。至于每一个等级的具体说明请参考芯片用户手册。下面说一下加密步骤,以CRP1为例:
首先将下图中0xFFFFFFFF,修改成0x12345678。
其次,图中IF :LNOT::DEF:NOCRP表示若是没有定义宏NOCRP则执行下面的代码,那么必须保证汇编中没有定义NOCRP宏。即保证下图中Define:一栏中没有定义NOCRP便可。
程序上电后,首先加载SP和PC,ARM规定从0地址处加载SP,从偏移为4的地址(0x00000004)处加载PC。而后将程序控制权交给程序。咱们知道0地址处存放__initial_sp,0x00000004地址处存放Reset_Handler,加载PC后,程序跳转到Reset_Handler开始运行。Reset_Handler函数体以下图所示:
首先调用SystemInit函数来初始化系统的各类时钟,而后调用__main函数(由KEIL微库或者C库实现),在__main函数中:.data段数据的初始化->.bss段变量清零->设置堆栈指针->库函数初始化(好比经常使用的malloc函数)->若是必要会设置main函数的argc和argv两个参数->调用用户main函数->退出。
如上图所示,这里的中断服务函数是弱声明的(由[WEAK]关键字标注)。所谓弱声明,即:若是用户定义了相同的函数则此处函数失效而使用用户定义的中断服务函数。这样是为了防止用户使能了中断而没有中断服务函数,从而形成程序崩溃。假设使能了中断,而用户又没有定义中断服务函数则会进入默认中断,以下图所示,默认中断为死循环(死循环与程序崩溃不是一个概念)。
第三步骤中,调用__main函数,而后__main调用库函数初始化堆栈,但库函数并不知道堆栈的大小,所以咱们须要告诉它,具体作法就是传递参数或声明标号。
下图为具体作法,能够看到第一行为:
IF :DEF:__MICROLIB
是条件编译选项,若是定义__MICROLIB,则编译图中红线上面部分,不然编译红线下面部分。那么就分2种状况。
2种状况的选择能够以下实现:
若是勾选【Options for Target】->【Target】->【Use MicroLIB】,以下图所示。即便用微库,则__MICROLIB会被定义,编译器编译红线以上代码。用EXPORT声明 __initial_sp、__heap_base和__heap_limit。
若是不勾选【Use MicroLIB】,则缺省使用KEIL C库,上图红线如下会参与编译,KEIL C库函数会调用__user_initial_stackheap,经过R0~R3将堆栈以参数形式传递给KEIL C库。