1. 有两种类型的实时系统:软实时系统和硬实时系统。在软实时系统中系统的宗旨是使各个任务运行得越快越好,并不要求限定某一任务必须在多长时间内完成。在硬实时系统中,各任务不仅要执行无误而且要做到准时。 2. 前后台系统: 应用程序是一个无限的循环,循环中调用相应的函数完成相应的操作,这部分可以看成后台行为(background)。中断服务程序处理异步事件,这部分可以看成前台行为(foreground)。后台也可以叫做任务级。前台也叫中断级。
3. 可重入型函数: 可重入型函数可以被一个以上的任务调用,而不必担心数据的破坏。可重入型函数任何时候都可以被中断,一段时间以后又可以运行,而相应数据不会丢失。可重入型函数或者只使用局部变量,即变量保存在CPU寄存器中或堆栈中。如果使用全局变量,则要对全局变量予以保护。以下技术之一即可使不可重入函数具有可重入性:
把变量定义为局部变量
调用函数之前关中断,调动后再开中断 用信号量禁止该函数在使用过程中被再次调用
4. 优先级反转: 应用程序执行过程中,任务的优先级是可变的,则称之为动态优先级。实时内核应当避免出现优先级反转问题。为防止发生优先级反转,内核能自动变换任务的优先级,这叫做优先级继承(Priority inheritance)
5. 互斥条件:实现任务间通讯最简便到办法是使用共享数据结构。特别是当所有到任务都在一个单一地址空间下,能使用全程变量、指针、缓冲区、链表、循环缓冲区等,使用共享数据结构通讯就更为容易。虽然共享数据区法简化了任务间的信息交换,但是必须保证每个任务在处理共享数据时的
排它性,以避免竞争和数据的破坏。与共享资源打交道时,使之满足互斥条件最一般的方法有:
关中断(μ
C/OS-Ⅱ在处理内部变量和数据结构时就是使用的这种手
段)
使用测试并置位指令 禁止做任务切换 利用信号量
6. 信号量:信号量用于:
控制共享资源的使用权(满足互斥条件) 标志某事件的发生 使两个任务的行为同步
对信号量只能实施三种操作:
初始化(INITIALIZE),也可称作建立(CREATE); 等信号(WAIT)也可称作挂起(PEND); 给信号(SIGNAL)或发信号(POST)。
这是我自己写的验证信号量的部分代码:
void task_one(void* pdata); void task_two(void* pdata);
OS_EVENT *tasktwo; #include \"includes.h\"
GUI_Init();
VCInit(); //初始化一些变量 OSInit();
timeSetEvent(OS_TICKS_PER_SEC, 0, OSTickISR, 0, TIME_PERIODIC); //产生节拍
tasktwo = OSSemCreate(0);
OSTaskCreate(task_two, 0, &TaskStk[7][TASK_STK_SIZE-1], 1); OSTaskCreate(task_one, 0, &TaskStk[8][TASK_STK_SIZE-1], 2);
OSStart();
7. 使用实时内核的优缺点: 实时内核也称为实时操作系统或RTOS。它的使用使得实时应用程序的设计和扩展变得容易,不需要大的改动就可以增加新的功能。通过将应用程序分割成若干独立的任务,RTOS使得应用程序的设计过程大为减化。使用可剥夺性内核时,所有时间要求苛刻的事件都得到了尽可能快捷、有效的处理。通过有效的服务,如信号量、邮箱、队列、延时、超时等,RTOS使得资源得到更好的利用。如果应用项目对额外的需求可以承受,应该考虑使用实时内核。这些额外的需求是:内核的价格,额外的ROM/RAM开销,2到4百分点的CPU额外负荷。
内核结构
1. 任务: 一个任务通常是一个无限的循环。看起来像其它C的函数一样,有函数返回类型,有形式参数变量,但是任务是绝不会返回的。故返回参数必须定义成void。当任务完成以后任务代码并非真的删除了,μC/OS-Ⅱ只是简单地不再理会这个任务了。 列出一任务:
void YourTask (void *pdata) {
for (;;) { (2) /* 用户代码 */
调用uC/OS-II的某种系统服务: OSMboxPend(); OSQPend();
(1)
OSSemPend();
OSTaskDel(OS_PRIO_SELF); OSTaskSuspend(OS_PRIO_SELF); OSTimeDly(); OSTimeDlyHMSM(); /* 用户代码 */ } }
形式参数变量(1)是由用户代码在第一次执行的时候带入的。该变量的类型是一个指向void的指针。这是为了允许用户应用程序传递任何类型的数据给任务。μC/OS-Ⅱ可以管理多达64个任务,但目前版本的μC/OS-Ⅱ有两个任务已经被系统占用了。作者保留了优先级为0、1、2、3、OS_LOWEST_PRIO-3、
OS_LOWEST_PRI0-2,OS_LOWEST_PRI0-1以及OS_LOWEST_PRI0这8个任务以被将来使用。为了使μC/OS-Ⅱ能管理用户任务,用户必须在建立一个任务的时候,将任务的起始地址与其它参数一起传给下面两个函数中的一个:OSTastCreat或OSTaskCreatExt()。
2. 任务控制块:一旦任务建立了,任务控制块OS_TCBs将被赋值。任务控制块是一个数据结构,当任务的CPU使用权被剥夺时,μC/OS-Ⅱ用它来保存该任务的状态。当任务重新得到CPU使用权时,任务控制块能确保任务从当时被中断的那一点丝毫不差地继续执行。OS_TCBs全部驻留在RAM中。
µC/OS-II任务控制块清单:
typedef struct os_tcb {
OS_STK *OSTCBStkPtr; //指向当前任务栈顶的指针。每个任务的栈的
容量可以是//任意的。
#if OS_TASK_CREATE_EXT_EN
void *OSTCBExtPtr; //指向用户定义的任务控制块扩展 OS_STK *OSTCBStkBottom; //是指向任务栈底的指针 INT32U OSTCBStkSize; //存有栈中可容纳的指针元数目 INT16U OSTCBOpt; //把“选择项”传给OSTaskCreateExt() INT16U OSTCBId; //存储任务的识别码,留给将来扩展用
#endif
//用于任务控制块OS_TCBs的双重链接,该
链表在时钟节
struct os_tcb *OSTCBNext; //拍函数OSTimeTick()中使用,用于刷新各个任务的任
struct os_tcb *OSTCBPrev; //务延迟变量.
#if (OS_Q_EN && (OS_MAX_QS >= 2)) || OS_MBOX_EN || OS_SEM_EN OS_EVENT *OSTCBEventPtr; //指向事件控制块的指针 #endif
#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; #endif } OS_TCB;
目前,一个用于空闲任务,另一个用于任务统计(如果OS_TASK_STAT_EN是设为1的)。在μC/OS-Ⅱ初始化的时候,所有任务控制块OS_TCBs被链接成单向空任务链表。当任务一旦建立,空任务控制块指针OSTCBFreeList指向的任务控制块便赋给了该任务,然后OSTCBFreeList的值调整为指向下链表中下一个空的任务控制块。一旦任务被删除,任务控制块就还给空任务链表。
3. 就绪表(Ready List):每个任务被赋予不同的优先级等级,从0级到最低优先级OS_LOWEST_PR1O。每个任务的就绪态标志都放入就绪表中的,就绪表中有两个变量OSRedyGrp和OSRdyTbl[]。在OSRdyGrp中,任务按优先级分组,8个任务为一组。OSRdyGrp中的每一位表示8组任务中每一组中是否有进入就绪态的任务。任务进入就绪态时,就绪表OSRdyTbl[]中的相应元素的相应位也置位。
以下程序清单中的代码用于将任务放入就绪表。Prio是任务的优先级
OSRdyGrp |= OSMapTbl[prio >> 3]; OSRdyTbl[prio >> 3] |= OSMapTbl[prio & 0x07];
如果一个任务被删除了,则用程序清单中的代码做求反处理
if ((OSRdyTbl[prio >> 3] &= ~OSMapTbl[prio & 0x07]) == 0) OSRdyGrp &= ~OSMapTbl[prio >> 3];
找出进入就绪态的优先级最高的任务
y = OSUnMapTbl[OSRdyGrp]; x = OSUnMapTbl[OSRdyTbl[y]]; prio = (y << 3) + x;
4. 任务调度(Task Scheduling):μC/OS-Ⅱ总是运行进入就绪态任务中优先级最高的那一个。确定哪个任务优先级最高,下面该哪个任务运行了的工作是由调度器(Scheduler)完成的。任务级的调度是由函数OSSched()完成的。中断级的调度是由另一个函数OSIntExt()完成的,OSSched()的所有代码都属临界段代码。
5. 系统初始化OSIint():OSInit()建立空闲任务idle task,这个任务总是处于就绪态的。OSInit()还得建立统计任务OSTaskStat()并且让其进入就绪态。以上两个任务的任务控制块(OS_TCBs)是用双向链表链接在一起的。μC/OS-Ⅱ还初始化了4个空数据结构缓冲区。
6. 任务启动OSStart():多任务的启动是用户通过调用OSStart()实现的。然而,启动μC/OS-Ⅱ之前,用户至少要建立一个应用任务。当调用OSStart()时,OSStart()从任务就绪表中找出那个用户建立的优先级最高任务的任务控制块,然后,OSStart()调用高优先级就绪任务启动函数
任务管理
1. 建立任务: 建立任务:OSTaskCreate() 或 OSTaskCreateExt()。OSTaskCreate()与µC/OS是向下兼容的,OSTaskCreateExt()是
OSTaskCreate()的扩展版本,提供了一些附加的功能。任务可以在多任务调度开始前建立,也可以在其它任务的执行过程中被建立。在开始多任务调度(即调用OSStart())前,用户必须建立至少一个任务。任务不能由中断服务程序(ISR)来建立。
OSTaskCreate()需要四个参数:task是任务代码的指针,pdata是当任务开始执行时传递给任务的参数的指针,ptos是分配给任务的堆栈的栈顶指针,prio是分配给任务的优先级。 OSTaskCreate()清单
INT8U OSTaskCreate (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT8U prio) {
void *psp; INT8U err;
if (prio > OS_LOWEST_PRIO) { //检测分配给任务的优先级是否有效
return (OS_PRIO_INVALID); }
OS_ENTER_CRITICAL(); //关中断
if (OSTCBPrioTbl[prio] == (OS_TCB *)0) { //确保在规定的优先级上还没有建立任务
OSTCBPrioTbl[prio] = (OS_TCB *)1; //放置一个非空指针保留该优先级
OS_EXIT_CRITICAL(); //开中断 psp = (void *)OSTaskStkInit(task, pdata, ptos, 0); //建立任务的堆栈
err = OSTCBInit(prio, psp, (void *)0, 0, 0, (void *)0, 0);
//从空闲的OS_TCB池中获得并初始化一
个OS_TCB
if (err == OS_NO_ERR) { OS_ENTER_CRITICAL();
OSTaskCtr++; //于保存产生的任务数目 OSTaskCreateHook(OSTCBPrioTbl[prio]);
//户自己定义的函数,用来扩展
OSTaskCreate()的功能
OS_EXIT_CRITICAL();
if (OSRunning) { //如果OSTaskCreate()函数是在某个任务的执行过程中被调用
OSSched(); //调用调度函数 } } else {
OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio] = (OS_TCB *)0; //放弃该任务的优先级
OS_EXIT_CRITICAL(); }
return (err); } else {
OS_EXIT_CRITICAL(); return (OS_PRIO_EXIST); } }
用OSTaskCreateExt()函数来建立任务会更加灵活,但会增加一些额外的开销。其内部结构与OSTaskCreate()大体相同。
2. 任务堆栈:每个任务都有自己的堆栈空间。堆栈必须声明为OS_STK类型,并且由连续的内存空间组成。用户可以静态分配堆栈空间(在编译的时候分配)也可以动态地分配堆栈空间(在运行的时候分配)。µC/OS-Ⅱ支持的处理
器的堆栈既可以从上(高地址)往下(低地址)长也可以从下往上长。用户在调用OSTaskCreate()或OSTaskCreateExt()的时候必须知道堆栈是怎样长的,因为用户必须得把堆栈的栈顶传递给以上两个函数,当OS_CPU.H文件中的OS_STK_GROWTH置为0时,用户需要将堆栈的最低内存地址传递给任务创建函数,当OS_CPU.H文件中的OS_STK_GROWTH置为1时,用户需要将堆栈的最高内存地址传递给任务创建函数。任务所需的堆栈的容量是由应用程序指定的。用户在指定堆栈大小的时候必须考虑用户的任务所调用的所有函数的嵌套情况,任务所调用的所有函数会分配的局部变量的数目,以及所有可能的中断服务例程嵌套的堆栈需求。另外,用户的堆栈必须能储存所有的CPU寄存器。 3. 堆栈检验OSTaskStkChk():
为了使用µC/OS-Ⅱ的堆栈检验功能,用户必须要做以下几件事情: 在OS_CFG.H文件中设OS_TASK_CREATE_EXT为1。
用OSTaskCreateExt()建立任务,并给予任务比实际需要更多的内存空间。 在OSTaskCreateExt()中,将参数opt设置为OS_TASK_OPT_STK_CHK+
OS_TASK_OPT_STK_
CLR。注意如果用户的程序启动代码清除了所有的RAM,并且从未删除过已建立了的任务,那么用户就不必设置选项OS_TASK_OPT_STK_CLR了。这样就会减少OSTaskCreateExt()的执行时间。
将用户想检验的任务的优先级作为OSTaskStkChk()的参数并调用之。
OSTaskStkChk()顺着堆栈的栈底开始计算空闲的堆栈空间大小。具体实现方法是统计储存值为0的连续堆栈入口的数目,直到发现储存值不为0的堆栈入口。用户应该使自己的应用程序运行足够长的时间,并且经历最坏的堆栈使用情况,这样才能得到正确的数。
4. 删除任务OSTaskDel():删除任务,是说任务将返回并处于休眠状态,并不是说任务的代码被删除了,只是任务的代码不再被µC/OS-Ⅱ调用。过调用OSTaskDel()就可以完成删除任务的功能。
删除任务程序清单
INT8U OSTaskDel (INT8U prio) {
OS_TCB *ptcb; OS_EVENT *pevent;
if (prio == OS_IDLE_PRIO) { //确保用户所要删除的任务并非是空闲任务
return (OS_TASK_DEL_IDLE); }
if (prio >= OS_LOWEST_PRIO && prio != OS_PRIO_SELF) {
//用户可以删除statistic任
务
return (OS_PRIO_INVALID); }
OS_ENTER_CRITICAL();
if (OSIntNesting > 0) { //确保用户不是在ISR例程中去试图删除一个任务
OS_EXIT_CRITICAL(); return (OS_TASK_DEL_ISR); }
if (prio == OS_PRIO_SELF) { //任务可以通过指定OS_PRIO_SELF参数来删除自己
Prio = OSTCBCur->OSTCBPrio; }
if ((ptcb = OSTCBPrioTbl[prio]) != (OS_TCB *)0) {//保证被删除的任务确实存在
if ((OSRdyTbl[ptcb->OSTCBY] &= ~ptcb->OSTCBBitX) == 0) { OSRdyGrp &= ~ptcb->OSTCBBitY; //任务处于就绪表中,它会直接被移除 }
if ((pevent = ptcb->OSTCBEventPtr) != (OS_EVENT *)0) {
if ((pevent->OSEventTbl[ptcb->OSTCBY] &= ~ptcb->OSTCBBitX) == 0) { //如果任务处于邮箱、消息队列或信号量的等待表中,它就从自己所处的表中被移除
pevent->OSEventGrp &= ~ptcb->OSTCBBitY; } }
Ptcb->OSTCBDly = 0;
//将任务的时钟延迟数清零,以确保自己重新允许中断的时候,ISR例程不会
使该任务就绪
Ptcb->OSTCBStat = OS_STAT_RDY; //阻止其它任务或ISR例程让该任务重新开始执行
OSLockNesting++; //阻止任务调度程序在删除过程中切换到其它的任务中去
OS_EXIT_CRITICAL(); //重新允许中断以减少中断的响应时间
OSDummy(); //确保在再次禁止中断之前至少执行了一个调用指令和一个返回指令
OS_ENTER_CRITICAL();
OSLockNesting--; //锁定嵌套计数器减一以重新允许任务调度
OSTaskDelHook(ptcb); //调用用户自定义的OSTaskDelHook()函数 OSTaskCtr--;
OSTCBPrioTbl[prio] = (OS_TCB *)0;//将指向被删除的任务的OS_TCB的指针指向NULL
if (ptcb->OSTCBPrev == (OS_TCB *)0) { //将任务从OS_TCB双向链表中移除
ptcb->OSTCBNext->OSTCBPrev = (OS_TCB *)0; OSTCBList = ptcb->OSTCBNext; } else {
ptcb->OSTCBPrev->OSTCBNext = ptcb->OSTCBNext; ptcb->OSTCBNext->OSTCBPrev = ptcb->OSTCBPrev; }
ptcb->OSTCBNext = OSTCBFreeList;
//OS_TCB返回到空闲OS_TCB表中,并允许其
它任务的建立
OSTCBFreeList = ptcb; OS_EXIT_CRITICAL();
OSSched(); //中断服务子程序是否曾使更高优先级的任务处于就绪状态
return (OS_NO_ERR); } else {
OS_EXIT_CRITICAL(); return (OS_TASK_DEL_ERR); } }
5. 请求删除任务,OSTaskDelReq():有时候,如果任务A拥有内存缓冲区或信号量之类的资源,而任务B想删除该任务,这些资源就可能由于没被释放而丢失。在这种情况下,用户可以想法子让拥有这些资源的任务在使用完资源后,先释放资源,再删除自己。用户可以通过OSTaskDelReq()函数来完成该功能。
6. 改变任务的优先级OSTaskChangePrio():为了改变调用本函数的任务的优先级,用户可以指定该任务当前的优先级或OS_PRIO_SELF,
OSTaskChangePrio()会决定该任务的优先级。用户还必须指定任务的新(即想要的)优先级。因为µC/OS-Ⅱ不允许多个任务具有相同的优先级,所以OSTaskChangePrio()需要检验新优先级是否是合法的
7. 挂起任务OSTaskSuspend():挂起任务可通过调用OSTaskSuspend()函数来完成。被挂起的任务只能通过调用OSTaskResume()函数来恢复。 OSTaskSuspend()要确保用户的应用程序不是在挂起空闲任务 确认用户指定优先级是有效的,记住最大的有效的优先级数是
OS_LOWEST_PRIO
检验用户是否通过指定OS_PRIO_SELF来挂起调用本函数的任务本身 用户也可以通过指定优先级来挂起调用本函数的任务 检验要挂起的任务是否存在
在任务的OS_TCB中设置OS_STAT_SUSPEND标志,以表明任务正在被挂
起
若被挂起的任务是调用本函数的任务本身,调用任务调度程序
任务管理2
1. 时钟节拍:µC/OS-Ⅱ(其它内核也一样)要求用户提供定时中断来实现延时与超时控制等功能。五个与时钟节拍有关的系统服务: OSTimeDly() OSTimeDlyHMSM() OSTimeDlyResume() OSTimeGet() OSTimeSet()
2. 任务延时函数OSTimeDly():调用该函数会使µC/OS-Ⅱ进行一次任务调度,并且执行下一个优先级最高的就绪态任务。 其实现过程如下:
如果用户指定0值,则表明用户不想延时任务,函数会立即返回到调
用者
将当前任务从就绪表中移除
延时节拍数被保存在当前任务的OS_TCB中, 每隔一个时钟节拍就减少
一个延时节拍数
任务调度程序执行下一个优先级最高的就绪任务
3. 结束延时OSTimeDlyResume():µC/OS-Ⅱ允许用户结束正处于延时期的任务。延时的任务可以不等待延时期满,而是通过其它任务取消延时来使自己处于就绪态。OSTimeDlyResume()也可以唤醒正在等待事件的任务。
4. 系统时间OSTimeGet()和OSTimeSet():无论时钟节拍何时发生,µC/OS-Ⅱ都会将一个32位的计数器加1。这个计数器在用户调用OSStart()初始化多任务和4,294,967,295个节拍执行完一遍的时候从0开始计数。在时钟节拍的频率等于100Hz的时候,这个32位的计数器每隔497天就重新开始计数。用户可以通过调用OSTimeGet()来获得该计数器的当前值。也可以通过调用OSTimeSet()来改变该计数器的值。
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- xiaozhentang.com 版权所有 湘ICP备2023022495号-4
违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务