一、系统启动
前面介绍了操作系统的工作原理,操作系统可以看作是应用程序与底层硬件中间的管理层,对下管理各硬件设备,对上服务各应用程序。对于应用程序的编写,学习编程语言时大家就不陌生了,如果想编写多线程并发的应用程序,之前也有一个系列介绍:C++多线程并发编程,这里就不介绍上层应用程序的开发了,本文把重点放到操作系统与硬件之间的交互上。
访问硬件主要是靠操作该硬件相关的寄存器实现的,前面介绍ARM中断系统与存储管理时有过详细的介绍。要想使硬件设备工作,一般需要RCC(Reset and Clock Control)时钟系统驱动,又由于外设通信速度远低于处理器运算速度,为了不让处理器白白等待外设通信过程而浪费运算资源,还需要有中断系统的支持;要操作外设寄存器,当然也离不开存储器随机访问能力的支持。但对这些硬件外设的访问可以在需要时再进行初始化配置,也即可以在主程序或系统启动之后再访问或管理这些硬件;那么,主程序或系统是如何启动的呢?
不管有没有操作系统,一个硬件平台上运行的系统总有一个main主程序(纯汇编程序除外),操作系统的启动也是在这个main主程序内调用相应的函数实现的。那么,硬件是如何进入main主程序的呢?了解计算机原理和编译原理的朋友应该知道,硬件是不识别C/C++这类高级语言的,硬件实际执行的是机器码,C/C++高级语言又是从main函数开始的,在进入main函数之前就需要更低级的语言引导了。机器码太不直观,没法直接用机器码编写程序,处理器设计者在设计处理器时同时设计了操作该处理器寄存器的指令系统,比如ARM处理器的ARM指令集和Thumb指令集,这些指令集就是比C/C++更低级的汇编语言,由于这些指令集可以直接操作硬件寄存器,从硬件上电到进入main主程序开始执行我们的程序或启动操作系统的任务就要靠这些指令集来完成了。
处理器也属于硬件设备的一种,处理器要想工作也需要上面提到的三个条件支持:时钟系统、中断系统、存储系统。处理器要想执行指令,需要设置RCC驱动时钟;要想响应中断,需要配置中断向量表;要想取指令保存运算数据,需要配置相应支持随机寻址的存储空间。但要执行处理器的指令集来配置这三个系统,处理器需要知道第一条指令的地址PC和运行指令需要的堆栈栈顶地址SP,这个就没法靠指令设置,而只能寄希望于硬件设置了。处理器设计者也确实是这么做的,在处理器上电后,PC与SP寄存器会从硬件设置的固定的物理地址处读取该地址保存的数据并赋值给PC与SP寄存器,然后处理器就可以开始执行第一条指令了,接下来的处理器就按照其指令集编写的程序配置RCC时钟、中断向量表、内存空间等基本环境,最后进入main主程序开始执行主程序内的任务。
1.1 设置系统时钟
还记得前面介绍中断管理时谈到的中断向量表吗?下面再附一段上电后的中断向量表:
中断向量表的前两项分别保存了MSP与PC的初始值,处理器刚上电复位后,硬件会自动根据向量表偏移地址(查询VTOR向量表偏移量寄存器,在中断管理中介绍过)找到中断向量表,硬件会自动从向量表首地址(映射地址0,在CM3中实际地址为Flash起始地址0x0800 0000)处读取数据并赋给栈指针SP,然后自动从向量表第二项地址处(CM3中一般为0x0800 0004)读取数据赋给程序计数器PC,开始执行PC地址上存储的指令,PC初始值指向复位向量,所以刚上电后首先执行的是复位指令。
下面以STM32F103为例,看看中断向量表前两项的值是多少:
// Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm\startup_stm32f10x_hd.s
; Amount of memory (in bytes) allocated for Stack
; Tailor this value to your application needs
; <h> Stack Configuration
; <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
...
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors
AREA |.text|, CODE, READONLY
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
从上面的代码来看,SP的初始值为__initial_sp,最开始的代码Stack_Mem SPACE Stack_Size分配了一段1KB(0x0000 0400)大小、不初始化(NOINIT)、可读写(READWRITE)、2^3=8字节对齐(ALIGN=3)的新内存空间,__initial_sp紧挨其后表示栈的结束地址,也即栈顶地址(别忘了栈一般是由高地址向低地址生长的)。PC的初始值为Reset_Handler,也即开始执行复位处理程序,再继续看Reset_Handler程序段的指令,主要作用是先执行SystemInit函数,再执行__main函数,下面先介绍SystemInit函数代码如下:
// Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\system_stm32f10x.c
/**
* @brief Setup the microcontroller system
* Initialize the Embedded Flash Interface, the PLL and update the
* SystemCoreClock variable.
* @note This function should be used only after reset.
* @param None
* @retval None
*/
void SystemInit (void)
{
/* Reset the RCC clock configuration to the default reset state(for debug purpose) */
/* Set HSION bit */
RCC->CR |= (uint32_t)0x00000001;
/* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
#ifndef STM32F10X_CL
RCC->CFGR &= (uint32_t)0xF8FF0000;
#else
RCC->CFGR &= (uint32_t)0xF0FF0000;
#endif /* STM32F10X_CL */
/* Reset HSEON, CSSON and PLLON bits */
RCC->CR &= (uint32_t)0xFEF6FFFF;
/* Reset HSEBYP bit */
RCC->CR &= (uint32_t)0xFFFBFFFF;
/* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
RCC->CFGR &= (uint32_t)0xFF80FFFF;
#ifdef STM32F10X_CL
/* Reset PLL2ON and PLL3ON bits */
RCC->CR &= (uint32_t)0xEBFFFFFF;
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x00FF0000;
/* Reset CFGR2 register */
RCC->CFGR2 = 0x00000000;
#elif defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000;
/* Reset CFGR2 register */
RCC->CFGR2 = 0x00000000;
#else
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000;
#endif /* STM32F10X_CL */
#if defined (STM32F10X_HD) || (defined STM32F10X_XL) || (defined STM32F10X_HD_VL)
#ifdef DATA_IN_ExtSRAM
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM */
#endif
/* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */
/* Configure the Flash Latency cycles and enable prefetch buffer */
SetSysClock();
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET