摘要:本文从源代码角度分析了uC/OS-II内核超时等待机制,证实在一定情况下超时时间间隔不准确,在时间间隔到期的情况下,内核仍有可能返回成功,这不符合一般的操作系统原理。另外,结合超时等待机制的通用模型以及一些主流内核的实现方法,提出了这一不足之处的改正方法。
1 引言 uC/OS-II是著名的源码公开的实时内核,是专为嵌入式应用设计的,可用于各类8位16位和32位单片机或DSP。现在有很多使用者正在或已经将其移植到各种类型的芯片。因为源码公开,uC/OS-II也经常被作为嵌入式实时内核的教材,为专业人员提供了学习实时内核的难得机会。在实际使用中不管基于何种操作系统平台,应用程序经常会等待一些系统资源,如信号量,事件标志,消息等。等待类型共有三种: (1)如果不能马上获取,悬挂等待; (2)不管是否能获取资源,马上返回,不会等待; (3) 如果不能马上获取资源,将进行有限时间的等待,即超时等待。 2 超时等待机制的基本原理 应用程序通过操作系统提供的系统调用接口获取资源时,在系统调用的入口参数里可以指定超时等待的最大时间,通常以毫秒为单位,内核会将其转化为系统的时钟滴嗒数(tick)。一般内核都会执行以下流程: - 如果资源能马上获取,系统调用将成功返回。
- 如果资源不能马上获取,内核将设置一定时器进行计时,把当前任务悬挂在该资源的等待队列上,该任务从就绪表中删除,并进行调度,让出CPU的使用权。
- 如果在指定的时间内资源变得可以获取了,定时器应马上停止计时,该任务从等待队列里摘下并且重新回到就绪表中等候调度。
- 如果定时器到时,任务应该从等待队列里摘下并且重新回到就绪表中,系统调用返回超时信息。
内核在每一个tick都会做一系列的工作,包括任务的延迟以及超时等待资源的定时器等相关的检查操作。一般来讲,在指定的时间间隔以外到达的资源和信号被认为是无效的,这也是指定超时时间间隔的原意所在,有些对时间要求苛刻的场合就有这种需求,内核必须处理好这方面的问题。 3 uC/OS-II内核超时等待机制的分析 假设某任务T超时等待信号量资源R,先来分析时钟节拍函数的源代码。 void OSTimeTick(void) { OS_TCB *ptcb; OSTimeTickHook(); ptcb=OSTCBList; while(ptcb->OSTCBPrio!=OS_IDLE_PRIO){ OS_ENTER_CRITICAL(); if(ptcb->OSTCBDly!=0){ if(--ptcb->OSTCBDly==0){ if(!(ptcb->OSTCBStat&OS_STAT_SUSPEND)){//(1) OSRdyGrp|=ptcb->OSTCBBity; //(2) OSRdyTbl[ptcb->OSTCBY]|=ptcb->OSTCBBitX;//(3) }else { ptcb->OSTCBDly=1; } } } ptcb=ptcb->OSTCBNext; OS_EXIT_CRITICAL(); } OS_ENTER_CRITICAL(); OSTime++; OS_EXIT_CRITICAL(); } 语句(1),(2),(3)表明:时钟中断服务程序在每一个时钟中断在需要的情况下对任务的延迟项进行减1操作,如果任务T的定时时间间隔到期(延迟项被减为0),并且任务T没有附加的挂起操作,任务T就会进入就绪表,然而该函数却没有进一步将任务T移出资源R的等待队列,也就是说此时任务T跨了两个状态,这两个状态从本质上讲是矛盾的。虽然任务T此时处于就绪状态,但未必马上就能获得执行权,这取决于任务T的优先级。在任务T没有被调度执行之前的这段时间内,假设资源R到达了,比如一个中断服务程序调用了OSSemPost函数,会是什么情况呢?我们再来分析OSSemPost函数。 void OSSemPost(OS_EVENT *pevent) { OS_ENTER_CRITICAL(); if(pevent->OSEventGrp!=0x00){ OS_EventTaskRdy(pevent,(void*)0,OS_STAT_SEM);//(4) OS_EXIT_CRITICAL(); OS_Sched(); return(OS_NO_ERR); } if(pevent->OSEventCnt<65535){ pevent->OSEventCnt++; OS_EXIT_CRITICAL(); return(OS_NO_ERR); } OS_EXIT_CRITICAL(); return(OS_SEM_OVF); } } 从语句(4)可以看出,在资源R的等待列表中有等待任务的情况下,等待表中最高优先级的任务将从等待列表中删除,并且进入就绪表。如果等待表中的最高优先级任务就是前面讲的等待超时的任务T,这相当于任务T又一次进入就绪表,不过只有一次从等待表中删除。任务T获取到了资源,只不过是在超时时间以外获取到的。任务T获得执行权以后从调度程序返回将运行函数OSSemPend()语句(6)处的条件代码,此时语句(5)处的条件不成立,任务按获取到资源对待。 void OSSemPend(OS_EVENT *pevent,INT16U timeout,INT8U *err) { OS_ENTER_CRITICAL(); if(pevent->OSEventType!=OS_EVENT_TYPE_SEM){ OS_EXIT_CRITICAL(); *err=OS_ERR_EVENT_TYPE; } if(pevent->OSEventCnt>0){ pevent->OSEventCnt--; OS_EXIT_CRITICAL(); *err=OS_NO_ERR; }else if(OSIntNesting>0){ OS_EXIT_CRITICAL(); *err=OS_ERR_PEND_ISR; }else{ OSTCBCur->OSTCBStat|=OS_STAT_SEM; OSTCBCur->OSTCBDly=timeout; OSEventTaskWait(pevent); OS_EXIT_CRITICAL(); OSSched(); OS_ENTER_CRITICAL(); if(OSTCBCur->OSTCBStat&OS_STAT_SEM){ //(5) OSEventTo(pevent); OS_EXIT_CRITICAL(); *err=OS_TIMEOUT; }else{ //(6) OSTCBCur->OSTCBEventPtr=(OS_EVENT*0); OS_EXIT_CRITICAL(); *err=OS_NO_ERR; } } } void OSEventTo(OS_EVENT *pevent) { if((pevent->OSEventTbl[OSTCBCur->OSTCBY]&=~OSTCBCur->OSTCBBitX)==0) { pevent->OSEventGrp&=~OSTCBBitY; } OSTCBCur->OSTCBStat=OS_STAT_RDY; v OSTCBCur->OSTCBEventPtr=(OS_EVENT*0);} 如果任务T由于超时进入就绪态,到T获得执行权之前,仍没有获取到资源R,将运行语句(5)处的条件代码,由函数OSEventTo()可以看出,此时任务T才被从等待表中删除,最后返回超时状态。 通过分析开放源码的nucleus内核,发现nucleus在超时到期时执行定时器的一个回调函数,此回调函数马上将等待任务从等待链表中删除,将返回状态定性为超时。这样在任务获得执行权前,即使资源到达,该任务也不会得到。这样一来,uC/OS-II内核只要在时钟节拍函数里增加代码将延时期满的任务从相应的资源等待列表中删除即可。这一工作很容易实现,内核任务控制块有指向所等待的信号量,消息等事件控制块的指针,事件控制块里有相应的等待表。对于 uC/OS-II新引进的事件标志组,任务控制块有指向相应的等待节点的指针,等待节点有指向相应的事件标志组控制块的指针,删除一个等待节点也能实现。
|