第4章 中断处理与时间管理,4.1 中断处理4.2 时钟节拍4.3 时间管理习题,4.1 中断处理,中断:由于某种事件的发生而导致程序流程的改变产生中断的事件称为中断源CPU响应中断的条件:至少有一个中断源向CPU发出中断信号;系统允许中断,且对此中断信号未予屏蔽图4.1 中断嵌套,中断服务程序ISR,中断一旦被识别,CPU会保存部分(或全部)运行上下文(context,即寄存器的值),然后跳转到专门的子程序去处理此次事件,称为中断服务子程序(ISR)μC/OS-Ⅱ中,中断服务子程序要用汇编语言来编写,然而,如果用户使用的C语言编译器支持汇编语言的话,用户可以直接将中断服务子程序代码放在C语言的程序文件中中断延迟,中断延迟:从硬件中断发生到开始执行中断处理程序第一条指令所用的时间是从中断发生到中断跳转指令执行完毕之间的这段时间,它是实时内核最重要的指标由于实时操作系统考虑得更多的是最坏的情况,而不是平均的情况,因此指令执行的时间必须按照最长的指令执行时间来计算计算公式,在前后台系统中:在不可剥夺型和不可剥夺内核中:,,,中断响应,中断响应:从中断发生起到开始执行中断用户处理程序的第一条指令所用的时间即从中断发生到刚刚开始处理异步事件之间的这段时间,它包括开始处理这个中断前的全部开销。
在前后台系统和不可剥夺型内核中: 中断响应 = 中断延迟 + 保存CPU内部寄存器的时间在可剥夺型内核中 中断响应 = 中断延迟 + 保存CPU内部寄存器的时间 + 内核进入中断服务函数的执行时间,中断恢复时间,CPU返回到被中断了的程序代码所需要的时间在前后台系统和不可剥夺型内核中对于可剥夺型内核,,前后台系统和不可剥夺型内核的中断延迟、响应和恢复时间的比较,,可剥夺型内核的中断延迟、中断响应和中断恢复时间的比较,图4.3 可剥夺型内核的中断延迟、响应和恢复时间的比较,非屏蔽中断,非屏蔽中断(NMI):指不能用系统指令来关闭的中断特点:中断优先级高、延迟时间短、响应快、不能被嵌套、不能忍受内核的延迟一般常应用于紧急事件处理,如掉电保护等非屏蔽中断的规则如下: (1) 在非屏蔽中断处理程序中,不能处理临界区代码、不能使用内核提供的服务 (2) 在非屏蔽中断处理程序中,参数的传递必须用全程变量,且全程变量的字节长度必须能够一次读完1)保存全部CPU寄存器的值;(2)调用OSIntEnter(),或直接把全局变量OSIntNesting(中断嵌套层次)加1;(3)执行用户代码做中断服务;(4)调用OSIntExit();(5)恢复所有CPU寄存器;(6)执行中断返回指令。
用户ISR的框架,标准的μC/OS-Ⅱ中断处理程序流程图,,,μC/OS-Ⅱ的中断处理过程示意图,μC/OS-Ⅱ的中断处理过程具体如下:,(1) 当一个中断发生时,若中断是开放的,则CPU运行完毕当前指令2) CPU自动将当前指令的下一条指令的程序计数器指针保存到堆栈中,然后再将中断矢量入口地址赋给程序计数器,将程序转入中断矢量入口单元 (3) 中断入口矢量单元中一般有一条长跳转指令,程序将根据长跳转指令的指向跳转到相应的用户程序去,执行中断服务子程序从第(1)步到第(3)步执行完毕之间的时间差,就是中断延迟时间4) 程序保存所有需要保存的CPU寄存器 (5) 调用一个内核系统服务——中断进入函数OSIntEnter(),通知内核,CPU已经进入中断服务子程序,并且计算中断嵌套层次μC/OS-Ⅱ的最大中断嵌套层次是255层,该数值主要是由中断嵌套层数计数器OSIntNesting的数据类型决定的从第(1)步到第(5)步执行完毕之间的时间差,就是中断响应时间6) 中断服务用户子程序7) 中断返回前需要调用一个内核系统服务——中断脱离函数OSIntExit(),通知内核CPU要退出当前中断。
中断脱离函数首先将中断嵌套计数器减1,若此时程序还处于中断嵌套中,则继续执行上一个中断;若程序没有中断嵌套,则中断脱离函数查找是否有更高优先级任务准备就绪若有更高优先级任务准备就绪,程序则返回到准备就绪的最高优先级任务运行;若没有或者调度上锁,则程序返回到被中断的任务继续运行8) 在执行完毕OSIntExit()后,无论程序转向何处,都要恢复所有CPU寄存器 (9) 执行中断返回指令 (10) 返回OSIntEnter(),/* 在调用本函数之前必须先将中断关闭 */void OSIntEnter (void){ if (OSRunning == TRUE) { if (OSIntNesting < 255) { OSIntNesting++; } }},OSIntExit(),void OSIntExit (void){ OS_ENTER_CRITICAL(); if ((--OSIntNesting|OSLockNesting) == 0) { OSIntExitY = OSUnMapTbl[OSRdyGrp]; OSPrioHighRdy=(INT8U)((OSIntExitY<< 3) + OSUnMapTbl[OSRdyTbl[OSIntExitY]]); if (OSPrioHighRdy != OSPrioCur) { OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]; OSCtxSwCtr++; OSIntCtxSw(); } } OS_EXIT_CRITICAL();},OSIntCtxSw(),在任务切换时,为什么使用OSIntCtxSw()而不是调度函数中的OS_TASK_SW()?原因有二点一半的任务切换工作,即CPU寄存器入栈,已经在前面做完了;需要保证所有被挂起任务的栈结构是一样的。
4.2 时钟节拍,时钟节拍是一种特殊的中断,相当于操作系统的心脏起搏器;μC/OS需要用户提供周期性信号源,用于实现时间延时和确认超时节拍率应在10到100Hz之间,时钟节拍率越高,系统的额外负荷就越重;时钟节拍的实际频率取决于用户应用程序的精度时钟节拍源可以是专门的硬件定时器,或是来自50/60Hz交流电源的信号时钟节拍的启动,用户必须在多任务系统启动以后再开启时钟节拍器,也就是在调用OSStart()之后;在调用OSStart()之后做的第一件事是初始化定时器中断void main(void){ ... OSInit(); /* 初始化uC/OS-II*/ /* 应用程序初始化代码... */ /* 调用OSTaskCreate()创建至少一个任务*/ 允许时钟节拍中断; /* 错误!可能crash!*/ OSStart(); /* 开始多任务调度 */ },时钟节拍服务的实现,μC/OS-Ⅱ中的时钟节拍服务是通过在中断服务子程序OSTickISR()中调用时钟节拍子程序OSTimeTick()来实现的时钟节拍计时的单位是一个时钟节拍。
时钟节拍ISR,void OSTickISR(void){ (1)保存处理器寄存器的值; (2)调用OSIntEnter()或将OSIntNesting加1; (3)调用OSTimeTick(); /*检查每个任务的时间延时*/ (4)调用OSIntExit(); (5)恢复处理器寄存器的值; (6)执行中断返回指令;},4.3 时间管理,与时间管理相关的系统服务:OSTimeDLY()OSTimeDLYHMSM()OSTimeDlyResmue()OStimeGet()OSTimeSet(),OSTimeDLY(),OSTimeDLY():任务延时函数,申请该服务的任务可以延时一段时间;调用OSTimeDLY后,任务进入等待状态;使用方法void OSTimeDly (INT16U ticks);ticks表示需要延时的时间长度,用时钟节拍的个数来表示OSTimeDLY(),void OSTimeDly (INT16U ticks){ if (ticks > 0) { OS_ENTER_CRITICAL(); if ((OSRdyTbl[OSTCBCur->OSTCBY] &= ~OSTCBCur->OSTCBBitX) == 0) { OSRdyGrp &= ~OSTCBCur->OSTCBBitY; } OSTCBCur->OSTCBDly = ticks; OS_EXIT_CRITICAL(); OSSched(); }},OSTimeDlyHMSM(),OSTimeDlyHMSM():OSTimeDly()的另一个版本,即按时分秒延时函数;使用方法INT8U OSTimeDlyHMSM( INT8U hours, // 小时 INT8U minutes, // 分钟 INT8U seconds, // 秒 INT16U milli // 毫秒 );,OSTimeDlyResume(),OSTimeDlyResume():让处在延时期的任务提前结束延时,进入就绪状态;使用方法INT8U OSTimeDlyResume (INT8Uprio);prio表示需要提前结束延时的任务的优先级/任务ID。
系统时间,每隔一个时钟节拍,发生一个时钟中断,将一个32位的计数器OSTime加1;该计数器在用户调用OSStart()初始化多任务和4,294,967,295个节拍执行完一遍的时候从0开始计数若时钟节拍的频率等于100Hz,该计数器每隔497天就重新开始计数;OSTimeGet():获得该计数器的当前值;INT32U OSTimeGet (void);OSTimeSet():设置该计数器的值void OSTimeSet (INT32U ticks);,习 题,μC/OS-Ⅱ是如何处理中断的?2. 写出μC/OS-Ⅱ中断服务子程序的示意性代码3. 如何正确使用时钟节拍器?4. 写出时间管理函数的函数原型,并举例说明如何使用。