第五讲 C/OS-Ⅱ移植分析和系统初始化 嵌入式系统设计与实例开发 ——ARM与C/OS-Ⅱ 第五讲 C/OS-Ⅱ移植分析和系统初始化 北京航空航天大学 嵌入式机电控制研究室
提 要 1 嵌入式系统的初始化 2 C/OS-Ⅱ移植分析
初始化程序的下载执行 宿主机 目标机 1)通过编程器将可执行目标文件烧写到BootROM(ROM、EPROM、FLASH)等; 2)通过串行口和网口下载执行目标文件,要求宿主机系统上有数据传输工具程序、目标机装载器、嵌入式监视器或目标机系统上的调试代理; 3)通过JTAG或BDM接口下载。 宿主机 目标机
嵌入式系统的初始化过程
嵌入式系统的初始化过程 硬件初始化阶段 1. 复位向量 ENTRY b ResetHandler ;for debug b HandlerUndef ;handlerUndef b HandlerSWI ;SWI interrupt handler b HandlerPabort ;handlerPAbort b HandlerDabort ;handlerDAbort b . ;handlerReserved b HandlerIRQ b HandlerFIQ
嵌入式系统的初始化过程(2) 2. 最小硬件初始化 硬件初始化阶段 1)设置适当的寄存器,使嵌入式处理器处于一个已知的状态: 获得CPU的类型。 获得或设置CPU的时钟频率。 2)禁止中断和高速缓存。 3)初始化内存控制器、内存芯片和高速缓存单元,包括: 得到内存的开始地址。 得到内存的大小。 如果有要求,则还需要进行主存测试
嵌入式系统的初始化过程(3) 3. 其余硬件初始化 硬件初始化阶段 引导代码调用合适的函数对目标机系统上的全部硬件部件进行初始化,包括: 建立执行处理程序。 初始化中断处理程序。 初始化总线接口。 初始化板级外设得到内存的开始地址。
嵌入式系统的初始化过程(4) 4.RTOS初始化 RTOS初始化阶段 1)RTOS初始化 2)RTOS对象和服务初始化 任务 信号量 定时器 中断 内存管理 3)RTOS任务堆栈初始化 4)RTOS扩展部件初始化 5)启动RTOS
嵌入式系统的初始化过程(5) 5.应用程序初始化 应用程序初始化阶段
ARM7TDMI系统初始化的一般过程 启动(系统上电/复位) 从程序入口点 关闭中断 初始化时钟等硬件相关寄存器 初始化存储器系统 初始化C所需要的存储器空间 调用C入口函数
一、设置程序入口指针 上电复位后直接到程序入口点执行,入口点一般为一个跳转表,跳转到复位处理程序处开始执行ARM7TDMI系统的初始化。 启动程序首先必须定义入口指针,而且整个应用程序只有一个入口指针。 例:AREA Boot,CODE,READONLY ENTRY /*设置程序入口指针*/
二、设置中断向量 ARM要求中断向量必须设置在从OX00000000地址开始,连续8*4字节的地址空间。 向量表包含一系列跳转指令,跳转到相应的中断服务程序。 对各未用中断,使其指向一个含返回指令的哑函数,以防止错误中断引起系统的混乱。
中断向量表 FIQ 0x1C 外部快速中断 IRQ 0x18 一般外部中断 (Reserved) 0x14 保留 Data Abort 数据异常 Frefetch Abort 0x0C 预取指异常 Software int 0x08 软件中断 Undef 0x04 未定义指令中断 Reset 0x00 复位中断
中断向量表的程序 AREA Boot,CODE,READONLY ENTRY B Reset_handler B Undef_Handler B SWI_Handler B PreAbort_Handler B . ;for reserved interrupt,stop here B IRQ_handler B FIQ_handler
三、初始化时钟和设置相关的寄存器 通过设置时钟控制器来确定CPU的工作频率,设置中断控制寄存器屏蔽中断。
四、初始化存储器系统 参考芯片手册,设置与内存映射相关的寄存器。
五、初始化堆栈 ARM处理器有好几种运行状态(模式),各种状态都需要有自己的堆栈,所以需要分别为这些堆栈分配空间并设置好各自的堆栈指针。 如果系统使用了DRAM或其他外设,需要设置一些寄存器,以确定其刷新频率、数据总线宽度等信息。有的系统还需设置一些寄存器来控制SDRAM、USB、网络接口等。
六、初始化C环境 在目标文件中,代码、数据放在不同的段中。源文件编译链接生成含.data、.text段的目标文件,且链接器生成的.data段是以系统RAM为参考地址。 故在系统启动时需要复制ROM或FLASH中的.data段到RAM,以完成对RAM的初始化。在初始化期间应将系统需要读写的数据和变量从ROM复制到RAM里运行。
链接器产生的符号表 符号由链接器自动产生,只读段(read-only RO)就是代码段,读写段(read-write RW)是已经初始化的全局变量,而零初始化段(zero-initialized section ZI)中存放未初始化的全局变量。
初始化C环境(2) C环境初始化,就是利用上述符号初始化RW和ZI段以使后面使用的全局变量的C程序正常运行。 这里有两个循环,第一个循环把预初始化的数据段(位于代码段的后面)复制到RAM中,另一个循环把未初始化的数据段ZI初始化为0,也就是实现把从ROM中的.data段复制到RAM,对ZI段内的数据初始化为0,以完成对C环境的初始化。
ROM地址的重映射 RAM Flash 0x0204 0x0200 (remap) 0x0100 (boot code) (Reset_handler) …… B Reset_Handler 0x0000 Flash 0x0204 RAM
初始化C环境(3)
七、呼叫C程序 对main函数的调用进入µC/OS 的入口,通过这个入口就进入µC/OS 的主函数,启动对µC/OS 的初始化。 例 IMPORT Main b Main ;C Entry
µC/OS 系统的初始化 完成了前面的硬件初始化和运行环境的相关设置后,进入Main(), Main()是µC/OS 的入口函数,启动对µC/OS 的初始化。
ARM7的硬件抽象层——uHALµC/OS ARM公司为操作系统的开发提供了一个硬件抽象层HAL,称为uHAL。 从结构上看,uHAL是一组库程序,需要说明的是,uHAL并不是专门为µC/OS 准备的,甚至也不是专为操作系统内核准备的。 uHAL只是个针对ARM核的函数库。 µC/OS 是建立在uHAL的基础之上的。
µC/OS 系统的初始化(2)
ARMTargetInit()函数结构 ARMTargetInit() 调uHAL打印接口打印系统信息 调用uHAL函数禁止所有中断 uHAL函数对ARM计数器初始化 结束
uHAL的功能 uHAL的作用之一是在操作系统本身进入正常运行之前,为系统提供基本的输入输出手段,例如uHALr_printf()等。 通过uHAL_ResetMMU(),将MMU设置在一个确定的初始状态。 通过ARMDisable()关闭中断。 通过uHAL_InitInterrupts()设置中断向量处理程序。 通过uHAL_InitTimer()对系统使用的计数器进行初始 化。
ARMTargetStart()的分析 创建了任务之后,ARMTargetStart()调用uHALr_InstallSystemTimer()创建一个系统时钟,为时钟中断做好准备。
提 要 1 嵌入式系统的初始化 2 C/OS-Ⅱ移植分析
操作系统移植的概念 所谓操作系统的移植,是指使一个实时操作系统能够在某个微处理器平台上运行。 COS-II的主要代码都是由标准的C语言写成的,移植方便。 移植的主要工作是修改部分与处理器硬件相关的代码。
移植的层次 操作系统的移植大体可以分为两个层次: 跨体系结构的移植。 针对特定处理器的移植。
移植COS-II满足的条件 处理器的C编译器能产生可重入代码。 在程序中可以打开或者关闭中断。 处理器支持中断,并且能产生定时中断(通常在10-100Hz之间)。 处理器支持能够容纳一定量数据的硬件堆栈。 处理器有将堆栈指针和其他CPU寄存器存储和读出到堆栈(或者内存)的指令。
什么是可重入代码 可重入的代码指的是一段代码(比如:一个函数)可以被多个任务同时调用,而不必担心会破坏数据。 也就是说,可重入型函数在任何时候都可以被中断执行,过一段时间以后又可以继续运行,而不会因为在函数中断的时候被其他的任务重新调用,影响函数中的数据。
可重入代码举例 void swap(int *x, int *y) { int temp; temp=*x; *x=*y; *y=temp; 程序1:可重入型函数 void swap(int *x, int *y) { int temp; temp=*x; *x=*y; *y=temp; }
非可重入代码举例 程序2:非可重入型函数 int temp; void swap(int *x, int *y) { temp=*x; *x=*y; *y=temp; } 返回
不可重入函数被中断破坏
如何使函数具有可重入性 使Swap()函数具有可重入性: 把Temp定义为局部变量。 调用Swap()函数之前关中断,调动后再开中断。 用信号量禁止该函数在使用过程中被再次调用。
打开/关闭中断 在COS-II中,可以通过: OS_ENTER_CRITICAL() OS_EXIT_CRITICAL() 宏来控制系统关闭或者打开中断。这需要处理器的支持。 在ARM7TDMI的处理器上,可以设置相应的寄存器来关闭或者打开系统的所有中断。
处理器支持中断并且能产生定时中断 COS-II是通过处理器产生的定时器的中断来实现多任务之间的调度的。 ARM7TDMI的处理器上可以产生定时器中断。
处理器支持硬件堆栈 COS-II进行任务调度的时候,会把当前任务的CPU寄存器存放到此任务的堆栈中,然后,再从另一个任务的堆栈中恢复原来的工作寄存器,继续运行另一个任务。所以,寄存器的入栈和出栈是COS-II多任务调度的基础。 ARM7处理器中有专门的指令处理堆栈,可以灵活的使用堆栈。
移植对开发工具的要求 移植COS-II需要一个标准的C交叉编译器。 由于移植时需要对CPU的寄存器进行操作,所以需要C交叉编译器能够支持汇编语言程序。 嵌入式C编译器一般都包括汇编器、链接器和定位器。链接器是用来将不同的模块(编译或汇编过的文件)链接成目标文件;定位器则允许将代码和数据放置在目标处理器的指定内存空间中。
移植µC/OS-II要点(1) 开关中断的方式。推荐使用method3 { #if OS_CRITICAL_METHOD == 3 OS_CPU_SR cpu_sr; #endif … OS_ENTER_CRITICAL(); OS_EXIT_CRITICAL(); }
使用method3方式的开关中断 #define OS_ENTER_CRITICAL() { cpu_sr = INTS_OFF(); } #define OS_EXIT_CRITICAL() { if(cpu_sr == 0) INTS_ON(); }
ARM的中断模式 设备的中断在ARM中被映射到了两个异常中断中——FIQ和IRQ。 通过控制CPSR中的对应数据位,可以开启或者关闭中断。 为了方便和统一µC/OS-II系统中断的处理,只使用了IRQ模式的中断。
移植µC/OS-II要点(2)——系统中断的处理 所有中断的调用都需要经过系统的接管。中断处理函数调用前后需要通知系统。例如: OSIntEnter(); yourInterruptFun(); OSIntExit();
OSIntExit的意义
ARM的工作模式 ARM处理器有7种操作模式: 用户模式(usr) 正常的程序执行模式。 快速中断模式(fiq) 支持高速数据传输或通道处理。 中断模式(irq) 用于通用中断处理。 管理员模式(svc) 操作系统的保护模式。 中止模式(abt) 支持虚拟内存和/或内存保护等异常。 系统模式(sys) 支持操作系统的特殊用户模式(运行操作系统任务)。 未定义模式(und) 支持硬件协处理器的软件仿真。 除了用户模式外,其他模式均可视为特权模式
ARM的寄存器(1) 37个寄存器: 可见的寄存器取决于处理器的模式,不同的模式映射了不同的工作寄存器。 31 个通用32位寄存器,包括程序计数器PC。 6 个状态寄存器。 15个通用寄存器 (R0 to R14), 以及2个状态寄存器和程序计数器(PC)在任何时候都中可见的。 可见的寄存器取决于处理器的模式,不同的模式映射了不同的工作寄存器。
ARM寄存器的组织 注:表明用户或系统模式使用的正常寄存器已经被异常模式指定的另一个寄存器取代
ARM的寄存器(2) R0 到 R15 可以直接访问。 R0 到 R14 是通用寄存器。 R13: 堆栈指针 (sp) (通常)。 每种处理器模式都有单独的堆栈。 R14: 链接寄存器 (lr)。 R15:程序计数器 (PC)。 CPSR :当前程序状态寄存器,包括代码标志状态和当前模式位。 5个SPSR(程序状态保存寄存器) :当异常发生时保存CPSR状态。
µC/OS-II在ARM上的任务切换 任务级的任务切换; 中断级的任务切换。
中断处理过程
OSIntExit() void OSIntExit (void){ OS_ENTER_CRITICAL(); (1) if ((--OSIntNesting | OSLockNesting) == 0) { (2) OSIntExitY = OSUnMapTbl[OSRdyGrp]; (3) OSPrioHighRdy = (INT8U)((OSIntExitY << 3) + OSUnMapTbl[OSRdyTbl[OSIntExitY]]); if (OSPrioHighRdy != OSPrioCur) { OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]; OSCtxSwCtr++; OSIntCtxSw(); (4) } OS_EXIT_CRITICAL();
OSIntExit的关键——OSIntCtxSw 实现中断级的任务切换 ARM在栈指针调整过程中的优势
移植µC/OS-II要点(3)——Thumb带来的问题 很多ARM内核集成了16位thumb指令集。 Thumb可以在一定程度上节省代码空间,提高系统效率。 Thumb会给中断级的任务切换带来麻烦。 CPSR中的T位不能直接操作。 Thumb状态将导致CPSR恢复以后的指令不能运行。 解决办法: 对Thumb的使用必须保证原子操作。 专门对任务切换中Thumb的情况作处理。 建议,小心使用C编译器,尽量不使用Thumb。
移植µC/OS-II的要点(4)—何时启动系统定时器 如果在OSStart之前启动定时器,则系统可能无法正确执行完OSStartHighRdy。 OSStart函数直接调用OSStartHighRdy去执行最高优先级的任务,OSStart不返回。 系统定时器应该在系统的最高优先级任务中启动。 使用OSRunning变量来控制操作系统的运行。 在我们的移植版本中,使用了µC/OS-II中的保留任务1作为系统任务,负责启动定时器。
例:C/OS-II在S3C44B0X上的移植 设置OS_CPU.H中与处理器和编译器相关的代码 。 用C语言编写六个操作系统相关的函数(OS_CPU_C.C)。 用汇编语言编写四个与处理器相关的函数(OS_CPU.ASM)。
设置与处理器和编译器相关的代码 OS_CPU.H中定义了与编译器相关的数据类型。比如:INT8U、INT8S等。 与 ARM处理器相关的代码,使用OS_ENTER_CRITICAL() 和OS_EXIT_CRITICAL() 宏开启/关闭中断。 设置堆栈的增长方向 :堆栈由高地址向低地址增长 。
设置includes.h typedef unsigned char BOOLEAN; typedef unsigned char INT8U; typedef signed char INT8S; typedef unsigned int INT16U; typedef signed int INT16S; typedef unsigned long INT32U; typedef signed long INT32S; typedef float FP32; typedef double FP64; typedef unsigned long OS_STK; typedef unsigned long OS_CPU_SR; extern int INTS_OFF(void); extern void INTS_ON(void); #define OS_ENTER_CRITICAL() { cpu_sr = INTS_OFF(); } #define OS_EXIT_CRITICAL() { if(cpu_sr == 0) INTS_ON(); } #define OS_STK_GROWTH 1 /*从高向低*/
程序状态寄存器 条件位: 中断禁止位: N = 1-结果为负,0-结果为正或0 I = 1: 禁止 IRQ F = 1: 禁止 FIQ 27 31 N Z C V Q 28 6 7 I F T mode 16 23 8 15 5 4 24 f s x c U n d e f i n e d J 条件位: N = 1-结果为负,0-结果为正或0 Z = 1-结果为0,0-结果不为0 C =1-进位,0-借位 V =1-结果溢出,0结果没溢出 Q 位: 仅ARM 5TE/J架构支持 指示增强型DSP指令是否溢出 J 位 J = 1: 处理器处于Jazelle状态 中断禁止位: I = 1: 禁止 IRQ F = 1: 禁止 FIQ T Bit 仅ARM xT架构支持 T = 0: 处理器处于 ARM 状态 T = 1: 处理器处于 Thumb 状态 Mode位(处理器模式位): 0b10000 User 0b10001 FIQ 0b10010 IRQ 0b10011 Supervisor 0b10111 Abort 0b11011 Undefined 0b11111 System
打开/关闭中断 EXPORT INTS_OFF EXPORT INTS_ON 31 7 INTS_OFF mrs r0, cpsr ; current CSR mov r1, r0 ; make a copy for masking orr r1, r1, #0xC0 ; mask off int bits msr CPSR_cxsf, r1 ; disable ints and r0, r0, #0x80 ; return IRQ bit mov pc,lr ; return INTS_ON bic r0, r0, #0x80 ; mask on ints msr CPSR_cxsf, r0 ; enable ints 31 7 … I F T Mode 31 7 … 1 31 7 … 1
设置OS_STK_GROWTH 绝大多数的微处理器和微控制器的堆栈是从上往下长的。但是某些处理器是用另外一种方式工作的。C/OS-Ⅱ被设计成两种情况都可以处理,只要在结构常量OS_STK_GROWTH中指定堆栈的生长方式就可以了。 置OS_STK_GROWTH为0表示堆栈从下往上长。 置OS_STK_GROWTH为1表示堆栈从上往下长。
用C语言编写六个操作系统相关的函数 void *OSTaskStkInit (void (*task)(void *pd),void *pdata, void *ptos, INT16U opt) void OSTaskCreateHook (OS_TCB *ptcb) void OSTaskDelHook (OS_TCB *ptcb) void OSTaskSwHook (void) void OSTaskStatHook (void) void OSTimeTickHook (void) 后5个函数为接口函数,可以不加代码。
OSTaskStkInit OSTaskCreate()和OSTaskCreateExt()通过调用OSTaskStkInit()来初始化任务的堆栈结构。因此堆栈看起来就像刚发生过中断并将所有的寄存器保存到堆栈中的情形一样。这里我们定义了堆栈是从上往下长的。 在用户建立任务时,用户传递任务的地址、pdata指针、任务的堆栈栈顶和任务的优先级给OSTaskCreate()和OSTaskCreateExt()。一旦用户初始化了堆栈,OSTaskStkInit()就需要返回堆栈指针所指的地址。OSTaskCreate()和OSTaskCreateExt()会获得该地址并将它保存到任务控制块(OS_TCB)中。
堆栈初始化 低地址内存 存储的处理器寄存器值 中断返回地址 处理器状态字 任务起始地址 pdata 高地址内存 堆栈指针 堆栈增长方向
ARM系统的堆栈初始化 低端内存 备份程序状态寄存器SPSR 当前程序状态寄存器CPSR 堆栈指针SP 任务传递参数R0(pdata) R1 …… R11 R12 中断返回地址LR 任务起始地址ptos(PC) 高端内存 堆栈指针SP 堆栈增长方向
OSTaskStkInit OS_STK * OSTaskStkInit (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT16U opt) { unsigned int * stk; stk = (unsigned int *)ptos; /* Load stack pointer */ //USE_ARG(opt); opt++; /* build a stack for the new task */ *--stk = (unsigned int) task; /* pc */ *--stk = (unsigned int) task; /* lr */ *--stk = 12; /* r12 */ *--stk = 11; /* r11 */ *--stk = 10; /* r10 */ *--stk = 9; /* r9 */ *--stk = 8; /* r8 */ *--stk = 7; /* r7 */ *--stk = 6; /* r6 */ *--stk = 5; /* r5 */ *--stk = 4; /* r4 */ *--stk = 3; /* r3 */ *--stk = 2; /* r2 */ *--stk = 1; /* r1 */ *--stk = (unsigned int) pdata; /* r0 */ *--stk = (SUPMODE); /* cpsr */ *--stk = (SUPMODE); /* spsr */ return ((OS_STK *)stk); }
OSTaskCreateHook 当用OSTaskCreate()和OSTaskCreateExt()建立任务的时候就会调用OSTaskCreateHook()。该函数允许用户或使用移植实例的用户扩展C/OS-Ⅱ功能。当C/OS-Ⅱ设置完了自己的内部结构后,会在调用任务调度程序之前调用OSTaskCreateHook()。该函数被调用的时候中断是禁止的。因此用户应尽量减少该函数中的代码以缩短中断的响应时间。 当OSTaskCreateHook()被调用的时候,它会收到指向已建立任务的OS_TCB的指针,这样它就可以访问所有的结构成员了。 函数原型:void OSTaskCreateHook (OS_TCB *ptcb) { ptcb=ptcb; }
OSTaskDelHook 当任务被删除的时候,就会调用OSTaskDelHook()函数。该函数在把任务从C/OS-Ⅱ的内部任务链表中删除之前被调用。当该函数被调用的时候,它会收到指向正被删除任务的OS_TCB的指针,这样它就可以访问所有的结构成员了。OSTaskDelHook()可以来检验TCB扩展是否被建立(一个非空指针),并进行一些清除操作。 函数原型:void OSTaskDelHook (OS_TCB *ptcb) { ptcb=ptcb; }
OSTaskSwHook 当发生任务切换的时候就会调用OSTaskSwHook()。OSTaskSwHook()可以直接访问OSTCBCur和OSTCBHighRdy,因为它们是全局变量。OSTCBCur指向被切换出去的任务OS_TCB,而OSTCBHighRdy指向新任务OS_TCB。注意在调用OSTaskSwHook()期间中断一直是被禁止的。因此用户应尽量减少该函数中的代码以缩短中断的响应时间。 函数原型:void OSTaskSwHook (void) { #if 0 if(OSRunning==TRUE){ /*保存拟被挂起任务的寄存器; } /*恢复拟被运行任务的寄存器; #endif
OSTaskStatHook OSTaskStatHook()每秒钟都会被OSTaskStat()调用一次。用户可以用OSTaskStatHook()来扩展统计功能。例如,用户可以保持并显示每个任务的执行时间,每个任务所用的CPU份额,以及每个任务执行的频率等。 函数原型:void OSTaskStatHook (void)
OSTimeTickHook OSTimeTickHook()在每个时钟节拍都会被OSTaskTick()调用。实际上,OSTimeTickHook()是在节拍被C/OS-Ⅱ真正处理,并通知用户的移植实例或应用程序之前被调用的。 函数原型:void OSTimeTickHook (void)
用汇编语言编写四个 与处理器相关的函数 OSStartHighRdy() OSCtxSw() OSIntCtxSw() OSTickISR()
OSStartHighRdy():运行优先级最高的就绪任务 LDR r4, addr_OSTCBCur ; 得到当前任务TCB地址 LDR r5, addr_OSTCBHighRdy ; 得到最高优先级任务TCB地址 LDR r5, [r5] ; 获得堆栈指针 LDR sp, [r5] ; 转移到新的堆栈中 STR r5, [r4] ; 设置新的当前任务TCB地址 LDMFD sp!, {r4} ; MSR SPSR, r4 LDMFD sp!, {r4} ; 从栈顶获得新的状态 MSR CPSR, r4 ; CPSR 处于 SVC32Mode摸式 LDMFD sp!, {r0-r12, lr, pc } ; 运行新的任务
OSCtxSw()的原型 void OSCtxSw(void) { 保存处理器寄存器; 将当前任务的堆栈指针保存到当前任务的OS_TCB中: OSTCBCur->OSTCBStkPtr = Stack pointer; 调用用户定义的OSTaskSwHook(); OSTCBCur = OSTCBHighRdy; OSPrioCur = OSPrioHighRdy; 得到需要恢复的任务的堆栈指针: Stack pointer = OSTCBHighRdy->OSTCBStkPtr; 将所有处理器寄存器从新任务的堆栈中恢复出来; 执行中断返回指令; }
OS_TASK_SW ();任务级的任务切换函数(1) STMFD sp!, {lr} ; 保存 pc STMFD sp!, {lr} ; 保存 lr STMFD sp!, {r0-r12} ; 保存寄存器和返回地址 MRS r4, CPSR STMFD sp!, {r4} ; 保存当前的PSR MRS r4, SPSR STMFD sp!, {r4} ; 保存SPSR ; OSPrioCur = OSPrioHighRdy LDR r4, addr_OSPrioCur LDR r5, addr_OSPrioHighRdy LDRB r6, [r5] STRB r6, [r4]
OS_TASK_SW():任务级的任务切换函数(2) ; 得到当前任务TCB地址 LDR r4, addr_OSTCBCur LDR r5, [r4] STR sp, [r5] ; 保存sp在被占先的任务的 TCB ; 得到最高优先级任务TCB地址 LDR r6, addr_OSTCBHighRdy LDR r6, [r6] LDR sp, [r6] ; 得到新任务堆栈指针 ; OSTCBCur = OSTCBHighRdy STR r6, [r4] ; 设置新的当前任务的TCB地址 ;保存任务方式寄存器 LDMFD sp!, {r4} MSR SPSR, r4 MSR CPSR, r4 ; 返回到新任务的上下文 LDMFD sp!, {r0-r12, lr, pc}
中断服务
关于栈指针调整
栈指针调整 调整堆栈指针(加一个数在堆栈指针上)来完成的。加在堆栈指针上的数必须是明确的,而这个数主要依赖于移植的目标处理器(地址空间可能是16,32或64位),所用的编译器,编译器选项,内存模式等等。另外,处理器状态字可能是8,16,32甚至64位宽,并且OSIntExit()可能会分配局部变量。有些处理器允许用户直接增加常量到堆栈指针中,而有些则不允许。在后一种情况下,可以通过简单的执行一定数量的pop(出栈)指令来实现相同的功能。一旦堆栈指针完成调整,新的堆栈指针会被保存到被切换出去的任务的OS_TCB中 。
OSIntCtxSW的实现
OSIntCtxSw()的原型 void OSIntCtxSw(void) { 调整堆栈指针来去掉在调用: OSIntExit(), 将当前任务堆栈指针保存到当前任务的OS_TCB中: OSTCBCur->OSTCBStkPtr = 堆栈指针; 调用用户定义的OSTaskSwHook(); OSTCBCur = OSTCBHighRdy; OSPrioCur = OSPrioHighRdy; 得到需要恢复的任务的堆栈指针: 堆栈指针 = OSTCBHighRdy->OSTCBStkPtr; 将所有处理器寄存器从新任务的堆栈中恢复出来; 执行中断返回指令; }
OSIntCtxSW ();中断级的任务切换函数(1) add r7, sp, #16 ; 保存寄存器指针 LDR sp, =IRQStack ;FIQ_STACK mrs r1, SPSR ; 得到暂停的 PSR orr r1, r1, #0xC0 ; 关闭 IRQ, FIQ. msr CPSR_cxsf, r1 ; 转换模式 (应该是 SVC_MODE) ldr r0, [r7, #52] ; 从IRQ堆栈中得到IRQ's LR (任务 PC) sub r0, r0, #4 ; 当前PC地址是(saved_LR - 4) STMFD sp!, {r0} ; 保存任务 PC STMFD sp!, {lr} ; 保存 LR mov lr, r7 ; 保存 FIQ 堆栈 ptr in LR (转到 nuke r7) ldmfd lr!, {r0-r12} ; 从FIQ堆栈中得到保存的寄存器 STMFD sp!, {r0-r12} ;在任务堆栈中保存寄存器
OSIntCtxSW ();中断级的任务切换函数(2) ;在任务堆栈上保存PSR 和任务 PSR MRS r4, CPSR bic r4, r4, #0xC0 ; 使中断位处于使能态 STMFD sp!, {r4} ; 保存任务当前 PSR MRS r4, SPSR STMFD sp!, {r4} ; SPSR ; OSPrioCur = OSPrioHighRdy // 改变当前程序 LDR r4, addr_OSPrioCur LDR r5, addr_OSPrioHighRdy LDRB r6, [r5] STRB r6, [r4] ; 得到被占先的任务TCB LDR r4, addr_OSTCBCur LDR r5, [r4] STR sp, [r5] ; 保存sp 在被占先的任务的 TCB
OSIntCtxSW ();中断级的任务切换函数(3) ; 得到新任务 TCB 地址 LDR r6, addr_OSTCBHighRdy LDR r6, [r6] LDR sp, [r6] ; 得到新任务堆栈指针 ; OSTCBCur = OSTCBHighRdy STR r6, [r4] ; 设置新的当前任务的TCB地址 LDMFD sp!, {r4} MSR SPSR, r4 BIC r4, r4, #0xC0 ; 必须退出新任务通过允许中断 MSR CPSR, r4 LDMFD sp!, {r0-r12, lr, pc}
测试µC/OS-II(1) /*任务定义*/ OS_STK SYS_Task_Stack[STACKSIZE]= {0, }; //system task刷新任务堆栈 #define SYS_Task_Prio 1 void SYS_Task(void *Id); OS_STK Task1_Stack[STACKSIZE]={0, }; void Task1(void *Id); #define Task1_Prio 12 OS_STK Task2_Stack[STACKSIZE]= {0, }; void Task2(void *Id); #define Task2_Prio 13
测试µC/OS-II(2) int Main(int argc, char **argv) { ARMTargetInit(); // do target (uHAL based ARM system) initialisation // OSInit(); OSTaskCreate(SYS_Task, (void *)0, (OS_STK *)&SYS_Task_Stack[STACKSIZE-1], SYS_Task_Prio); OSTaskCreate(Task2, (void *)0, (OS_STK *)&Task2_Stack[STACKSIZE-1], Task2_Prio); OSTaskCreate(Task1, (void *)0, (OS_STK *)&Task1_Stack[STACKSIZE-1], Task1_Prio ); OSStart(); // start the game // // never reached // return 0; }//
测试µC/OS-II(3) void Task1(void *Id) { for(;;){ Uart_Printf("run task1\n"); OSTimeDly(1000); } void Task2(void *Id) for (;;) Uart_Printf("run task2\n"); OSTimeDly(2000); void SYS_Task(void *Id) uHALr_InstallSystemTimer(); Uart_Printf("start system task.\n"); OSTimeDly(10000);
关于移植 相对于其他的嵌入式操作系统,µC/OS-II的移植虽然是一个很简单的过程,但是,对于不熟悉µC/OS-II的开发者,移植还是有一定难度的。
移植要点 定义函数OS_ENTER_CRITICAL和OS_ENTER_CRITICAL。 定义函数OS_TASK_SW执行任务切换。 定义函数OSCtxSw实现用户级上下文切换,用纯汇编实现。 定义函数OSIntCtxSw实现中断级任务切换,用纯汇编实现。 定义函数OSTickISR。 定义OSTaskStkInit来初始化任务的堆栈。
µC/OS 的完善 固定的基于优先级的调度,不支持时间片,使用起来不方便。一个任务的基础上增加一个基于时间片的微型调度核。 系统时钟中断,没有提供用户使用定时器,可以借鉴Linux的定时器加以修改。 在对临界资源的访问上使用关闭中断实现,没有使用CPU提供的硬件指令,例如测试并置位。 只是一个实时多任务内核,没有图形用户接口(GUI)、文件系统(FS)和TCP/IP协议栈。
谢 谢 各 位