转自:http://bbs.ednchina.com/BLOG_ARTICLE_1969777.HTM
采用各种算法和策略,始终保持系统行为的可预测性。即在任何情况下,在系统运行的任何时刻,OS的资源配置策略都能为争夺资源(包括CPU、内存、网络带宽等)的多个实时任务合理地分配资源,使每个实时任务的实时性要求都能得到满足。
与GPOS不同,RTOS注重的不是系统的平均表现,而是要满足每个实时任务在最坏情况下的实时性要求。也就是说,RTOS注重的是个体表现,更准确地说是个体最坏情况表现。
RTOS与GPOS的差别主要表现在:
a) 任务调度策略不同;
b) 内存管理方式不同;
c) 中断处理方式不同;
d) 系统管理方式不同;
(1) 可重入函数:指函数代码在运行过程中可以被中断,中断返回后仍能够恢复到原来的状态,并能准确无误执行的函数。
可重入函数可以被一个以上的任务调用,而不必担心数据被破坏。可重入函数或者只使用局部变量,即变量保存在CPU寄存器或堆栈中;或者使用全局变量,则要对全局变量予以保护。
(2) 不可重入函数:函数在运行过程中不可以被中断。
实现任务间通信最简便的办法是使用共享数据,但要保证任务在处理共享数据时的排它性。使共享资源满足互斥条件,最一般的方法有:
(1) 关中断
使用某种实时内核,一般情况下关中断的时间最长不超过内核本身的关中断时间,这样就不会影响系统中断延迟。
(2) 使用测试并置位指令
Test&Set操作可能是微处理器一条不会被中断的指令,否则应该在程序中关中断做TAS操作再开中断。
(3) 禁止做任务切换
此时任务切换虽然是禁止的,但仍允许中断。如果这时中断来了,ISR会在这一临界区内立即执行。
(4) 利用信号量;
临界状态指当前程序处于不可中断状态。一般情况下,在调用不可重入函数前或在修改全局变量数据时,都需要先进入临界状态。进入临界状态的主要操作是关闭所有可以屏蔽的中断;而退出临界状态的主要操作是恢复到上次进入临界状态时前中断管理的状态。
在uC/OS-II中,宏OS_ENTER_CRITICAL()描述进入临界状态所完成的操作,宏OS_EXIT_CRITICAL()描述退出临界状
态的操作。uC/OS-II提供了3种进入和退出临界状态的办法,可以根据CPU类型由宏OS_CRITICAL_MOTHOD指定具体的临界状态处理办法。
uC/OS-II大致可以分成系统核心(包含任务调度)、任务管理、时间管理、多任务同步与通信、内存管理、CPU移植等部分。
(1) 核心部分(OSCore.c) :uC/OS-II处理核心,包括初始化、启动、中断管理、时钟中断、任务调度及事件处理等用于系统基本维持的函数。
(2) 任务管理(OSTask.c) :包含与任务操作密切相关的函数,包括任务建立、删除、挂起及恢复等,uC/OS II以任务为基本单位进行调度。
(3) 时钟部分(OSTime.c) :uC/OS-II中最小时钟单位是timetick(时钟节拍),其中包含时间延迟、时钟设置及时钟恢复等与时钟相关的函数。
(4) 多任务同步与通信(OSMbox.c, OSQ.c, OSSem.c, OSMutex.c, OSFlag.c):包含事件管理函数,涉及Mbox、msgQ、Sem、Mutex、Flag等。
(5) 内存管理部分(OSMem.c):主要用于构建私有的内存分区管理机制,其中包含创建memPart、申请/释放memPart、获取分区信息等函数。
(6) CPU接口部分:uC/OS-II针对特定CPU的移植部分,由于牵涉到SP等系统指针,通常用汇编语言编写,包括任务切换、中断处理等内容。
在uC/OS-II中,一个任务就是一个线程,该任务可以认为CPU完全属于它自己。任务有自己的堆栈和CPU寄存器,并且被赋予一定的优先级。任务可能处于睡眠、就绪、运行、等待或中断服务状态之一。
4. uC/OS-II与VxWorks的比较
主要包含在C源文件OS_CORE.C中。
uC/OS-II采用基于优先级的调度算法,总是选择当前处于就绪状态的优先级最高的任务进行调度。uC/OS-II是可抢占性的强实时性OS,在完成中断后允许进行新的任务调度。
uC/OS-II有两种调度方式:任务级任务调度、中断级任务调度。
INT8U const OSUnMapTbl[256] = {…};
OS_EXT INT8U OSRdyGrp;
OS_EXT INT8U OSRdyTbl[OS_RDY_TBL_SIZE];
指在非中断返回时进行任务调度,一般发生在当前任务因时间延迟或等待某事件而阻塞或被挂起,或有更高优先级的任务处于就绪状态。
任务的基本信息:
任务级任务切换:从一个任务直接切换至另一个任务,不涉及CPU状态的切换,OS_TASK_SW()既保存当前任务上下文,又恢复新任务上下文。
过程:OS_Sched() -> OS_SchedNew() -> OS_TASK_SW()
中断级任务切换:在中断处理完成后,通过OSIntExit()判断是否有更高优先级就绪任务。如果有,调用OSIntCtxSW()恢复新任务上下文。注意:在中断处理中,已经保存了被中断任务的上下文,所以这里仅仅恢复。
过程:OSIntExt() –> OSIntEnter() -> ISR –> OSIntExit() -> OSIntCtxSW()
uC/OS-II提供调度器锁定功能,在锁定期间不能进行任务调度。uC/OS-II使用全局变量OSLockNesting标识是否锁定了任务调度器。
在中断处理中,不允许进行任务管理、事件管理及任务调度等操作。uC/OS-II通过全局变量OSIntNesting标识当前是否处于中断状态。在所有任务及事件管理的程序中,都有对OSIntNesting进行判断的语句。
关中断使得uC/OS-II能够同时避免有其他任务或中断服务进入临界代码段。调用uC/OS-II功能函数时,中断总应当是开着的。
uC/OS-II首先调用OSInit()进行初始化,然后创建任务(此时还未启动系统,仅仅为其分配资源),然后调用OSStart()启动系统,将CPU控制权交给uC/OS-II,OS根据任务优先级选择由哪个任务开始执行,或创建新的任务。
OSInit()主要完成初始化操作,包括初始化全局变量(在OS_InitMisc()中)、任务就绪表、TCB、ECB、FCB、内存单元、消息队列,并创建空闲任务。如果有必要,创建统计任务。
uC/OS-II初始化了5个空的数据结构缓冲区,每个缓冲区都是单向链表,允许uC/OS-II从缓冲区中迅速取得或释放一个缓冲区中的元素。
uC/OS-II调用OSInit()后的变量与数据结构如下图所示:
OSStart()在一切准备就绪且需要首先创建的任务都被创建后,启动uC/OS-II。它从就绪表中查找最高优先级就绪任务,并恢复其上下文开始执行。
过程:OSStart() -> OS_SchedNew() -> OSStartHighRdy()
问题:任务第一次被调用时,哪来的上下文供其恢复呢?创建任务时,调用了OSTaskStkInit()初始化任务堆栈,可此函数中没有涉及任务的上下文呀?
uC/OS-II调用OSStart()后的变量和数据结构如下图所示:
OSTaskStat用于计算CPU利用率。设置OS_CFG.H中的OS_TASK_STAT_EN为1,创建统计任务,在系统启动后一直处于就绪状
态。刚开始时,空闲任务运行1S,为计算CPU利用率提供一个基准值,并保存在统计任务的堆栈中,这个值不会改变除非重新启动CPU。此后空闲任每次被其
它任务抢去CPU时,它里面的计数器就会直接记录下CPU空闲的时间。
任何实时系统的时钟硬件设备每隔一段时间(一个系统tick)产生一个硬件中断,OS接收到该中断后,更新时间计数器,更新所有对时钟依赖的程序代码,从而维持系统有序稳定的运行。
主要包含在C源文件OS_TIME.C中。
(2) ECB管理机制
(3) ECB管理函数
(1)代码段.text:存放CPU执行的机器指令,通常.text是可共享且只读的。
(2)数据段.data:.rodata(常量数据)、.rwdata(已初始化全局变量、静态变量)。
(3)未初始化数据段.bss:未初始化的全局变量、静态变量。
(4)栈.stack:存放函数参数、局部变量及任务切换时的上下文。
(5)堆.heap:用于动态内存分配。
在uC/OS-II中,任务是事件运行和管理的基本单元。一个uC/OS-II任务至少包含程序代码、栈和TCB,还可选择性使用相当于堆的动态内存空间。程序运行时,uC/OS-II中的任务相当于可执行代码(可单独运行的单元)。
uC/OS-II任务的各部分如何管理???
主要用来存储任务的当前属性。(问:任务第一次被调度时,该如何运行?)
(3)TCB全局变量
OS_TASK.C:11个函数
Mbox用于多任务间单一消息的传递,uC/OS-II使用ECB管理Mbox的基本信息,OSEventPtr指向创建Mbox时指定的内存空间。事件的创建由具体的事件管理程序实现。主要包含在C源文件OS_MBOX.C中。
msgQ是uC/OS-II任务间通信的机制,可实现多条消息传递,即可以同时存储多条消息。uC/OS-II使用循环队列管理机制。主要包含在C源文件OS_Q.C中。
msgQ管理:使用指针数组存储所有消息的位置;使用QCB标识指针数组中消息的基本信息;使用ECB管理整个msgQ。QCB在编译时分配空间,即当前系统中可用的msgQ个数是预先设置的,系统运行时不能修改。
struct os_q *OSQPtr; //用于构建空闲QCB链表
void **OSQStart; //指向msgQ指针数组的起始位置
void **OSQEnd; //指向msgQ指针数组的结束位置
void **OSQIn; //指向msgQ指针数组下一个可以插入消息的位置
void **OSQOut; //指向msgQ指针数组下一个可以读出消息的位置
INT16U OSQSize; //msgQ指针数组的大小
INT16U OSQEntries; //msgQ指针数组当前可以读取的消息个数
}OS_Q;
uC/OS-II中,什么是事件?事件是uC/OS-II管理任务间同步与通信的机制。
事件是处理事件的对象感兴趣的,能够感知或捕获到一种事件状态的改变。
Sem主要用来实现任务间同步及标识某类资源的可用个数,即某个特定资源可供多少任务同时使用。主要包含在C源文件OS_SEM.C中。
Mutex用来实现对资源的排他性访问,可能引起优先级反转。任何任务在占有某个互斥锁事件时,都不能阻塞等待其它任何事件,否则会造成死锁。主要包含在C源文件OS_MUTEX.C中。
优先级反转是指,低优先级任务占有高优先级任务运行所需的资源,而使高优先级不得不等低优先级任务把资源释放才能执行。
uC/OS-II使用ECB管理Mutex,其成员变量OSEventCnt:高8位存储Mutex被使用时提供给任务的prio;低8位在没有任务占有Mutex时为0xFF,否则为占有任务的prio。
优先级反转及优先级反转避免分别如下图所示:
a) 提升Mutex拥有者任务的优先级的相关操作:
b) 恢复Mutex拥有任务的优先级的相关操作:
(3)Mutex管理函数
uC/OS-II提供事件组标志实现多事件管理。Flag只是使用0/1来表示某个事件是否发生过,而不能直接被用来传递数据和消息。可以选择性地设置一个Flag最多可以管理的任务同步状态。主要包含在C源文件OS_FLAG.C中。
typedef struct os_flag_grp{
INT8U OSFlagType; //事件类型
void *OSFlagWaitList; //指向等待的任务链表
OS_FLAGS OSFlagFlags; //信号列表
INT8U OSFlagName[OS_FLAG_NAME_SIZE];
}OS_FLAG_GRP;
typedef struct os_flag_node{
void *OSFlagNodeNext;
void *OSFlagNodePrev;
void *OSFlagNodeTCB;
void *OSFlagNodeFlagGrp; //指向此任务所等待的事件组标志
OS_FLAGS OSFlagNodeFlags; //等待的事件
INT8U OSFlagNodeWaitType; //等待方式
}OS_FLAG_NODE;
OS_FLAG_NODE *pnode,
OS_FLAGS flags,
INT8U wait_type, //挂起任务,
INT16U timeout); //直到等待的事件或超时
类似于:OS_EventTaskWait();
类似于:OS_EventTO();
OS_FLAGS flags,
INT8U wait_type,
INT8U *err);
OS_FLAGS flags,
INT8U opt,
INT8U *err);
OS_FLAGS flags_rdy);
a) OSRdyTbl[ptcb->OSTCBY] & ptcb->OSTCBBitX != 0
如:函数OSMutexPend()
b) (ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY
如:函数OSTimeTick()
c) ptcb->OSTCBStat == OS_STAT_RDY
如:函数OS_EventTaskRdy()
uC/OS-II根据需要将内存空间分成多个内存分区,每个内存分区由具有相同大小的内存块(Block)组成。主要包含在C源文件OS_MEM.C中。
typedef struct os_men{
void *OSMemAddr; //首地址
void *OSMemFreeList; //分区中空闲空间的起始地址
INT32U OSMemBlkSize; //分区中块成员空间大小
INT32U OSMemNBlks; //块结构数量
INT32U OSMemNFree; //剩余空闲块个数
INT8U OSMemName[];
}OS_MEM;
INT32U blksize, INT8U *err);
(1)能不能一次申请1B的堆空间?
(2)uC/OS-II在申请栈空间和堆空间时有什么不同?
(1) 包含uC/OS-II的总头文件includes.h;
(2) 定义任务栈的大小和任务栈;
(3) 分配任务优先级;
(4) 实现任务功能;
(5) 调用OSInit()函数初始化uC/OS-II的数据结构;
(6) 创建用户任务,启动uC/OS-II;
用户任务函数必须是无限循环,程序执行流由OS内核改变。在执行完用户代码后最好调用系统服务,主动把CPU使用权让给有需要的任务。
模式一:
void YourTask(void *pdata)
{
for(;;)
{
/*用户代码*/
调用uC/OS II的服务;
OSMboxPend();
OSQPend();
OSSemPend();
OSTimeDlyHMSM();
/*用户代码*/
}
}
模式二(自删除任务)
void YourTask(void *pdata)
{
/*用户代码*/
OSTaskDel(OS_PRIO_SELF);
} //这种任务运行完后就自我删除了。
方法:中断堆栈和任务堆栈分开设计
原因:uC/OS-II占用RAM主要用于任务TCB、任务堆栈等方面,任务堆栈大是由于硬件设计中没有把中断堆栈和任务堆栈分开,造成了考虑任务堆栈大
小时,不仅要计算任务中局部变量和函数嵌套层数,还要考虑中断的最大嵌套层数,导致大量RAM被浪费。可以把中断堆栈和任务堆栈分开设计,这样计算任务堆
栈时,就不需要把中断处理(包括中断嵌套)中的内存占用计算到任务堆栈中,只需计算每个任务本身需要的内存大小即可。
(1) 编写中断程序时,有条件的情况下尽量使用汇编语言,这样可以避免一些编译器自身的操作,减少指针调整次数。
(2) 用C编写ISR时,有时要调用汇编函数,其中一些压栈的PC可能破坏堆栈结构,这就要调整堆栈,保证正确的格式。
(3)由于uC/OS-II中断处理原始设计中可能不调整SP,需要在调用OSIntExit()返回后,判断程序是否处于中断嵌套状态。