uCOS-II实时操作系统
第四章 中断和时钟
完成操作系统任务的学习后,下面开始学习中断和时钟,中断是计算机处理异步事件的重要机制;而任务的延时和调度等是一些与时间相关的事件,需要系统提供一个系统时钟。下面开始内容的学习!
中断
系统在接收到中断请求后,如果CPU处于中断允许状态,系统会终止当前任务,保存当前任务现场,然后调转到中断向量入口,进而执行中断服务函数,在中断函数执行完后,需要注意的是,uCOS-II系统会根据情况来决定是正常返回,或者转而运行另一个更高优先级的就绪任务(进行任务调度)。系统的中断响应过程如下:
图中左边紫色框住部分即为正常中断相应至中断返回的过程,紫色框外部分则是系统转而运行另一个更高优先级的就绪任务的过程
那么系统是如何根据情况确定是正常返回还是执行任务调度呢?这里介绍编写uCOS-II中断服务函数时要用到的两个重要函数:OSIntEnter()和OSIntExit()。
其中OSIntEnter()作用是记录中断嵌套的层数(因为中断嵌套为0时才允许中断时执行任务调度)。下面是其代码实现:
// os_core.c
void OSIntEnter (void)
{
if (OSRunning == OS_TRUE) {
if (OSIntNesting < 255u) {
OSIntNesting++; /* Increment ISR nesting level */
}
}
}
另一个是退出中断服务函数OSIntExit(),其代码实现如下:
// os_core.c
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--; // 中断嵌套层数计数器减一
}
/* 中断嵌套层数计数器为0 */
if (OSIntNesting == 0u) {
/* 调度器未被锁定. */
if (OSLockNesting == 0u) {
OS_SchedNew();
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
/* 最高级别就绪任务不是当前任务才执行任务切换 */
if (OSPrioHighRdy != OSPrioCur) {
#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();
}
}
从代码中看出,在中断函数执行完后,是否执行任务切换的条件为:中断嵌套层数计数器为0;而且调度器未被锁定;而且最高级别就绪任务不是当前任务,在满足这三个条件后,实际上由OSIntCtxSw()执行中断级任务切换
查看OSIntCtxSw()的实现可以发现,其与普通任务切换函数OSCtxSw()一模一样。
//os_cpu_a.asm
OSIntCtxSw
PUSH {R4, R5}
LDR R4, =NVIC_INT_CTRL ;触发PendSV异常 (causes context switch)
LDR R5, =NVIC_PENDSVSET
STR R5, [R4]
POP {R4, R5}
BX LR
NOP
系统时钟
任何操作系统都需要一个周期性的时钟源用于为系统提供诸如延时等服务。
uCOS-II使用硬件定时器产生一个周期为毫秒级的周期性中断来实现系统时钟。相邻两次中断之间的时间间隔为最小时钟单位,也称为时钟节拍(Time Tick)
硬件定时器周期性中断的中断服务程序为OSTickISR(),其通过调用OSTimeTick()来完成系统在每个节拍需要做的工作。
// delay.c
/* OSTickISR()在正点原子下载的ucos-ii项目中没找到,发现了systick中断服务函数SysTick_Handler() */
void SysTick_Handler(void)
{
if(delay_osrunning==1) //OS开始跑了,才执行正常的调度处理
{
OSIntEnter(); //进入中断
OSTimeTick(); //调用ucos的时钟服务程序
OSIntExit(); //触发任务切换软中断
}
}
// os_core.c
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(); /* Call user definable hook */
#endif
#if OS_TIME_GET_SET_EN > 0u
OS_ENTER_CRITICAL(); /* Update the 32-bit tick counter */
OSTime++; //用于记录节拍数
OS_EXIT_CRITICAL();
#endif
if (OSRunning == OS_TRUE) {
......
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 */
/* 将任务控制块中用来存放任务延时时限的OSTCBDly变量减1 */
ptcb->OSTCBDly--;
/* 等待任务超时 */
if (ptcb->OSTCBDly == 0u) {
/*任务不是挂起任务时才可以使其进入就绪状态*/
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();
}
}
}
从代码中可以看出,OSTimeTick()在系统时钟中断时,遍历任务控制块链表中的任务(任务控制块),将任务控制块中用来存放任务延时时限的OSTCBDly变量减1,直到OSTCBDly递减为0(超时)且任务不是挂起任务时使对应任务进入就绪状态。一句话概括为:OSTimeTick()函数主要实现将系统中的延时任务转变为就绪任务
注意到函数OSTimeTick(),提供了一个名为OSTimeTickHook()的函数
OSTimeTickHook()是OSTimeTick()的钩子函数,OSTimeTick()是系统函数,但在系统函数中留了接口(钩子函数),方便我们用户在系统调用中插入自己的一些工作,其代码如下:
// os_cpu.c
#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
}
#endif
时间管理–任务的延时和取消延时
嵌入式的任务是一个无限循环,为了避免高优先级的任务一直独占CPU,uCOS-II规定,除了空闲任务之外的所有任务必须在任务中合适的位置调用系统提供的延时函数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; /* Load ticks in TCB */
// 调用调度函数
OS_EXIT_CRITICAL();
OS_Sched(); /* Find next task to run! */
}
}
当OSTimeDly()函数延时期满,或者调用延时取消函数OSTimeDlyResume()时,任务会立即进入就绪状态。下面是OSTimeDlyResume()函数的代码:
#if OS_TIME_DLY_RESUME_EN > 0u
INT8U OSTimeDlyResume (INT8U prio)
{
OS_TCB *ptcb;
#if OS_CRITICAL_METHOD == 3u /* Storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
if (prio >= OS_LOWEST_PRIO) {
return (OS_ERR_PRIO_INVALID);
}
OS_ENTER_CRITICAL();
ptcb = OSTCBPrioTbl[prio]; /* Make sure that task exist */
if (ptcb == (OS_TCB *)0) {
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NOT_EXIST); /* The task does not exist */
}
if (ptcb == OS_TCB_RESERVED) {
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NOT_EXIST); /* The task does not exist */
}
if (ptcb->OSTCBDly == 0u) { /* See if task is delayed */
OS_EXIT_CRITICAL();
return (OS_ERR_TIME_NOT_DLY); /* Indicate that task was not delayed */
}
ptcb->OSTCBDly = 0u; /* Clear the time delay */
if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) {
ptcb->OSTCBStat &= ~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;
OS_EXIT_CRITICAL();
/* 进行一次调度 */
OS_Sched(); /* See if this is new highest priority */
} else {
OS_EXIT_CRITICAL(); /* Task may be suspended */
}
return (OS_ERR_NONE);
}
#endif