文章目录
一.UCOSII的中断
任务在运行过程中,应外部或者内部异常事件的请求中止当前任务,而去处理异步事件的过程叫中断。
1.中断响应过程
UCOSII系统响应中断的过程是:系统接收到中断请求后,如果此时CPU处于中断允许状态,系统会中止当前任务,转区执行中断服务子程序;当中断处理结束后,系统将会根据情况返回到被中止的任务继续运行,或者转向运行另一个更高优先级的就绪任务。也就是说中断返回会引发一次任务调度,不一定回到原来被打断的任务执行,而是选择最高优先级就绪任务执行。
在编写中断服务程序时,要用到两个重要的函数OSIntEnter()和OSIntExit()。
2.进入中断服务函数OSIntEnter()函数
函数OSIntEnter()的作用就是把全局变量OSIntNesting加1,从而用它来记录中断嵌套的层数。函数OSIntEnter()的代码如下:
void OSIntEnter (void)
{
if (OSRunning == OS_TRUE) {
if (OSIntNesting < 255u) {
OSIntNesting++; /*中断嵌套层数计数器加1*/
}
}
}
3.退出中断服务函数OSIntExit()
退出中断服务函数OSIntExit()的流程图如下所示:
可以看出,这个函数在中断嵌套层数为0.调度器未被锁定且从任务就绪表中找到最高级别就绪任务又不是被中断的任务的条件下进行任务切换。否则就返回被中断的服务子程序。
函数OSIntExit()的代码如下:
void OSIntExit (void)
{
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
if (OSRunning == OS_TRUE) {
OS_ENTER_CRITICAL();
if (OSIntNesting > 0u) { /* Prevent OSIntNesting from wrapping */
OSIntNesting--; //中断嵌套层数减1
}
if (OSIntNesting == 0u) { /* Reschedule only if all ISRs complete ... */
if (OSLockNesting == 0u) { /* ... and not locked. */
OS_SchedNew();
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
if (OSPrioHighRdy != OSPrioCur) { /* No Ctx Sw if current task is highest rdy */
#if OS_TASK_PROFILE_EN > 0u
OSTCBHighRdy->OSTCBCtxSwCtr++; /* Inc. # of context switches to this task */
#endif
OSCtxSwCtr++; /* Keep track of the number of ctx switches */
OSIntCtxSw(); /* Perform interrupt level ctx switch */
}
}
}
OS_EXIT_CRITICAL();
}
}
4.中断服务子程序流程
5.中断级任务切换函数OSIntCtxSw()
前面谈到,系统在执行完中断程序之后不一定返回被中断的任务,而是要通过一次任务调度来决定去向,因此需要一个中断级任务调度器。
中断级任务切换函数OSIntCtxSw()代码如下(由于是采用汇编语言编写,因此这里只给出伪代码):
OSIntCtxSw()
{
OSTCBCur=OSTCBHighRdy; //任务控制块的切换
SOPrioCur=OSPrioHighRdy;
SP=OSTCBHighRdy->OSTCBStkPtr; //使SP指向待运行任务堆栈
用出栈指令把R1,R2....弹入CPU的通用寄存器;
RETI; //中断返回,使PC指向待运行任务
}
6.应用程序中的临界段
在UCOSII中,把那些不希望被中断打断的代码段叫做临界段。
{
OS_ENTER_CRITICAL(); //进入临界区(无法被中断打断)
............................ //被保护的代码段
OS_EXIT_CRITICAL(); //退出临界区(可以被中断打断)
}
二.UCOSII的时钟
1.时钟中断
UCOSII和大多数计算机操作系统一样,用硬件定时器产生一个周期为ms级的周期性中断来实现系统时钟。最小的时钟单位就是两次中断之间的相间隔的时间,这个最小时钟单位叫做节拍(Time Tick)。
硬件定时器已时钟节拍定时的产生中断,该中断的服务函数叫做OSTickISR()。中断服务程序通过调用函数OSTimeTick()来完成系统在每个时钟节拍时需要做的工作。
以下为中断服务函数OSTickISR()的伪代码:
void OSTickISR()
{
保存CPU寄存器;
调用OSIntEnter(); //记录中断嵌套层数
if(OSIntNesting==1)
{
OSTCBCur->OSTCBStkPtr=SP;//在任务TCB中保存堆栈指针
}
调用OSTimeTick(); //节拍处理,主要节拍中断任务在该函数中完成!!!
清除中断;
开中断;
调用OSIntExit(); //中断嵌套层数减1
恢复CPU寄存器;
中断返回;
}
OSTimeTick()叫做时钟节拍函数,源代码如下:
void OSTimeTick (void)
{
OS_TCB *ptcb;
#if OS_TICK_STEP_EN > 0u
BOOLEAN step;
#endif
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
#if OS_TIME_TICK_HOOK_EN > 0u
OSTimeTickHook(); /* 钩子函数 */
#endif
#if OS_TIME_GET_SET_EN > 0u
OS_ENTER_CRITICAL(); /* 进入临界段 */
OSTime++; //记录节拍数
OS_EXIT_CRITICAL(); //退出临界段
#endif
if (OSRunning == OS_TRUE) //判断运行状态是否为真
{
#if OS_TICK_STEP_EN > 0u
switch (OSTickStepState) { /* Determine whether we need to process a tick */
case OS_TICK_STEP_DIS: /* Yes, stepping is disabled */
step = OS_TRUE;
break;
case OS_TICK_STEP_WAIT: /* No, waiting for uC/OS-View to set ... */
step = OS_FALSE; /* .. OSTickStepState to OS_TICK_STEP_ONCE */
break;
case OS_TICK_STEP_ONCE: /* Yes, process tick once and wait for next ... */
step = OS_TRUE; /* ... step command from uC/OS-View */
OSTickStepState = OS_TICK_STEP_WAIT;
break;
default: /* Invalid case, correct situation */
step = OS_TRUE;
OSTickStepState = OS_TICK_STEP_DIS;
break;
}
if (step == OS_FALSE) { /* Return if waiting for step command */
return;
}
#endif
ptcb = OSTCBList; /* Point at first TCB in TCB list */
while (ptcb->OSTCBPrio != OS_TASK_IDLE_PRIO) { /* 进行轮询每个任务,不是空闲任务 */
OS_ENTER_CRITICAL();
if (ptcb->OSTCBDly != 0u) { /* No, Delayed or waiting for event with TO */
ptcb->OSTCBDly--; /* Decrement nbr of ticks to end of delay */
if (ptcb->OSTCBDly == 0u) { /* Check for timeout */
if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) {
ptcb->OSTCBStat &= (INT8U)~(INT8U)OS_STAT_PEND_ANY; /* Yes, Clear status flag */
ptcb->OSTCBStatPend = OS_STAT_PEND_TO; /* Indicate PEND timeout */
} else {
ptcb->OSTCBStatPend = OS_STAT_PEND_OK;
}
if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) { /* Is task suspended? */
OSRdyGrp |= ptcb->OSTCBBitY; /* No, Make ready */
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
}
}
}
ptcb = ptcb->OSTCBNext; /* Point at next TCB in TCB list */
OS_EXIT_CRITICAL();
}
}
}
其中钩子函数为每个时钟周期执行一次,执行的操作由开发者自己定义,钩子函数源码如下所示:
#if (OS_CPU_HOOKS_EN > 0) && (OS_TIME_TICK_HOOK_EN > 0)
void OSTimeTickHook (void)
{
#if OS_app_HOOKS_EN > 0 //钩子函数使能
App_TimeTickHook();//预留出钩子函数处理一些事情的APP函数
#endif
#if OS_TMR_EN > 0
OSTmrCtr++;
if (OSTmrCtr >= (OS_TICKS_PER_SEC / OS_TMR_CFG_TICKS_PER_SEC)) {
OSTmrCtr = 0;
OSTmrSignal();
}
#endif
}
UCOSII在每次响应定时中断时调用OSTimeTick(),做了两件事情:一是给计数器OSTime加1,记录节拍数;二是遍历任务控制块链表中所有任务控制块。简单来说,OSTimeTick()的任务就是在每个时钟节拍了解每个任务的延时状态,使其中已经到了延时时限的非挂起任务进入就绪状态。
2.时间管理
UCOSII规定:除了空闲任务之外的所有任务必须在任务中合适的位置调用系统提供的函数OSTimeDly(),使当前任务的运行暂停一段时间并进行一次任务调度,让出CPU的使用权。
OSTimeDly()源码:
void OSTimeDly (INT32U ticks)
{
INT8U y;
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
if (OSIntNesting > 0u) { /* See if trying to call from an ISR */
return;
}
if (OSLockNesting > 0u) { /* See if called with scheduler locked */
return;
}
if (ticks > 0u) { /* 0 means no delay! */
OS_ENTER_CRITICAL();
y = OSTCBCur->OSTCBY; /* Delay current task */
OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX;
if (OSRdyTbl[y] == 0u) {
OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY; //取消当前任务的就绪状态
}
OSTCBCur->OSTCBDly = ticks; /* 延时节拍数存入任务控制块*/
OS_EXIT_CRITICAL();
OS_Sched(); /* 进行一次任务调度*/
}
}
取消任务的延时:使任务进入就绪状态,如果新任务比正在运行的任务优先级高,则立即引发一次任务调度(即高优先级可以打断低优先级)。
OSTimeDlyResume(); //取消任务延时
三.总结
1.UCOSII中,中断服务函数执行结束之后,系统将会进行一次中断级的任务调度,选择最高优先级就绪任务执行,不一定返回执行之前被中断任务。
2.采用全局变量OSIntNesting来记录中断嵌套次数。
3.可以通过设置临界区来屏蔽中断。
4.时钟是由硬件计数器定时产生周期性中断信号来实现,每次叫一个节拍,在其中断服务函数中进行一系列操作见下。
5.每个节拍中断服务函数中要遍历所有任务块,把其中记录任务延时时间的OSTCBDly减1,并使延时时间到的任务进入就绪状态。
6.在节拍中断服务函数中提供了钩子函数,可以在其中编写自己的代码。
7.OSTimeDly()使当前任务的运行暂停一段时间并进行一次任务调度,让出CPU的使用权。