嵌入式系统BootLoader的设.ppt
嵌入式系统BootLoader的设计与实现,基于S3C44B0X的C/OS-移植,操作系统移植概述,所谓操作系统的移植,是指一个操作系统能够在某个微处理器平台上运行。操作系统C/OS-II是一个基于优先级的抢占式实时多任务内核。C/OS-II的大部分代码是用ANSI C语言编写的,也包含一小部分汇编语言代码,使之可以提供给不同架构的微处理器使用。至今,从8位到64位,C/OS-II已经在多种不同架构的微处理器上移植成功。,C/OS-II移植纲要,Phase 1,Phase 2,Phase 3,1.C/OS-II移植的条件,2.编译器的选择,3.工作状态的选择,4.工作模式的选择,5.BootLoader,6.系统初始化流程,7.最小启动代码编写,8.C/OS-II相关文件修改,9.确保交叉编译环境正常工作,10.验证OSTaskStkInit()和OSStartHighRdy(),11.验证OSCtxSw(),12.验证OSTickISR()和OSIntCtxSw(),移植前的规划,操作系统移植,测试验证,Phase 1 移植前的规划,C/OS-II移植的条件,1,2,3,4,编译器的选择,工作状态的选择,工作模式的选择,COS-II移植的条件,处理器的C编译器能产生可重入代码;在程序中可以打开/关闭中断;处理器支持中断,并且能产生定时中断(通常为10-100Hz);处理器能支持一定数量的数据存储硬件堆栈;处理器有将堆栈指针以及其它CPU寄存器的内容读出、并存储到堆栈或内存中去的指令。针对上述移植条件,基于ARM7TDMI Core的S3C44B0X处理器满足其中的硬件要求。,Phase 1 移植前的规划,C/OS-II移植的条件,1,2,3,4,编译器的选择,工作状态的选择,工作模式的选择,移植对编译器的要求及选择,除了对硬件有要求外,还需要一个支持C语言和ARM汇编语言的综合编译开发环境(IDE)。综合编译开发环境至少需要包括C编译器、ARM汇编器和链接器。C编译器用来对C语言程序进行编译生成汇编代码。由于移植时需要对CPU的寄存器进行操作,所以需要汇编器能够支持汇编语言程序。链接器根据定位信息将不同的模块(编译或汇编过的文件)链接成一个单一的、绝对定位的可执行的映像文件。针对ARM处理器核的C语言编译器有很多,目前在国内比较流行的有SDT、ADS、IAR、KEIL和GCC等;其中SDT和ADS均为ARM公司自己开发,ADS为SDT的升级版,以后ARM公司不再支持SDT。本移植采用ADS1.2集成开发环境进行程序的编译和调试。,Phase 1 移植前的规划,C/OS-II移植的条件,1,2,3,4,编译器的选择,工作状态的选择,工作模式的选择,工作状态的选择,自从ARM7TDMI Core以后,体系结构中具有T变种的ARM处理器核可以工作在以下两种状态,并支持两个指令集:ARM状态 ARM状态下执行字对准的32位ARM指令;Thumb状态 Thumb状态下执行半字对准的16位Thumb指令;COS-II的任务可以在任何一种工作状态下运行,并可以进行状态切换。为了移植代码的编写简单,本移植只在ARM状态下实现。,Phase 1 移植前的规划,C/OS-II移植的条件,1,2,3,4,编译器的选择,工作状态的选择,工作模式的选择,ARM处理器工作模式(1),除用户模式外,其它模式均为特权模式。ARM内部寄存器和一些片内外设在硬件设计上只允许(或者可选为只允许)特权模式下访问。此外,特权模式可以自由的切换处理器模式,而用户模式不能直接切换到别的模式。,ARM处理器工作模式(2),这五种模式称为异常模式。它们除了可以通过程序切换进入外,也可以由特定的异常进入。当特定的异常出现时,处理器进入相应的模式。每种异常模式都有一些独立的寄存器,以避免异常退出时用户模式的状态不可靠。本移植中COS-II的任务正常运行在管理(SVC)模式。,Phase 2 操作系统移植,BootLoader,1,2,3,4,系统初始化流程,最小启动代码编写,C/OS-II相关文件修改,BootLoader的基本概念,要想让COS-II在S3C44B0X处理器上正常运行,我们需要对系统的硬件环境进行初始化。对于PC机,其开机后操作系统启动前的硬件初始化操作是由BIOS(Basic Input/Output System)完成的,但对于嵌入式系统来说,出于通用性、价格方面的考虑,通常并没有像BIOS那样的固件程序,因此启动时用于完成初始化操作的引导加载程序必须自行编写完成,这段程序一般被称为Bootloader程序。BootLoader是系统加电(或复位)后运行的第一段软件代码。通过这段代码,我们可以初始化系统硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便最终调用操作系统内核。简单地说,BootLoader就是在操作系统内核运行之前运行的一段初始化程序。,BootLoader的特点,BootLoader除了依赖CPU的体系结构外,还依赖于具体的嵌入式板级设备的配置,比如板卡的硬件地址分配,RAM芯片的类型,其它外设的类型等。对于两块不同的嵌入式开发板,即使它们是基于同一种CPU而构建的,如果他们的硬件资源和配置不一致,要想让运行在一块板子上的BootLoader程序也能运行在另一块板子上,也还是需要作一些必要的修改。因此,为嵌入式系统建立一个通用的BootLoader是很困难的。尽管如此,我们仍然可以对BootLoader(尤其是基于同种Core的微处理器)归纳出一些通用的概念和设计思路,用来指导用户特定的BootLoader设计与实现。,BootLoader的启动过程,为了增加BootLoader的通用性和可移植性,本文把启动过程分为phasel和phase2两个阶段。phase1阶段执行的是用汇编语言来实现的依赖CPU体系结构的代码,这样可以提高系统的启动速度;phase2阶段完成的是OS内核启动前的准备工作,多采用处理能力强、可移植性好的C语言来实现。phase1阶段执行的代码,我们称之为最小启动代码。所谓最小启动代码是指为了完成系统(OS或用户应用程序)启动所必须的最少硬件的初始化程序。,BootLoader的安装媒介,系统加电或复位后,所有的CPU通常都从CPU制造商预先安排的地址上取指令。基于ARM7TDMI Core的嵌入式系统中,系统在上电或复位时是从0 x00000000地址开始取第一条指令执行,而在这个地址处安排的通常就是系统的BootLoader程序。嵌入式系统通常都有某种类型的固态存储设备(比如:ROM、EPROM或FLASH等)被安排这个起始地址上用来存放BootLoader程序。,BootLoader的下载方式,通过编程器将可执行目标文件烧写到BootROM中;通常通过串行口、网口或JTAG等接口下载。,串口,网口,JTAG,目标机,MPU,RAM,BOOT ROM,下载工具,宿主机,Phase 2 操作系统移植,BootLoader,1,2,3,4,系统初始化流程,最小启动代码编写,C/OS-II相关文件修改,嵌入式系统的初始化流程,1,2,3,4,5,6,7,硬件初始化,RTOS初始化,软件初始化,设置中断向量表,最小硬件初始化,RTOS初始化,启动RTOS,最小启动代码,phase1,phase2,用户程序,RTOS运行环境初始化,硬件抽象层初始化,用户程序初始化,嵌入式系统的初始化流程(1),1,2,3,硬件初始化阶段,最小硬件初始化,最小启动代码,中断向量表放在上电后映射在从0 x00000000开始的8*4个字节的连续存储空间中,其作用是指定了各种异常中断处理程序的入口地址。,phase1,设置中断向量表,RTOS运行环境初始化,嵌入式系统的初始化流程(2),硬件初始化阶段,禁用看门狗定时器;屏蔽所有中断;设定CPU的时钟频率;初始化存储器;分配各种模式下的栈空间。,最小硬件初始化,1,2,3,最小硬件初始化,最小启动代码,设置中断向量表,RTOS运行环境初始化,phase1,嵌入式系统的初始化流程(3),硬件初始化阶段,为RTOS运行准备合适的RAM空间;呼叫RTOS主(Main)程序。,RTOS运行环境初始化,1,2,3,最小硬件初始化,最小启动代码,设置中断向量表,RTOS运行环境初始化,phase1,嵌入式系统的初始化流程(4),RTOS初始化阶段,4,5,6,硬件抽象层初始化,RTOS初始化,启动RTOS,硬件抽象层初始化,系统cache、总线设置;中断及中断向量处理子程序初始化;I/O端口配置;初始化定时器,为时钟中断做好准备;对RTOS所需的其它设备初始化。,phase2,嵌入式系统的初始化流程(5),RTOS初始化阶段,4,5,6,硬件抽象层初始化,RTOS初始化,启动RTOS,RTOS初始化,RTOS内核启动参数初始化;RTOS扩展部件初始化。,phase2,嵌入式系统的初始化流程(6),RTOS初始化阶段,4,5,6,硬件抽象层初始化,RTOS初始化,启动RTOS,启动RTOS,运行优先级最高的就绪任务;启动RTOS时钟中断。,phase2,嵌入式系统的初始化流程(7),用户程序初始化阶段,用户程序初始化,用户程序正常运行所进行的初始化。,7,用户程序,用户程序初始化,Phase 2 操作系统移植,BootLoader,1,2,3,4,系统初始化流程,最小启动代码编写,C/OS-II相关文件修改,设置中断向量表,因为中断向量表中每种异常只分配4个字节,所以不能放下整个异常中断处理程序,只能放一条跳转指令,用以跳转到相应的异常中断处理程序。,禁用看门狗定时器和屏蔽所有外部中断,示意代码分析,WTCON EQU 0 x01d30000;看门狗定时器控制寄存器INTMSK EQU 0 x01e0000c;中断屏蔽寄存器 ResetHandler LDR r0,=WTCON LDR r1,=0 x00000000;Watch Dog Disable STR r1,r0 LDR r0,=INTMSK LDR r1,=0 x07ffffff;All Interrupt Disable STR r1,r0,说明:默认初始状态允许看门狗定时器工作(p496),所以要关闭。如果要屏蔽所有中断,可用MRS指令设置CPSR的I/F位(p431,p440)。,S3C44B0X时钟发生器组成(1),S3C44B0X的时钟发生器可产生CPU和外设所需要的时钟信号。时钟发生器可在S/W的控制下向外设提供时钟信号,也可断开时钟与每个外设的连接来降低功耗。首先要求要确定主时钟MCLK,然后以主时钟作为一种基准,通过分频控制定时器,串口等外设的时钟频率。S3C44B0X时钟发生器组成:主时钟源有两个:一个是XTAL0和EXTAL0连接的外部晶振,另一个是EXTCLK连接的外部时钟;PLL的功能是以低频振荡器的输出作为输入,产生S3C44B0X需要的高频信号;还有一个时钟控制逻辑部件来可产生稳定的时钟频率。S3C44B0X时钟源的选择由处理器的两个引脚OM3:2控制。OM3:2=00时钟源为晶振,OM3:2=01时钟源为外部时钟。本移植采用晶振作为时钟源。晶振为6MHz,即fin=6MHz。,S3C44B0X时钟发生器组成(2),时钟发生器框图,说明:时钟发生器(p446),PLL锁相环原理框图,PLL锁相环框图,说明:PLL锁相环原理(p446-p447),设置PLL分频器,输出时钟频率fpllo和输入的时钟频率fin的关系如下:fpllo=(m*fin)/(p*2s)其中,m=MDIV(分频器M的分频值)+8,p=PDIV(分频器P的分频值)+2,s=SDIV(分频器S的分频值)PLL值的选择向导:fpllo必须大于20Mhz,小于66MHz;fpllo*2s必须小于170MHz;,时钟控制逻辑部件,当PLL被配置为一个新的频率值时,时钟控制逻辑部件在PLL输出稳定之前禁止fout,直到PLL锁定系统时钟后取消禁止。PLL锁时实际上是PLL输出稳定所需要的时间,这个时间应长于208us。锁定时间值计算公式如下:tlock=(1/fin)*n 其中n=LTIMECNT值。,设定CPU及各功能模块时钟频率,示意代码分析,PLLCON EQU 0 x01d80000;PLL控制寄存器 CLKCON EQU 0 x01d80004;时钟控制寄存器 LOCKTIME EQU 0 x01d8000c;锁时计数寄存器M_DIV EQU 0 x48 P_DIV EQU 0 x02S_DIV EQU 0 x01LDR r0,=LOCKTIMELDR r1,=0 x00000fff;tlock=(1/fin)*LTIMECNTSTR r1,r0 LDR r0,=PLLCON;fin=6MHz,fout=60MHLDR r1,=(M_DIV12)+(P_DIV4)+S_DIV)STR r1,r0LDR r0,=CLKCONLDR r1,=0 x00007ff8;All Unit Block CLK EnableSTR r1,r0,存储器初始化概述,存储器系统的初始化是指对Flash、RAM存储器的地址范围,数据总线宽度及DRAM的刷新等进行软件设置。设置对象是存储器控制寄存器。芯片不同设置不同,具体需要参考芯片手册。S3C44B0X的存储器控制寄存器是以0 x01C80000为起始地址的13个连续的32位寄存器。可以一次将预先配置好的初始化数据存入与存储器控制器相关的13个寄存器。,S3C44B0X处理器不支持存储器地址重映射(Memory Remap)。,S3C44B0X复位后的存储器地址分配,典型系统中存储器的分配情况,;BWSCON EQU 0 x01000002;|-Bank0=16bit Nor-Flash(AM29LV160DB)(2M);|-Bank1 NotInit;|-Bank2 NotInit;|-Bank3 NotInit;|-Bank4 NotInit;|-Bank5 NotInit;|-Bank6=16bit SDRAM(HY57V641620ET)(8M);|-Bank7 NotInit,S3C44B0X存储控制器功能描述,大/小端模式选择 S3C44B0X 具有一个输入引脚ENDIAN,处理器通过它的输入逻辑电平来确定数据类型是小端还是大端:0:小端 1:大端 逻辑电平在复位期间由该管脚的上拉或下拉电阻确定。,S3C44B0X存储控制器功能描述,Bank0 总线宽度 BOOT ROM 在地址上位于ARM 处理器的Bank0 区,它可能具有多种数据总线宽度,因为Bank0是系统自举ROM存储体,所以必须在访问ROM之前定义Bank0的总线宽度,如下表所示:,其他存储体的总线宽度只能在系统复位后由程序进行设定,由地址为0 x01c8000的特殊寄存器BWSCON的相应位决定。,S3C44BOX存储系统的特点,-存储器引脚连接,6.2.3 S3C44B0X存储控制器功能描述,ONE BYTE BOOT ROM DESIGN,6.2.3 S3C44B0X存储控制器功能描述,例:,HALF-WORD BOOT ROM DESIGN WITH BYTE EEPROM/FLASH,6.2.3 S3C44B0X存储控制器功能描述,存储器初始化(2),示意代码分析,LDR r0,=SMRDATALDMIA r0,r1-r13LDR r0,=0 x01c80000;BWSCON AddressSTMIA r0,r1-r13SMRDATA DATA DCD cBWSCON DCD cBANKCON0;GCS0 DCD cBANKCON1;GCS1 DCD cBANKCON2;GCS2 DCD cBANKCON3;GCS3 DCD cBANKCON4;GCS4 DCD cBANKCON5;GCS5 DCD cBANKCON6;GCS6 DCD cBANKCON7;GCS7 DCD cREFRESH;REFRESH DCD cBANKSIZE;BANKSIZE DCD cMRSRB6;MRSR6 DCD cMRSRB7;MRSR7,分配各种工作模式下的栈空间,如前面所述,基于ARM7TDMI Core的微处理器有用户、系统、FIQ、IRQ、管理、中止和未定义共7种模式,程序根据不同需求可能需要切换到不同的工作模式下运行。因为每种模式下都有各自的堆栈指针(SP),所以需要为每种模式分配栈空间。,ARM状态各模式下的寄存器,不同异常中断发生进入不同工作模式,当系统上电或复位后从地址0 x00000000开始取第一条复位异常指令B ResetHandler执行,并自动切换到管理模式。,各种模式下的栈空间分配代码分析,示意代码分析,LDR sp,=SVCStack BL InitStacks InitStacks MRS r0,cpsr BIC r0,r0,#MODEMASK ORR r1,r0,#UNDEFMODE|NOINT;Undef Mode MSR cpsr_cxsf,r1 LDR sp,=UndefStack;Abort Mode;IRQ Mode;FIQ Mode;SVC Mode;USR Mode is not initialized.,映像文件的组成,ARM链接器把编译(或汇编)生成的目标文件和所需要的库文件链接在一起生成可执行的ELF(Executable Linkable Format)格式的映像文件。ARM编译器编译生成的文件称为目标文件。编译器只对单个文件工作,每次只能编译一个源文件,输出一个目标文件。但链接器每次可以链接多个目标文件,把这些文件链接成一个可执行的映像文件。文件是编译器操作的基本单位,而段则是链接器操作的基本单位。链接器不关心有多少个输入文件,只关心有多少个和何种属性的输入段。链接器的输入段来源主要有两种:一种是来自源文件中的段,另一种是来自系统库文件中的段。这些输入段有3种属性:只读(RO)段、可读写(RW)段和初始化为0(ZI)段。链接器输出的可执行的映像文件也包含一个或多个段(称为输出段)。输出段也有三种属性:只读段、可读写段和初始化为0段。,映像文件输出段的属性,简单的说,程序代码和常量存放在RO段;可读写的全局变量和静态变量存放在RW段;RW段中要被初始化为零的变量被称为ZI段。链接器把属性相同的输入段按照一定的顺序组织在一起,形成输出段。,加载地址和执行地址(1),映像文件可以有两种地址:加载地址和执行地址。加载地址就是文件在存储器中的存储地址,执行地址就是文件在运行时的地址。根据存储器的存储原理不同,在比较简单的系统中,加载地址和执行地址可以是同一地址。而在复杂的系统中,常常需要把程序的一部分或全部从加载地址转移到执行地址。,加载地址和执行地址(2),在嵌入式系统中,Nor Flash是常见的一种存储芯片,系统掉电数据不会丢失。因此很适合作为BootLoader的存储介质。Nor Flash的读取和RAM很类似,支持Execute On Chip,即程序可以直接在FLASH片内执行,但不可以直接进行写操作。根据Nor Flash的上述特点,我们经过连接器链接生成的映像文件的加载地址一般在Nor Flash中,其中只读(RO)段的存储地址就是其执行地址,而可读写(RW)段和初始化为0(ZI)段必须被复制到RAM存储器进行,即后两者的存储地址和执行地址不同。,链接器的链接类型,ADS1.2链接器的链接类型有三种:Partia、Simple和Scattered。Partia方式表示链接器只进行部分链接,经过部分链接生成的目标文件,可以作为以后进一步链接时的输入文件。Simple方式是默认的链接方式,它使用的是链接器选项中指定的地址映射方式,生成简单的ELF格式的映像文件。Scattered方式使得链接器要根据scatter描述文件中指定的地址映射,生成复杂的ELF格式的映像文件。本移植采用默认的Simple链接方式。选中Simple方式后,就会出现【Simple image】选项卡。RO Base:这个文本框设置包含有RO段的的加载地址,同时又是运行地址。RW Base:这个文本框设置包含RW和ZI段的执行地址。,链接器产生的地址映射过程,链接器把所有输入目标文件链接在一起,生成一个输出的映像文件,需要大量的关于输入段的信息,信息主要有两类:分类信息和定位信息。链接器把相同属性的输入段组织成一个输出段。在默认情况下,链接器生成的映像文件包括RO、RW和ZI三种属性的输出段,并按照下列顺序组织输入段:,高地址,低地址,输出映像文件的组成,Image文件只包含了RO段和RW段。之所以映像文件不包含ZI段,是因为ZI段中的数据都是0,没必要包含,只要程序运行之前将ZI数据所在的区域一律清零即可。这就需要程序根据编译器给出的ZI地址及大小来将相应得RAM区域清零。,执行地址不同于加载地址的实例,已知RO Base为0 x00000,RW Base为0 x08000,在加载地址为0 x06000位置存储了RW属性的存储段。,RTOS运行环境初始化代码,示意代码分析,LDR r0,=|Image$RO$Limit|;得到ROM加载域中RW数据段的起始地址 LDR r1,=|Image$RW$Base|;得到RAM执行域中RW数据段的起始地址 LDR r3,=|Image$ZI$Base|;得到RAM中ZI段的起始地址 CMP r0,r1 BEQ%F10 CMP r1,r3;复制加载域的RW段到执行域 LDRCC r2,r0,#4 STRCC r2,r1,#4 BCC%B01 LDR r1,=|Image$ZI$Limit|;对ZI段(未初始化的数据段)初始化为0 MOV r2,#02 CMP r3,r1 STRCC r2,r3,#4 BCC%B2,设置Bootloader的初始入口点,BootLoader映像文件必须定义初始入口点,该点地址就是可执行文件运行时的起点,而且整个应用程序只有一个初始入口点。映像文件有两种入口点:初始入口点和普通入口点。在汇编语言源文件中,由ENTRY伪操作定义的入口点是普通入口点。在C库中_main()函数也是一个普通入口点。可以定义多个普通入口点。初始入口点可以是普通入口点。在输入段中,ENTRY伪操作可以告知链接器,不要在执行优化链接过程中把这些输入段作为不使用的段删除。为确保这段代码被链接在0 x0地址处,并且作为整个程序的初始入口点。通常需要在连接选项中显式的指定程序初始入口点。本移植初始入口点(Image entry point)为0 x00000000地址。,呼叫RTOS主程序,对Main函数的调用进入C/OS-II的入口,通过这个入口就进入对C/OS-II的初始化。示意代码分析,IMPORT MainB Main;RTOS Entry,Phase 2 操作系统移植,BootLoader,1,2,3,4,系统初始化流程,最小启动代码编写,C/OS-II相关文件修改,驱动/硬件抽象层uHAL,对于驱动/硬件抽象(uHAL)这一层,因为直接驱动硬件,其标准化变得很困难,因此只能提供部分相对通用的接口。从结构上看,uHAL是一组库函数,C/OS-II通过调用函数ARMTargetInit()驱动硬件。ARMTargetStart()主要包括下列各种初始化:通过Sys_Init(),设置系统Cache;通过Port_Init()初始化I/O端口;通过Uart_Init()初始化Uart用于调试信息显示;通过uHAL_InitInterrupts()设置中断和中断向量处理程序;uHALr_InstallTimer()创建一个系统时钟,为时钟中断做好准备。示意代码分析,void Main(void)ARMTargetInit();/初始化系统硬件/Main,C/OS-II软/硬件体系结构,用于产生 系统时钟,移植时需要编写的代码,移植需要修改的文件,因为COS-II在设计的时候就已经充分考虑了可移植性,所以,COS-II的移植还是相对简单的。根据上图C/OS-体系结构特点,在C/OS-移植过程中涉及到移植的代码都包含在三个文件中:,OS_CPU.H,OS_CPU_C.C,OS_CPU_A.S,与处理器相关的常数、宏及数据类型定义,任务堆栈初始化和系统接口函数的编写,需要用C语言编写10个函数,编写4个汇编语言函数,C/OS-II移植文件提要,OS_CPU_C.C,OS_CPU_A.S,OS_CPU.H,OS_CPU.H-ARM编译器支持的数据类型,C/OS-II中不直接使用C语言中的short、int、long等数据类型定义,因为它们与处理器类型有关,隐含着不可移植性。针对不同处理器,编译器需要使用不同的字节长度来表示同一数据类型。ARM编译器支持的数据类型及其对齐方式如下表所示:,OS_CPU.H-根据处理器设置数据类型,选择ADS1.2的armcc.exe编译器,需要定义如下数据类型:,typedef unsigned char BOOLEAN;/*布尔类型*/typedef unsigned char INT8U;/*8 位无符号整数*/typedef signed char INT8S;/*8 位有符号整数*/typedef unsigned short INT16U;/*16 位无符号整数*/typedef signed short INT16S;/*16 位有符号整数*/typedef unsigned int INT 32U;/*32 位无符号整数*/typedef signed int INT32S;/*32 位有符号整数*/typedef float FP32;/*单精度浮点数*/typedef double FP64;/*双精度浮点数*/typedef unsigned int OS_STK;/*堆栈入口宽度为32位*/typedef unsigned int OS_CPU_SR;/*CPU状态寄存器宽度为32位*/,OS_CPU.H-临界段宏,C/OS-II使用宏OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()实现临界段代码的保护。C/OS-II需要先禁止中断再访问临界段的代码,并且在访问完毕后重新允许中断。基于ARM7TDMI Core处理器,可以通过对CPSR的读写来禁止和使能中断,CPSR寄存器的格式下:,条件代码标志,保留,控制位,I=1:禁止IRQ,F=1:禁止FIQ,I,F,OS_CPU.H-临界段宏的实现方法,C/OS-II支持3种不同的方式实现宏OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL(),由OS_CRITICAL_METHOD常量指出。我们使用第3种方式。,#define OS_CRITICAL_METHOD 3#if OS_CRITICAL_METHOD=3 OS_CPU_SR cpu_sr;#define OS_ENTER_CRITICAL()cpu_sr=OSCPUSaveSR();#define OS_EXIT_CRITICAL()OSCPURestoreSR(cpu_sr);#endif,OS_CPU.H-打开/关闭中断,下面是OSCPUSaveSR()和OSCPURestoreSR()两个函数的具体实现代码(在OS_CPU_A.S文件中定义)。OSCPUSaveSR()实现将CPSR的IRQ和FIQ中断屏蔽位置1,并将CPSR初始值保存到变量cpu_sr中。OSCPURestoreSR()将实现将CPSR恢复成cpu_sr保存的值。,OSCPUSaveSR mrs r0,cpsr;当前PSR orr r1,r0,#0 xC0;屏蔽中断位 msr cpsr_c,r1;关中断(IRQ and FIQ)mov pc,lr;返回 OSCPURestoreSR mrs cpsr_c,r0;当前PSR mov pc,lr;返回,7 6,0,31,0,31,7 6,OS_CPU.H-设置栈的增长方式,栈的操作方式是随所使用的处理器不同而不同。根据数据压栈时SP的变化方向不同,栈分为向上增长(SP向高地址方向增长)和向下增长(SP向低地址方向增长)。根据数据压栈时SP指向的数据位置不同,它又可分为满栈(SP指向最后压入栈的数据)和空栈(SP指向最后压入栈的数据的上一个或下一个空位置,这取决于栈的增长方向)。根据上述分类,栈可有四种组合:满递增、满递减、空递增和空递减。基于ARM处理器支持以上四种形式的栈。为了提高可移植性,C/OS-II被设计成支持向上增长和向下增长两种方式,移植时只要配置OS_STK_GROWTH常数以指定堆栈的增长方向就可以了。置OS_STK_GROWTH为0,表示堆栈向上增长;置OS_STK_GROWTH为1,表示堆栈向下增长。但由于ADS1.2的C语言编译器仅支持满递减方式。所以栈的增长方向只能一种情况,定义如下:,#define OS_STK_GROWTH 1/*栈由高地址向低地址增长*/,OS_CPU.H-C/OS-II任务调度原理,C/OS-II是抢占式实时多任务操作系统,它总是运行进入就绪态任务中优先级最高的那一个。令处理器中止当前正在运行的任务转而去运行另一个更高优先级的就绪任务的工作叫做任务切换(context switch)。任务切换主要做的事情就是保存正在运行(将被挂起)任务的当前状态(即处理器寄存器的全部内容)到该任务的栈中,并且把将要运行的更高优先级任务的当前状态从栈中恢复到处理器寄存器中。因此,在C/OS-中,设计的每个任务都要有自己的栈,并且处于就绪状态的任务的栈结构看起来就像刚发生过中断一样,所有的寄存器都保存在栈中。在任务就绪表中查找最高优先级的任务并切换到该任务运行的工作是由调度器(Scheduler)完成的。C/OS-II有两种调度器:一种是任务级的调度器;另一种是中断级的调度器。任务级的调度是由函数OSSched()完成的,其中任务切换是由宏OS_TASK_SW()完成的。中断级的调度是由另一个函数OSIntExit()完成的,其中任务切换是由函数OSIntCtxSw()完成的。这里主要定义OS_TASK_SW()宏。,OS_CPU.H-定义OS_TASK_SW(),为了使任务切换时象发生了中断一样,OS_TASK_SW()可以采用两种方法实现。方法一,采用软件中断或指令陷阱来完成这项功能。多数处理器支持此功能。这种情况要求中断向量地址必须指向中断服务子程序或指令陷阱处理函数OSCtxSw()。方法二,采用模拟中断发生来完成这项功能。一些处理器不提供软件中断机制,这种情况下,用户需要模拟软件中断,将栈结构设置成与软件中断发生后的栈结构一样。此时OS_TASK_SW()只会简单得调用OSCtxSw()而不是将向量地址指向OSCtxSw()。本文采用第二种方法定义宏,如下所示。OSCtxSw()将在文件OS_CPU_A.S中详细说明。,#define OS_TASK_SW()OSCtxSw(),C/OS-II移植文件提要,OS_CPU.H,OS_CPU_A.S,OS_CPU_C.C,OS_CPU_C.C-编写相关函数,OS_CPU.C的移植包括任务堆栈初始化和系统接口(钩子)函数的编写,需要用C语言编写10个操作系统相关的函数。,OS_CPU_C.C-OSTaskStkInit()栈结构,该函数用于初始化任务堆栈,使任务的堆栈看起来就像刚发生中断一样,即任务被执行时,就像从中断返回一样。在编写此函数之前,必须先确定任务的堆栈结构。而任务的堆栈结构是与CPU的体系结构、编译器有密切的关联。根据ADS1.2编译器使用的是满递减栈,本移植任务栈的结构设计如下图所示。,OS_CPU_C.C-任务堆栈初始化示意图,堆栈指针SP,高端内存,低端内存,说明:首先,OSTaskStkInit()将任务起始地址保存到栈中;其次OSTaskStkInit()将寄存器R13R1保存到栈中,这里将它们分别设定一个值;因为ADS1.2编译器是使用寄存器R0传递第一个参数的,所以将p_arg保存到栈中R0的位置;最后需要保存CPSR和SPSR;在初始化栈后,OSTaskStkInit()应当返回堆栈指针SP指向的地址。OSTaskCreate()或OSTaskCreateExt()得到这个地址并且保存在该任务的控制块中。,OS_CPU_C.C-OSTaskStkInit()代码分析,OS_STK*OSTaskStkInit(void(*task)(void*pd),void*p_arg,OS_STK*ptos,INT16U opt)OS_STK*stk;opt=opt;stk=ptos;*stk=(OS_STK)task;/*R15(PC)*/*-stk=(OS_STK)task;/*R14(LR)*/*-stk=0;/*R12*/*-stk=0;/*R11*/*-stk=0;/*R10*/*-stk=0;/*R9*/*-stk=0;/*R8*/*-stk=0;/*R7*/*-stk=0;/*R6*/*-stk=0;/*R5*/*-stk=0;/*R4*/*-stk=0;/*R3*/*-stk=0;/*R2*/*-stk=0;/*R1*/*-stk=(unsigned int)p_arg;/*R0*/*-stk=(ARMSVC32MODE|0 x40);/*CPSR*/*-stk=(ARMSVC32MODE|0 x40);/*SPSR*/return(OS_STK*)stk);,C/OS-II移植文件提要,OS_CPU.H,OS_CPU_C.C,OS_CPU_A.S,OS_CPU_A.S-编写与处理器相关的函数,在OS_CPU_A.S文件中,C/OS-II的移植要求用户编写4个汇编语言函数:OSStartHighRdy()OSCtxSw()OSTickISR()OSIntCtxSw(),如果编译器支持C语言内嵌汇编代码,就可以将所有与处理器相关代码放到OS_CPU_C.C文件中,而不必使用单独的汇编语言文件。,Contents,OSStartHighRdy(),1,OSCtxSw(),2,OSTickISR(),3,OSIntCtxSw(),4,OS_CPU_A.S-OSStartHighRdy()函数,C/OS-II启动多任务环境的函数是OSStart()。在调用OSStart()之前,必须先调用OSInit()。当调用OSStart()时,首先从任务就绪表中找出优先级最高任务的任务控制块,然后OSStartHighRdy()函数负责从优先级最高就绪任务的TCB控制块中获得该任务的堆栈指针SP,通过SP依次将CPU现场恢复,这时系统就将控制权交给用户创建的该任务进程。该函数仅在OSStart()多任务启动时被执行一次,之后多任务的切换由OSCtxSw()或OSIntCtxSw()函数实现。OSStartHighRdy()函数只是做了任务切换工作的一半,即只是完成了高优先级任务寄存器的恢复,因为在这之前C/OS-II没有启动,所以没有任务在运行,就没有CPU的寄存器需要保存。,OS_CPU_A.S-OSStartHighRdy()执行流程图,OS_CPU_A.S-OSStartHighRdy()代码分析,示意代码分析,OSStartHighRdy;MSR CPSR_c,#(ARM_INT_DIS|ARM_SVC_MODE)BL OSTaskSwHook LDR R0,=OSRunning MOV R1,#1 STRB R1,R0 LDR R0,=OSTCBHighRdy LDR R0,R0 LDR SP,R0;LDR R1,=OSTCBCur;STR R0,R1 LDMFD SP!,R0 MSR SPSR_cxsf,R0 LDMFD SP!,R0 MSR CPSR_cxsf,R0 LDMFD SP!,R0-R12,LR,PC,Conte