嵌入式系统 —嵌入式实时操作系统C/OS-Ⅱ分析 2006年5月
主要内容 嵌入式操作系统 C/OS-Ⅱ简介 C/OS-Ⅱ内核结构 C/OS-Ⅱ任务管理 C/OS-Ⅱ时间管理
为什么需要操作系统
硬件抽象层的引入 嵌入式实时系统自底向上包含三个部分 由于嵌入式系统应用的硬件环境差异较大 嵌入式实时操作系统——RTOS 嵌入式实时应用程序 由于嵌入式系统应用的硬件环境差异较大 新增加的中间层位于操作系统和硬件之间,包含了系统中与硬件相关的大部分功能 ,隐蔽了底层硬件的多样性
嵌入式系统的体系结构
HAL简介 硬件抽象层(HAL)是体系结构相关的底层程序 处理系统启动、硬件初始化以及中断与异常 硬件抽象层对内核其它部分提供统一的调用接口 HAL可以提供BSP规范,提供跨平台可移植性
硬件抽象层接口定义和代码设计特点 硬件抽象层具有与硬件密切相关性 硬件抽象层具有与操作系统无关性 接口定义的功能应包含硬件或系统所需硬件支持的所有功能 接口定义简单明了,太多接口函数会增加软件模拟的复杂性 具有可测性的接口设计有利于系统的软硬件测试和集成
HAL设计目标 支持多种的国际主流嵌入式芯片 支持数字电视机顶盒、智能手机、数字化音视频、数字仪表等数字化产品中常见外设的驱动 基于甚高端通讯设备的硬件抽象层,能够支持多CPU体系结构(SMP),以及基于网络元素NE的甚高端通讯设备 基于智能手机的硬件抽象层,能够支持以Intel PCA体系结构为内核的智能手机、以Intel EIA体系结构为内核的智能手机,以及以Motorola的88000系列为内核的PDA 基于IC卡的汇编级硬件抽象层,能够支持数字电视条件接收CA的8/16/32位CPU IC设计和机器码级汇编抽象层
通用操作系统和嵌入式(实时)操作系统 通用操作系统:Windows/NT/XP、Linux、UNIX等,用于PC机、服务器, 嵌入式(实时)操作系统:用于嵌入式设备的操作系统,具有通用操作系统的基本特点,又具有系统实时性、硬件的相关依赖性、软件固态化以及应用的专用性等特点; 嵌入式(实时)操作系统通常包括与硬件相关的底层驱动软件、系统内核、设备驱动接口、通信协议、图形界面、标准化浏览器Browser等; 嵌入式(实时)操作系统的重要指标:实时性(中断响应时间、任务切换时间等)、尺寸(可裁剪性 )、可扩展性(内核、中间件)
嵌入式操作系统的发展 操作系统内核 应用程序 驱动程序和固件 文件系统 API GUI 通信协议 库函数 80年代初期 80年代中期-90年代中期 90年代末期-21世纪
常见的嵌入式操作系统 实时嵌入式操作系统的种类繁多,大体上可分为两种,商用型和免费型。 商用型的实操作系统功能稳定、可靠,有完善的技术支持和售后服务,但往往价格昂贵,如Vxworks、QNX、WinCE、Palm OS等。 免费型的实时操作系统在价格方面具有优势,目前主要有Linux,μC/OS是一种源码开放的商业RTOS
学习嵌入式操作系统 学习一种实时操作系统RTOS,如C/OS-Ⅱ,掌握实时系统的概念和设计方法 嵌入式系统以应用为中心,要选择“适用”的操作系统 嵌入式Linux 自己尝试“写”RTOS
RTOS在嵌入式系统中的位置 应用 BSP 嵌入式硬件平台 RTOS FS 其它组件 C/C++ KERNEL TCP/IP 设备驱动 调试工具 设备I/O BSP 嵌入式硬件平台
主要内容 嵌入式操作系统 C/OS-Ⅱ简介 C/OS-Ⅱ内核结构 C/OS-Ⅱ任务管理 C/OS-Ⅱ时间管理
C/OS简介 1、C/OS——Micro Controller O S,微控制器操作系统 2、 C/OS简介 美国人Jean Labrosse 1992年完成 应用面覆盖了诸多领域,如照相机、医疗器械、音响设备、发动机控制、高速公路电话系统、自动提款机等 1998年C/OS-II,目前的版本C/OS -II V2.61,2.72 2000年,得到美国航空管理局(FAA)的认证,可以用于飞行器中 网站www.ucos-II.com(www.micrium.com)
C/OS的性能特点(一) 公开源代码 可移植性(Portable) 绝大部分 C/OS-II的源码是用移植性很强的ANSI C写的。和微处理器硬件相关的那部分是用汇编语言写的。汇编语言写的部分已经压到最低限度,使得 C/OS-II便于移植到其他微处理器上。 C/OS-II可以在绝大多数8位、16位、32位以至64位微处理器、微控制器 、数字信号处理器(DSP)上运行。 可固化(ROMable) C/OS-II是为嵌入式应用而设计的,这就意味着,只要用户有固化手段(C编译、连接、下载和固化), C/OS-II可以嵌入到用户的产品中成为产品的一部分。 可裁剪(Scalable) 可以只使用 C/OS-II中应用程序需要的那些系统服务。也就是说某产品可以只使用很少几个 C/OS-II调用,而另一个产品则使用了几乎所有 C/OS-II的功能,这样可以减少产品中的 C/OS-II所需的存储器空间(RAM和ROM)。这种可剪裁性是靠条件编译实现的。
C/OS的性能特点(二) 占先式(Preemptive) 多任务 C/OS-II可以管理64个任务,然而,目前这一版本保留8个给系统。应用程序最多可以有56个任务 可确定性 全部 C/OS-II的函数调用与服务的执行时间具有可确定性。 任务栈 每个任务有自己单独的栈, C/OS-II允许每个任务有不同的栈空间,以便压低应用程序对RAM的需求。 系统服务 C/OS-II提供很多系统服务,例如邮箱、消息队列、信号量、块大小固定的内存的申请与释放、时间相关函数等。 中断管理 中断可以使正在执行的任务暂时挂起,如果优先级更高的任务被该中断唤醒,则高优先级的任务在中断嵌套全部退出后立即执行,中断嵌套层数可达255层。 稳定性与可靠性
µC/OS-II图籍 描述了µC/OS-II内部的工作原理 随书的CD中包含了源代码 工业界最清晰的源代码 除英文版外,有中文和韩文版 English Chinese Korean ISBN 1-57820-103-9 美国CMP BOOK ISBN 7-81077-290-2 北京航空航天大学出版社 ISBN 89-951540-5-5
µC/OS-II的各种商业应用 全世界有数百种产品在应用: Avionics(航空电子设备) Medical Cell phones Routers and switches High-end audio equipment Washing machines and dryers UPS (Uninterruptible Power Supplies) Industrial controllers GPS Navigation Systems Microwave Radios Instrumentation Point-of-sale terminals 更多
µC/OS-II提供的系统服务 信号量 带互斥机制的信号量 减少优先级倒置的问题 事件标志 消息信箱 消息队列 内存管理 时钟管理 任务管理
µC/GUI and µC/FS µC/GUI 嵌入式的用户界面 用ANSI C书写 支持任何8, 16, 32-bits CPU 彩色,灰度等级或黑白显示 代码尺寸小 µC/FS 嵌入式的文件系统 支持SMC, MMC, SD, CF, IDE, Flash, RAM其他介质
主要内容 嵌入式操作系统 C/OS-Ⅱ简介 C/OS-Ⅱ内核结构 C/OS-Ⅱ任务管理 C/OS-Ⅱ时间管理
C/OS-II的文件结构
C/OS-II分析 内核结构 任务管理 时间管理 任务之间通信与同步 C/OS的移植
C/OS-II开关中断的方法 当处理临界段代码时,须关中断,处理完毕后,再开中断 关中断时间是实时内核最重要的指标之一。它影响用户系统对实时事件的相应特性。 在实际应用中,关中断的时间很大程度上取决于微处理器的结构和编译器生成的代码质量 微处理器通常具有关中断/开中断操作。C编译器须具有某种机制,能够在c中直接实现关中断/开中断操作 C源代码中插入汇编语言的语句,易实现关中断/开中断操作 关中断/开中断操作作为语言的扩展部分,直接从C语言中可以 关中断/开中断
C/OS-II开关中断的方法(续1) C/OS-II定义了两个宏调用来开关中断: OS_ENTER_CRITICAL( ) (禁止中断的宏) OS_EXIT_CRITICAL( ) (启用中断的宏) 通常成对出现 上述宏定义取决于使用的微处理器。在文件OS_CPU.H有相应的宏定义 在C/OS-II中,每种微处理器都有自己的OS_CPU.H文件
C/OS-II开关中断的方法(续2) { .. OS_ENTER_CRITICAL( ); /* C/OS-II临界段代码*/ OS_EXIT_CRITICAL( ); } 如果在所有挂起类(PEND)调用之前,如:调用OSTimeDel()(挂起时间)功能函数之前关中断,会出现什么现象? 通常,调用C/OS-II功能函数时,中断总应当是开放的。
C/OS-II开关中断的方法(续3) OS_ENTER_CRITICAL( )及OS_EXIT_CRITICAL( )可以用3种不同的方法实现 具体方法取决于用户打算移植到的处理器的性能及所用的C编译器 用定义(#define)常数OS_CRITICAL_METHOD可以选择具体使用哪种方法 该常数在与CPU类型有关的移植文件OS_CPU.H中定义
C/OS-II中采用了3种开关中断的方法 OS_CRITICAL_METHOD==1 用最简单的方式来实现2个宏调用 用处理器指令关中断,完成OS_ENTER_CRITICAL() 用开中断指令完成OS_EXIT_CRITICAL() 问题: 如果调用C/OS-II功能函数时,中断是关掉的,则从C/OS-II函数返回时,中断就打开了 若调用C/OS-II功能函数之前已将中断关掉,那么用户往往希望从C/OS-II函数返回时,中断仍然是关着的。这时,这种方法就不妥当。 对特定的处理器,这种办法是唯一的选择
C/OS-II中采用了3种开关中断的方法(续1) OS_CRITICAL_METHOD==2 在堆栈中保存中断的开/关状态,然后再关中断 实现OS_ENTER_CRITICAL()时,先在堆栈中保存中断的开/关状态,然后再关中断 实现OS_EXIT_CRITICAL()时,从堆栈中弹出原来中断的开/关状态 利用这种机制,不论用户在调用C/OS-II功能函数之前中断是开或关,函数的进入和返回状态都得到了保护。即:调用前中断的开/关状态,在调用之后保持不变
C/OS-II中采用了3种开关中断的方法(续2) OS_CRITICAL_METHOD==3 把当前处理器的状态字(PSW)保存在C函数的局部变量中(如OS_CPU_SR) 关中断时保存,开中断时恢复
C/OS-II中采用了3种开关中断的方法(续3) Void Some_ uCOS_II_Service(arguments) { OS_CPU_SR cpu_sr; cpu_sr=get_processor_psw(); disable_interrupts(); /*临界段代码*/ set_processor_psw(cpu_sr); }
任务(task) 典型的任务:一个无限循环 void mytask(void *pdata) { for (;;) { do something; waiting; }
Task Structure void YourTask (void *pdata) (1) { for (;;) { (2) /* 用户代码 */ 调用uC/OS-II的某种系统服务: OSMboxPend(); OSQPend(); OSSemPend(); OSTaskDel(OS_PRIO_SELF); OSTaskSuspend(OS_PRIO_SELF); OSTimeDly(); OSTimeDlyHMSM(); }
任务完成后的自我删除 void YourTask (void *pdata) { /* 用户代码 */ 当任务完成后,任务可以自我删除 任务代码并非真的删除了,系统只是简单地不再理会这个任务 这个任务的代码也不会再运行了,即使任务调用了OSTaskDel(),这个任务也绝不会返回 void YourTask (void *pdata) { /* 用户代码 */ OSTaskDel(OS_PRIO_SELF); }
C/OS –II中的任务 C/OS –II 2.5版本支持64个任务,每个任务一个特定的优先级。数字越小,优先级越高 系统总是运行进入就绪态优先级最高的任务 任务优先级号就是任务编号(ID).优先级号也被一些内核功能函数调用。如OsTaskChangePrio( )及OsTaskDel( ). 系统占用了8个任务,保留优先级为0、1、2、3、OS_LOWEST_PRIO-3、 OS_LOWEST_PRIO-2、 OS_LOWEST_PRIO-1、 OS_LOWEST_PRIO-0 即:建议不使用上述最高4个和最低4个优先级,用户任务仍可达56个
任务状态 在任一给定的时刻,任务的状态一定是以下五种状态之一: 睡眠态(task dormant) 就绪态(task ready) 运行态(task running) 等待状态(task waiting) 中断服务态(ISR running)
睡眠态(task dormant) 指任务驻留在程序空间(ROM或RAM),还没有交给系统来管理的状态 任务交给系统通过调用以下函数之一来实现: OSTaskCreate() OSTaskCreateExt() 告知系统: 任务的起始地址 任务建立时,用户给任务赋予的优先级 任务要使用的栈空间大小等
就绪态(task ready) 任务一旦创建就进入就绪态,准备运行 任务的创建可以是在多任务开始之前,也可以动态地由一个运行着的任务创建 若刚创建任务的优先级高于创建它的任务的优先级,它将立即获得cpu的使用权 任务可通过OSTaskDel()返回睡眠态;或调用该函数让另一个任务进入睡眠态
运行态(task running) 就绪的任务只有当所有优先级高于它的任务都转为等待状态,或被删除后,才能进入运行态 任何时刻只有一个任务处于运行态 调用OSStart()可以启动多任务。该函数只能在启动时调用一次 OSStart()运行用户初始化代码中已经建立的、进入就绪态的优先级最高的任务
等待状态(task waiting) 正在运行的任务可以通过下面的调用进入等待状态。延迟时间到,立即强制执行任务切换,让下一个优先级最高、并进入就绪态的任务执行。 OSTimeDly() OSTimeDlyHMSM() 等待时间过去后,系统服务(内部)函数OSTimeTick()使延迟了的任务进入就绪态 用户无需在应用程序代码中调用这个函数
等待状态(续) 正在运行的任务可能需要通过调用函数等待某一事件发生。如果该事件并未发生,任务就进入等待状态 OSFlagPend();OSMutexPend() OSSemPend();OSMboxPend() 当事件发生或等待超时,被挂起的任务就进入就绪态
中断服务态(ISR running) 正在执行的任务是可以被中断的,除非该任务将中断关闭,或系统将中断关闭。被中断的任务便进入了中断服务态 响应中断后,正在运行的任务被挂起,中断服务子程序控制了CPU的使用权
中断服务态(ISR running) 中断服务子程序可能会报告一个或多个事件的发生,而使一个或多个任务进入就绪态 上述情况下,从中断服务子程序返回之前,C/OS –II 要判定: 被中断的任务是否还是就绪态任务中优先级最高的 如果中断服务子程序使另一个优先级更高的任务进入了就绪态,则新进入就绪态的这个优先级更高的任务将得以运行;否则,原来被中断了的任务将继续运行。
任务状态 当所有的任务都在等待时间发生或等待延迟时间结束时,C/OS –II 执行被称为空闲任务(Idle Task)的内部函数,即:OSTaskIdle()
任务状态 恢复任务 中断 任务的CPU使用权被剥夺
任务控制块(TCB) 任务控制块 OS_TCB是一个数据结构,保存该任务的相关参数,包括任务堆栈指针、状态、优先级、任务表位置、任务链表指针等 所有的任务控制块分为两条链表: 空闲链表 使用链表 当任务的CPU使用权被剥夺时,系统用它来保存该任务的状态 全部驻留在RAM中
µC/OS-II任务控制块. typedef struct os_tcb { OS_STK *OSTCBStkPtr(当前任务堆栈栈顶指针); #if OS_TASK_CREATE_EXT_EN>0 void *OSTCBExtPtr; (指向用户定义的任务控制块扩展) OS_STK *OSTCBStkBottom;(指向任务堆栈栈底指针) INT32U OSTCBStkSize;(存有栈中可容纳的指针元数目) INT16U OSTCBOpt; INT16U OSTCBId; (存储任务的识别码ID,备用) #endif struct os_tcb *OSTCBNext; (任务控制块OS-TCB双向链表的后链接) struct os_tcb *OSTCBPrev; (任务控制块OS-TCB双向链表的前链接) #if (OS_Q_EN && (OS_MAX_QS >= 2)) || OS_MBOX_EN || OS_SEM_EN OS_EVENT *OSTCBEventPtr;(指向事件控制块的指针)
#if (OS_Q_EN && (OS_MAX_QS >= 2)) || OS_MBOX_EN void *OSTCBMsg;(指向传递给任务的消息的指针) #endif INT16U OSTCBDly; INT8U OSTCBStat;(任务的状态字) INT8U OSTCBPrio; (任务的优先级) INT8U OSTCBX; INT8U OSTCBY; INT8U OSTCBBitX; INT8U OSTCBBitY; #if OS_TASK_DEL_EN BOOLEAN OSTCBDelReq; } OS_TCB;
空任务列表 所有的任务控制块都被放置在任务控制块列表数组OSTCBTbl[ ]中 系统初始化时,所有任务控制块被链接成空任务控制块的单向链表 任务建立后,空任务控制块指针OSTCBFreeList指向的任务控制块就赋给了该任务,然后OSTCBFreeList的值调整为指向链表中的下一个空任务控制块 任务一旦被删除,任务控制块就还给空任务链表
任务级的任务调度--OSSched C/OS是占先式实时多任务内核,优先级最高的任务一旦准备就绪,则拥有CPU的所有权即开始投入运行。
就绪表 根据就绪表确定最高优先级 两个关键: 优先级数分解为高三位和低三位分别确定 高优先级有着小的优先级号
根据优先级找到任务在就绪任务表中的位置 每个就绪的任务都放入就绪表中(ready list)中,就绪表用两个变量表示:OSRdyGrp、OSRdyTbl[] OSRdyGrp 1 2 7 6 5 4 3 X Y 任务优先级 10 8 9 15 14 13 12 11 18 16 17 23 22 21 20 19 26 24 25 31 30 29 28 27 34 32 33 39 38 37 36 35 42 40 41 47 46 45 44 43 50 48 49 55 54 53 52 51 58 56 57 63 62 61 60 59 [0] [1] [2] [3] [4] [5] [6] [7] OSRdyTbl[7] 优先级最低任务 (空闲任务) 优先级最高任务 任务优先级号
采用查表法确定高优先级任务 查表法具有确定的时间,增加了系统的可预测性, C/OS –II中所有的系统调用时间都是确定的 High3 =OSUnMapTbl[OSRdyGrp]; Low3 =OSUnMapTbl[OSRdyTbl[High3]]; Prio =(High3<<3)+Low3; OSRdyGrp 1 2 7 6 5 4 3 X Y 任务优先级 10 8 9 15 14 13 12 11 18 16 17 23 22 21 20 19 26 24 25 31 30 29 28 27 34 32 33 39 38 37 36 35 42 40 41 47 46 45 44 43 50 48 49 55 54 53 52 51 58 56 57 63 62 61 60 59 [0] [1] [2] [3] [4] [5] [6] [7] OSRdyTbl[7]
任务调度器(task scheduler)
Task scheduler void OSSched (void) { INT8U y; OS_ENTER_CRITICAL(); (禁止中断的宏) if ((OSLockNesting =0)&&(OSIntNesting= 0)) (如果调用来自中断服务程序,或者至少调用了一次给任务调度上锁函数,任务调度函数将退出,不做调度) y = OSUnMapTbl[OSRdyGrp]; (否则,调度找出进入就绪态且优先级最高的任务,就绪任务表相应的位置位) OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]) if (OSPrioHighRdy != OSPrioCur) { (优先级最高的任务是否是当前正在运行的任务,若是不调度) OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];(若不是,为了实现任务切换,指向优先级最高的任务控制块) OSCtxSwCtr++; (统计计数,跟踪任务切换次数) OS_TASK_SW(); (任务切换) } OS_EXIT_CRITICAL(); (开放中断的宏)
给调度器上锁 void OSSchedLock (void) { if (OSRunning == TRUE) { OS_ENTER_CRITICAL(); if(OSLockNesting<255){ OSLockNesting++; } OS_EXIT_CRITICAL();
给调度器开锁 void OSSchedUnlock (void) { if (OSRunning == TRUE) { OS_ENTER_CRITICAL(); if (OSLockNesting > 0) { OSLockNesting--; if ((OSLockNesting==0)&&(OSIntNesting==0)) { OS_EXIT_CRITICAL(); OSSched(); } else { }
任务切换 将被挂起的任务寄存器入栈 将较高优先级任务的寄存器出栈
任务级的任务切换OS_TASK_SW()
调用OS_TASK_SW()前的数据结构 高优先级任务(切换后运行的任务) OS_TCB 低优先级任务 OS_TCB (即将被挂起任务) OSTCBCur (1) OSTCBHighRdy (3) 指向即将 运行的任 务的栈顶 存贮器低地址 存贮器低地址 CPU (2) PSW PC R1 R2 R3 R4 SP (4) 堆栈方向 R1 R2 R3 R4 PC (5) PSW 即将被挂起任务的栈顶 存贮器高地址 存贮器高地址
保存当前CPU寄存器的值 R4 R4 R3 R3 R2 R2 R1 R1 PC PC PSW PSW SP R1 R2 R3 R4 PC 低优先级任务 OS_TCB 高优先级任务 OS_TCB OSTCBCur OSTCBHighRdy (3) (3) 存贮器低地址 存贮器低地址 CPU PSW PC R1 R2 R3 R4 PSW PC R1 R2 R3 R4 SP R1 R2 R3 堆栈方向 (2) R4 PC PSW (1) 存贮器高地址 存贮器高地址
重新装入要运行的任务 R4 R4 R3 R3 R2 R2 R1 R1 PC PC PSW PSW 复制 SP R1 R2 R3 R4 PC 低优先级任务 OS_TCB 高优先级任务 OS_TCB OSTCBCur OSTCBHighRdy OSTCBCur (1) 复制 (2) (3) 存贮器低地址 存贮器低地址 CPU PSW PC R1 R2 R3 R4 PSW PC R1 R2 R3 R4 SP (4) R1 R2 R3 (3) 堆栈方向 R4 PC PSW (4) (1) (4) 存贮器高地址 存贮器高地址
任务切换OS_TASK_SW()的代码 Void OSCtxSw(void) { 将R1,R2,R3及R4推入当前堆栈; OSTCBCurOSTCBStkPtr = SP; OSTCBCur = OSTCBHighRdy; SP = OSTCBHighRdy OSTCBSTKPtr; 将R4,R3,R2及R1从新堆栈中弹出; 执行中断返回指令; }
C/OS-II中的中断 中断:由于某种事件的发生,而导致程序流程的改变。产生中断的事件称为中断源。 CPU响应中断的条件: 系统允许中断,且对此中断信号未予屏蔽 中断类型: 硬件中断 外部中断 陷井中断 现场控制量的中断
C/OS-II中的中断服务子程序 用户中断服务子程序: 保存全部CPU寄存器; 调用OSIntEnter()或OSIntNesting直接加1; if(OSIntNesting==1){ OSTCBCur->OSTCBStkPtr=SP; } 清中断源; 重新开中断; 执行用户代码做中断服务; 调用OSIntExit(); 恢复所有CPU寄存器; 执行中断返回指令;
中断服务
Search for highest priority – similar to scheduler Half context switch is done by interrupt response – i.e. registers saved
中断与时钟节拍 时钟节拍(时钟滴答)Tick,是一种定时器中断,可通过编程方式实现 时钟节拍是一种特殊的中断,操作系统的心脏。首先32位的整数OSTime加一。对任务列表进行扫描,判断是否有延时任务应该处于准备就绪状态,最后进行上下文切换。
时钟节拍中断服务子程序 Void OSTickISR(void) { 保存处理器寄存器的值; 调用OSIntEnter(),或是将OSIntNesting加1 if(OSIntNesting==1){ OSTCBCur->OSTCBStkPtr=SP; } 调用OSTimeTick(); 发出中断设备的中断; 重新允许中断(可选用) 调用OSIntExit(); 恢复处理器寄存器的值; 执行中断返回指令;
时钟节拍函数OSTimeTick() void OSTimeTick (void) { OS_TCB *ptcb; OSTimeTickHook(); (1) ptcb = OSTCBList; (2) while (ptcb->OSTCBPrio != OS_IDLE_PRIO) { (3) OS_ENTER_CRITICAL(); if (ptcb->OSTCBDly != 0) { if (--ptcb->OSTCBDly == 0) { if (!(ptcb->OSTCBStat & OS_STAT_SUSPEND)) { (4) OSRdyGrp |= ptcb->OSTCBBitY; (5) OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX; } else { ptcb->OSTCBDly = 1; } ptcb = ptcb->OSTCBNext; OS_EXIT_CRITICAL(); OS_ENTER_CRITICAL(); (6) OSTime++; (7)
空闲任务OSTaskIdle() 系统总要建立一个空闲任务,该任务在没有其它任务进入就绪状态时投入运行 空闲任务永远设为最低优先级 空闲任务不可能被应用软件删除 void OSTaskIdle (void *pdata) { pdata = pdata; for (;;) { OS_ENTER_CRITICAL(); OSIdleCtr++; OS_EXIT_CRITICAL(); }
C/OS-II初始化 首先调用系统初始化函数OSIint()。OSIint()初始化μC/OS-Ⅱ所有的变量和数据结构(见OS_CORE.C)。 OSInit()建立空闲任务idle task,这个任务总是处于就绪态的。空闲任务OSTaskIdle()的优先级总是设成最低,即OS_LOWEST_PRIO。 如果统计任务允许OS_TASK_STAT_EN和任务建立扩展允许都设为1,则OSInit()还得建立统计任务OSTaskStat()并且让其进入就绪态。OSTaskStat的优先级总是设为OS_LOWEST_PRIO-1 空闲和统计任务的任务控制块(OS_TCBs)是用双向链表链接在一起的。OSTCBList指向这个链表的起始处。当建立一个任务时,这个任务总是被放在这个链表的起始处。
调用OSInit()之后的数据结构
空闲缓冲区
μC/OS-Ⅱ的启动 void main (void) { OSInit(); /* 初始化uC/OS-II */ . 通过调用OSTaskCreate()或OSTaskCreateExt()创建至少一个任务; OSStart(); /* 开始多任务调度!OSStart()永远不会返回 */ }
启动多任务 void OSStart (void) { INT8U y; INT8U x; if (OSRunning == FALSE) { y = OSUnMapTbl[OSRdyGrp]; x = OSUnMapTbl[OSRdyTbl[y]]; OSPrioHighRdy = (INT8U)((y << 3) + x); OSPrioCur = OSPrioHighRdy; OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]; OSTCBCur = OSTCBHighRdy; OSStartHighRdy(); }
关于OSStartHighRdy void OSStartHighRdy (void){ 调用用户定义的OSTaskSwHook(); OSRunning = TRUE; 得到将要恢复运行任务的堆栈指针: Stack pointer=OSTCBHighRdyOSTCBStkPtr 从新任务堆栈中恢复处理器的所有寄存器; 执行中断返回指令; }
调用OSStart()之后的数据结构
主要内容 嵌入式操作系统 C/OS-Ⅱ简介 C/OS-Ⅱ内核结构 C/OS-Ⅱ任务管理 C/OS-Ⅱ时间管理
建立任务 Use one of two services OS TaskCreate() OSTaskCreateExt ()
建立任务,OSTaskCreate() INT8U OSTaskCreate (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT8U prio) { void *psp; INT8U err; if (prio > OS_LOWEST_PRIO) { (1) return (OS_PRIO_INVALID); } OS_ENTER_CRITICAL(); if (OSTCBPrioTbl[prio] == (OS_TCB *)0) { (2) OSTCBPrioTbl[prio] = (OS_TCB *)1; (3) OS_EXIT_CRITICAL(); (4) psp = (void *)OSTaskStkInit(task, pdata, ptos, 0); err = OSTCBInit(prio, psp, (void *)0, 0, 0, (void *)0, 0); if (err == OS_NO_ERR) {
OSTaskCtr++; OSTaskCreateHook(OSTCBPrioTbl[prio]); OS_EXIT_CRITICAL(); if (OSRunning) { OSSched(); } } else { OS_ENTER_CRITICAL(); OSTCBPrioTbl[prio] = (OS_TCB *)0; return (err); return (OS_PRIO_EXIST);
其它相关函数 堆栈检验,OSTaskStkChk() 删除任务,OSTaskDel() 请求删除任务,OSTaskDelReq() 改变任务的优先级,OSTaskChangePrio() 挂起任务,OSTaskSuspend() 恢复任务,OSTaskResume()
主要内容 嵌入式操作系统 C/OS-Ⅱ简介 C/OS-Ⅱ内核结构 C/OS-Ⅱ任务管理 C/OS-Ⅱ时间管理
时间管理 Five services: OSTimeDLY() OSTimeDLYHMSM() OSTimeDlyResmue() OStimeGet() OSTimeSet()
OSTimeDly( ) void OSTimeDly (INT16U ticks) { if (ticks > 0) { OS_ENTER_CRITICAL(); if ((OSRdyTbl[OSTCBCur->OSTCBY] &= ~OSTCBCur->OSTCBBitX) == 0) { OSRdyGrp &= ~OSTCBCur->OSTCBBitY; } OSTCBCur->OSTCBDly = ticks; OS_EXIT_CRITICAL(); OSSched();
OSTimeDlyHMSM( )
主要内容 嵌入式操作系统 C/OS-Ⅱ简介 C/OS-Ⅱ内核结构 C/OS-Ⅱ任务管理 C/OS-Ⅱ时间管理
任务间通信手段 C/OS中,采用多种方法保护任务之间的共享数据和提供任务之间的通信。 提供OS_ENTER_CRITICAL和OS_EXIT_CRITICAL来对临界资源进行保护 OSSchedLock( )禁止调度保护任务级的共享资源。 提供了经典操作系统任务间通信方法:信号量、邮箱、消息队列,事件标志。
事件控制块ECB 所有的通信信号都被看成是事件(event), 一个称为事件控制块(ECB, Event Control Block)的数据结构来表征每一个具体事件,ECB的结构如下 程序4.5 ECB的结构如下 --------------------------------------------------------------------- typedef struct { void *OSEventPtr; /*指向消息或消息队列的指针*/ INT8U OSEventTbl[OS_EVENT_TBL_SIZE]; /*等待任务列表*/ INT16U OSEventCnt; /*计数器(当事件是信号量时)*/ INT8U OSEventType; /*事件类型:信号量、邮箱等*/ INT8U OSEventGrp; /*等待任务组*/ } OS_EVENT; 与TCB类似的结构,使用两个链表,空闲链表与使用链表
事件的等待任务列表
空闲事件控制块链表
事件控制块TCB的操作 对事件控制块进行的操作包括: 初始化一个事件控制块OS_EventWaitListInit(); 使一个任务进入就绪态OS_EventTaskRdy(); 使一个任务进入等待某事件的状态OS_EventTaskWait(); 因为等待超时而使一个任务进入就绪态OS_EventTO()。
信号量semaphore 信号量在多任务系统中用于:控制共享资源的使用权、标志事件的发生、使两个任务的行为同步。 uC/OS中信号量由两部分组成:信号量的计数值和等待该信号任务的等待任务表。信号量的计数值可以为二进制, 也可以是其他整数。 系统通过OSSemPend( )和OSSemPost( )来支持信号量的两种原子操作P()和V()。P()操作减少信号量的值,如果新的信号量的值不大于0,则操作阻塞;V()操作增加信号量的值。
任务、中断服务子程序和信号量之间的关系 OSSemDel()
信号量操作 µC/OS-II提供了6个对信号量进行操作的函数。它们是: OSSemCreate():建立一个信号量 OSSemDel(): 删除一个信号量 OSSemPend():等待一个信号量 OSSemPost():发出一个信号量 OSSemAccept():无等待地请求一个信号量 当一个任务请求一个信号量时,如果该信号量暂时无效,也可以让该任务简单地返回,而不是进入睡眠等待状态 OSSemQuery()函数:查询一个信号量的当前状态
邮 箱 邮箱是µC/OS-II中另一种通讯机制,它可以使一个任务或者中断服务子程序向另一个任务发送一个指针型的变量。 邮 箱 邮箱是µC/OS-II中另一种通讯机制,它可以使一个任务或者中断服务子程序向另一个任务发送一个指针型的变量。 该指针指向一个包含了特定“消息”的数据结构。为了在µC/OS-II中使用邮箱,必须将OS_CFG.H中的OS_MBOX_EN常数置为1。 使用邮箱之前,必须先建立该邮箱。该操作可以通过调用OSMboxCreate()函数来完成,并且要指定指针的初始值。
邮 箱 µC/OS-II提供了7种对邮箱的操作: OSMboxCreate():建立一个邮箱 OSMboxDel():删除一个邮箱 邮 箱 µC/OS-II提供了7种对邮箱的操作: OSMboxCreate():建立一个邮箱 OSMboxDel():删除一个邮箱 OSMboxPend():等待邮箱中的消息 OSMboxPost():向邮箱发送一则消息 OSMboxPostOpt():向邮箱发送一则消息 OSMboxAccept():无等待地从邮箱中得到一则消息 OSMboxQuery():查询一个邮箱的状态
任务、中断服务子程序和邮箱之间的关系
消息队列 消息队列是µC/OS-II中另一种通讯机制,它可以使一个任务或者中断服务子程序向另一个任务发送以指针方式定义的变量 因具体的应用有所不同,每个指针指向的数据结构变量也有所不同 为了使用µC/OS-II的消息队列功能,需要在OS_CFG.H 文件中,将OS_Q_EN常数设置为1,并且通过常数OS_MAX_QS来决定µC/OS-II支持的最多消息队列数 在使用一个消息队列之前,必须先建立该消息队列。这可以通过调用OSQCreate()函数,并定义消息队列中的单元数(消息数)来完成
µC/OS-II提供了8个对消息队列进行操作的函数: OSQCreate():建立一个消息队列 OSQDel():删除一个消息队列 OSQPend():等待消息队列中的消息 OSQPost():向消息队列发送一则消息(FIFO) OSQPostFront():向消息队列发送一则消息(LIFO) 只是在插入新的消息到消息队列中时,使用.OSQOut,而不是.OSQIn作为指向下一个插入消息的单元指针 OSQAccept():无等待地从消息队列中获得消息 OSQFlush():清空消息队列 OSQQuery():获取消息队列的状态
任务、中断服务子程序和消息队列之间的关系
消息与任务
操作系统的启动和运行过程 嵌入式控制器硬件初始化 操作系统初始化 LCD初始化 装载字库 调用系统配置文件 创建任务 初始化用户界面、时钟 启动多任务调度 主任务 用户程序 消息处理 任务1 任务n ……
小结 C/OS-Ⅱ是一个实时内核,非常适合用于有实时性要求的控制系统中 C/OS-Ⅱ要在其它领域中应用,有时还需要扩展GUI、FS、TCP/IP等嵌入式中间件和协议栈