三、uC/OS-II的中断和时钟
3.1 uC/OS-II的中断
3.1.1 uC/OS-II的中断过程
uC/OS-II系统响应中断的过程是:系统接收到中断请求后,如果这是CPU处于中断允许状态(即中断时开放的),系统就会中止正在运行的当前任务,而按照中断向量的指向转而去运行中断服务子程序;当中断服务子程序的运行结束后,系统会根据情况返回到被中止的任务去继续运行,或者转向运行另一个具有更高优先级别的就绪任务。
uC/OS-II中断响应的过程示意图:
函数OSIntEnter()的作用就是把全局变量OSIntNesting 加1,从而用它记录中断嵌套的层数。OSIntEnter()的代码如下:
void OSIntEnter(void)
{
if(OSRunning == TRUE)
{
if(OSIntNesting < 255)
{
OSIntNesting++; //中断嵌套层数计数器加1
}
}
}
函数OSIntExit()的流程图:
退出中断服务函数OSIntExit()的源代码如下:
void OSIntExit(void)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
if(OSRunning == TRUE)
{
OS_ENTER_CRITICAL();
if(OSIntNesting > 0)
{
OSIntNesting--; //中断嵌套层数计数器减1
}
if((OSIntNesting == 0) && (OSLockNesting == 0))
{
OSIntExitY = OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy = (INT8U)((OSIntExitY << 3) + OSUnMapTbl[OSRdyTbl[OSIntExitY]]);
if(OSPrioHighRdy != OSPrioCur)
{
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
OSCtxSwCtr++;
OSIntCtxSw();
}
}
OS_EXIT_CRITICAL();
}
}
一个中断服务子程序的流程图如下:
在uC/OS-II中,通常用一个任务来进行异步事件的处理,而在中断服务程序只是通过向任务发消息的方法去激活这个任务。
3.1.2 中断级任务切换函数
中断级任务切换函数OSIntCtxSw()示意代码如下:
OSIntCtxSw()
{
OSTCBCur = OSTCBHighRdy; //任务控制块的切换
OSPrioCur = OSPrioHighRdy;
SP = OSTCBHighRdy -> OSTCBStkPtr; //使SP指向待运行任务堆栈
用出栈指令把R1、R2、…弹入CPU的通用寄存器中;
RETI; //中断返回,使PC指向待运行任务
}
3.1.3 应用程序中的临界段
1.临界段的基本概念
在uC/OS-II中,那些不希望被中断的代码段就是临界段;
从代码上来看,处在关中断和开中断之间的代码段就是临界段。
2.宏 OS_ENTER_CRITICAL() 和 OS_EXIT_CRITICAL()的实现方法
方式1:
#define OS_ENTER_CRITICAL() \
asm(“DI”) //关中断
#define OS_EXIT_CRITICAL() \
asm(“EI”) //开中断
方式2:
#define OS_ENTER_CRITICAL() \
asm(“PUSH PSW”) \ //通过保存程序状态字来保存中断允许标志
asm(“DI”) //关中断
#define OS_EXIT_CRITICAL() \
asm(“POP PSW”) //恢复中断允许标志
方式3:
#define OS_ENTER_CRITICAL() \
cpu_sr = get_processor_psw(); \ //通过保存程序状态字在全局变量sr中
disable_interrupts(); //关中断
#define OS_EXIT_CRITICAL() \
set_processor_psw(cpu_sr); //用sr恢复程序状态字
3.2 uC/OS-II的时钟
任何操作系统都要提供一个周期性的信号源,以供系统处理诸如延时、超时等与时间有关的事件,这个周期性的信号源叫做时钟。
硬件定时器以时钟节拍为周期定时的产生中断,该中断的中断服务程序叫做 OSTickISR().中断服务程序通过调用函数OSTimeTick()来完成系统在每个时钟节拍时需要做的工作。
OSTickISR()示意性代码如下:
void OSTickISR(void)
{
保存CPU寄存器;
调用OSIntEnter(); //记录中断嵌套层数
if(OSIntNesting == 1)
{
OSTCBCur -> OSTCBStkPtr = SP; //在任务TCB中保存堆栈指针
}
调用OSTimeTick(); //节拍处理
清除中断;
开中断;
调用OSIntExit(); //中断嵌套层数减1
恢复CPU寄存器;
中断返回;
}
OSTimeTick)示意性代码如下:
uC/OS-II在每次响应定时中断时调OSTimeTick()做了两件事情:一是给计数器OSTime加1;二是遍历任务控制块链表中的所有任务控制块,把各个任务控制块中用来存放任务延时时限的OSTCBDly变量减1,并使该项为0,同时又不使被挂起的任务进入就绪状态。
3.3 时间管理
3.3.1 任务的延时
uC/OS-II规定:除了空闲任务之外的所有任务必须在任务中合适的位置调用系统提供的函数OSTimeDly(),使当前任务的运行延时(暂停)一段时间并进行一次任务调度,以让出CPU的使用权。
函数OSTimeDly()的代码如下:
void OSTimeDly()
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
if(ticks > 0)
{
OS_ENTER_CRITICAL();
if((OSRdyTbl[OSTCBCur -> OSTCBY] &= ~OSTBCur -> OSTCBitX) == 0)
{
OSRdyGrp &= ~OSTCBCur -> OSTCBBitY; //取消当前任务的就绪状态
}
OSTCBCur -> OSTCBDly = ticks; //延时节拍数存入任务控制块
OST_EXIT_CRITICAL();
OS_Sched(); //调用调度函数
}
}
延时函数OSTimeDlyHMSM()的代码如下:
INT8U OSTimeDlyHMSM(
INT8U hours, //小时
INT8U minutes, //分
INT8U seconds, //秒
INT16U milli //毫秒
);
3.3.2 取消任务的延时
函数OSTimeDlyResume()的原型如下:
INT8U OSTimeDlyResume(INT8U prio);
参数prio为被取消延时任务的优先级别
函数OSTimeDlyResume()源代码如下:
小结:
1.在uC/OS-II中,中断服务子程序运行结束之后,系统将会根据情况进行一次中断级的任务调度去运行优先级别最高的就绪任务,而并不一定要接续运行被中断的任务。
2.uC/OS-II的中断允许嵌套,用全局变量OSIntNesting来记录嵌套数。
3.uC/OS-II的中断服务程序的工作通常是由中断激活的一个任务来完成的。
4.在任务中可以设置临界区的方法来屏蔽中断。设置临界区的宏有三种方式来实现。
5.uC/OS-II的时钟通常是一个由硬件计数器定时产生周期性中断信号来实现的,每一次中断叫做一个节拍,其中断服务程序叫做节拍服务程序。
6.uC/OS-II在每一个节拍服务里都要遍历系统中全部任务的任务控制块,把其中记录任务延时时间的成员OSTCBDly减1,并使延时时间到的任务进入就绪状态。
7.uC/OS-II有10个函数提供了钩子函数,应用程序设计人员可以再钩子函数中编写写自己的代码。
8.uC/OS-II进行时间管理的函数中,最重要的是延时函数OSTimeDly()和OSTimeDlyHMSM().