从本节开始,开始讲述一些关于OS移植的内容;与Linux移植不同,本次讲的是嵌入式实时操作系统的移植,类似于ucos-ii这种,所以后面在说到任务抢占时,可能和熟知的Linux有所区别。
众所周知操作系统的运行依赖于底层硬件环境,无论是Windows还是Linux,操作系统运行前的操作肯定是一系列初始化操作;自己装过Windows的人一定都见过一个叫BIOS的界面,在这里面为Windows分配系统盘,设置启动方式等。为什么要指定系统盘?我们开机后,计算机是如何从系统盘检测到操作系统而不是其他硬盘分区?在操作系统启动前,计算机还做了哪些事?这些就是本章将要讲述的内容。
在操作系统运行前,都会先对CPU进行初始化操作,各个厂家都会提供类似于startup.s一类的文件,用于CPU初始化,也即启动代码。我们知道操作系统一般都是用C语音写的,但C语言的执行需要堆栈环境,CPU刚启动时,并没有分配堆栈区域,C语言是无法执行的,所以启动代码都是汇编实现的。
PUBWEAK Reset_Handler
SECTION .text:CODE:NOROOT:REORDER(2)
Reset_Handler
LDR R0, =SystemInit
BLX R0
LDR R0, =__iar_program_start
BX R0
如上代码,在startup.s文件中,会定义一系列中断,其中Reset_Handler是复位中断服务函数,即系统复位时执行的第一个中断,SystemInit是系统提供的初始化函数,CPU使用的时钟初始化操作,一般放在此处进行。__iar_program_start是编译器相关的,经过一些操作后,最终指向main()函数入口。
在操作系统移植时,根据处理器或操作系统要求,通常会修改Reset_Handler,例如添加中断向量表重定向操作,指定MSP(主堆栈指针)和PSP(进程堆栈指针)等。
PUBWEAK NMI_Handler
SECTION .text:CODE:NOROOT:REORDER(1)
NMI_Handler
B NMI_Handler
PUBWEAK HardFault_Handler
SECTION .text:CODE:NOROOT:REORDER(1)
HardFault_Handler
B HardFault_Handler
PUBWEAK MemManage_Handler
SECTION .text:CODE:NOROOT:REORDER(1)
MemManage_Handler
B MemManage_Handler
PUBWEAK BusFault_Handler
SECTION .text:CODE:NOROOT:REORDER(1)
BusFault_Handler
B BusFault_Handler
PUBWEAK UsageFault_Handler
SECTION .text:CODE:NOROOT:REORDER(1)
UsageFault_Handler
B UsageFault_Handler
PUBWEAK SVC_Handler
SECTION .text:CODE:NOROOT:REORDER(1)
SVC_Handler
B SVC_Handler
后面是其它的中断服务例程,操作系统移植时一般不需要修改,默认使用官方的就行。
对于Cortex-M3系列的处理器来说,当中断产生时,默认是从0地址处查找中断向量表;但通常情况下,0地址处一般存放的是BOOT(例如U-BOOT)代码,用于系统运行前的相关配置操作。如果0地址处存放的是BOOT代码,当中断产生时,中断指针指向的就是BOOT,这样就会因为找不到相应中断服务例程,而产生异常。所以需要把中断向量表重定向,让中断产生时,从其他地址处查找中断向量表。
PUBWEAK Reset_Handler
SECTION .text:CODE:NOROOT:REORDER(2)
Reset_Handler
mrs r0, CONTROL
orr r0, r0, #0x2
msr CONTROL, r0
isb
ldr r0, =SFE(CSTACK)
msr psp, r0
LDR R0, =__vector_table
LDR R1, =SCB_VTOR
STR R0, [R1]
LDR R0, =SystemInit
BLX R0
LDR R0, =__iar_program_start
BX R0
在上面的代码中
LDR R0, =__vector_table
LDR R1, =SCB_VTOR
STR R0, [R1]
SCB_VTOR是中断向量表重定向寄存器地址,__vector_table是重定向的地址,__vector_table在启动配置文件.icf中指定;添加这三步操作后,当有中断产生时,处理器就会从地址__vector_table处查找中断向量表。当然关于__vector_table也不是随意指定的,应合理划分存储器,防止因地址出错,而导致系统奔溃。
C语言的运行,需要堆栈环境,通常栈生长方向向下的处理器,内存分配如上图所示。
在Cortex-M3系列处理器中,将SP(堆栈指针)分成了MSP和PSP两种。
提供两种堆栈指针的目的是,将用户程序(或者说上层应用程序)的堆栈区域和操作系统的堆栈区域分开。为了防止用户程序的堆栈错误从而破坏OS的堆栈,致使OS奔溃,Cortex-M提供了两种堆栈供我们使用,MSP–主堆栈、PSP–进程堆栈。
Cortex-M复位时,默认操作系统和用户程序使用的是同一个堆栈区,为了防止上诉情况的发生,我们可以设置使OS的堆栈和用户程序的堆栈分开使用。让操作系统使用MSP,用户程序使用PSP,这样就可以避免因为用户程序的堆栈错误从而破坏OS的堆栈,致使OS奔溃。
操作系统使用的堆栈和用户程序使用的堆栈,可以理解为内核使用的堆栈和上层应用使用的堆栈,简单的可以理解为,开辟了两个堆栈区,用两个指针指向这两个区域。
mrs r0, CONTROL
orr r0, r0, #0x2
msr CONTROL, r0
isb
ldr r0, =SFE(CSTACK)
msr psp, r0
CONTROL:控制寄存器,定义特权状态 ,并且决定使用哪一个堆栈指针。
通过向CONTROL控制寄存器写入不同值,可以决定使用哪个堆栈指针。SFE(CSTACK)是堆栈区域的地址。
各处理器厂家,一般都会提供类似于 startup.s这种的启动文件,用于CPU初始化;但处理器厂家提供的一般都是通用或者示例代码,使用时要根据操作系统或具体的处理器做相应的修改。通常所做的操作就是指定堆栈指针、中断向量表重定向。
操作系统移植时,除了要修改启动文件外,还有一个至关重要的文件–配置文件.icf,用于指定内存分配。链接器 Linker 就是根据该文件来为变量分配对应的区域。.icf文件的作用及如何修改,在下面的文章将做解释。