在 uC/OS 系统中,中断相当于一个优先级最高的任务。中断一般用于处理比较紧急的事件,而且只做简单处理,例如标记该事件,带退出中断后再做详细处理。在使用 uC/OS系统时,一般建议使用信号量、消息或事件标志组等标志中断的发生,将这些内核对象发布给处理任务,处理任务再做详细处理。
在使用 uC/OS 系统时,一般建议使用信号量、消息或事件标志组等标志事件的发生,将事件发布给处理任务,处理任务再做纤细处理。在使用 uC/OS 系统时,中断的处理一般是先在中断服务函数中通过发布信号量、消息或事件标志组等内核对象来标志中断的发生,
等退出中断后再由相关处理任务详细处理中断。根据这些内核对象的发布大致可以分为两种情况,一种是在中断中直接发布,另一种是退出中断后再发布,也就是中断延迟发布。通过宏 OS_CFG_ISR_POST_DEFERRED_EN(位于“os_cfg.h”)可以使能或禁用中断延迟发布
#define OS_CFG_ISR_POST_DEFERRED_EN 1u //使能/禁用中断延迟发布
使能中断延时发布,可以将中断级任务转换成任务级任务,而且在进入临界段时也可以使用锁调度器代替关中断,这就大大减小了关中断时间,有利于提高系统的实时性。在前面提到的 OSTimeTick()、OSSemPost()、OSQPost()、OSFlagPost()、OSTaskSemPost()、OSTaskQPost()、OSTaskSuspend()和 OSTaskResume() 这些函数,在使能中断延时发布后,如果在中断中调用这些函数,不会直接执行相关功能操作,而是先使用 OS_IntQPost() 函数(位于“os_int.c”)
把这些事件发布到中断队列记录起来,待退出中断后,就会调度具有最高优先级的中断延迟提交任务 OS_IntQTask() 函数(位于“os_int.c”)真正处理这些事件。
OS_IntQPost() 函数的定义位于“os_int.c”
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
void OS_IntQPost (OS_OBJ_TYPE type, //内核对象类型 void *p_obj, //被发布的内核对象 void *p_void, //消息队列或任务消息 OS_MSG_SIZE msg_size, //消息的数目 OS_FLAGS flags, //事件标志组 OS_OPT opt, //发布内核对象时的选项 CPU_TS ts, //发布内核对象时的时间戳 OS_ERR *p_err) //返回错误类型 { CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必需该宏,该宏声明和定义一个局部变 //量,用于保存关中断前的 CPU 状态寄存器 SR(临界段关中断只需保存SR) //,开中断时将该值还原。 #ifdef OS_SAFETY_CRITICAL //如果使能(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) { //如果错误类型实参为空 OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return; //返回,不继续执行 } #endif CPU_CRITICAL_ENTER(); //关中断 if (OSIntQNbrEntries < OSCfg_IntQSize) { //如果中断队列未占满 OSIntQNbrEntries++; if (OSIntQNbrEntriesMax < OSIntQNbrEntries) { //更新中断队列的最大使用数目的历史记录 OSIntQNbrEntriesMax = OSIntQNbrEntries; } /* 将要重新提交的内核对象的信息放入到中断队列入口的信息记录块 */ OSIntQInPtr->Type = type; //保存内核对象类型 OSIntQInPtr->ObjPtr = p_obj; //保存被发布的内核对象 OSIntQInPtr->MsgPtr = p_void; //保存消息内容指针 OSIntQInPtr->MsgSize = msg_size; //保存消息大小 OSIntQInPtr->Flags = flags; //保存事件标志组 OSIntQInPtr->Opt = opt; //保存选项 OSIntQInPtr->TS = ts; //保存对象被发布的时间错 OSIntQInPtr = OSIntQInPtr->NextPtr; //指向下一个带处理成员 /* 让中断队列管理任务 OSIntQTask 就绪 */ OSRdyList[0].NbrEntries = (OS_OBJ_QTY)1; //更新就绪列表上的优先级0的任务数为1个 OSRdyList[0].HeadPtr = &OSIntQTaskTCB; //该就绪列表的头指针指向 OSIntQTask 任务 OSRdyList[0].TailPtr = &OSIntQTaskTCB; //该就绪列表的尾指针指向 OSIntQTask 任务 OS_PrioInsert(0u); //在优先级列表中增加优先级0 if (OSPrioCur != 0) { //如果当前运行的不是 OSIntQTask 任务 OSPrioSaved = OSPrioCur; //保存当前任务的优先级 } *p_err = OS_ERR_NONE; //返回错误类型为“无错误” } else { //如果中断队列已占满 OSIntQOvfCtr++; //中断队列溢出数目加1 *p_err = OS_ERR_INT_Q_FULL; //返回错误类型为“无错误” } CPU_CRITICAL_EXIT(); //开中断 }
OS_IntQTask () 函数的定义位于“os_int.c”:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
void OS_IntQTask (void *p_arg) { CPU_BOOLEAN done; CPU_TS ts_start; CPU_TS ts_end; CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必需该宏,该宏声明和 //定义一个局部变量,用于保存关中断前的 CPU 状态寄存器 // SR(临界段关中断只需保存SR),开中断时将该值还原。 p_arg = p_arg; while (DEF_ON) { //进入死循环 done = DEF_FALSE; while (done == DEF_FALSE) { CPU_CRITICAL_ENTER(); //关中断 if (OSIntQNbrEntries == (OS_OBJ_QTY)0u) { //如果中断队列里的内核对象发布完毕 OSRdyList[0].NbrEntries = (OS_OBJ_QTY)0u; //从就绪列表移除中断队列管理任务 OS_IntQTask OSRdyList[0].HeadPtr = (OS_TCB *)0; OSRdyList[0].TailPtr = (OS_TCB *)0; OS_PrioRemove(0u); //从优先级表格移除优先级0 CPU_CRITICAL_EXIT(); //开中断 OSSched(); //任务调度 done = DEF_TRUE; //退出循环 } else { //如果中断队列里还有内核对象 CPU_CRITICAL_EXIT(); //开中断 ts_start = OS_TS_GET(); //获取时间戳 OS_IntQRePost(); //发布中断队列里的内核对象 ts_end = OS_TS_GET() - ts_start; //计算该次发布时间 if (OSIntQTaskTimeMax < ts_end) { //更新中断队列发布内核对象的最大时间的历史记录 OSIntQTaskTimeMax = ts_end; } CPU_CRITICAL_ENTER(); //关中断 OSIntQOutPtr = OSIntQOutPtr->NextPtr; //中断队列出口转至下一个 OSIntQNbrEntries--; //中断队列的成员数目减1 CPU_CRITICAL_EXIT(); //开中断 } } } }
在 执 行 任 务 OS_IntQTask () 时 真 正 起 到 提 交 作 用 的 是 OS_IntQRePost() 函 数 ,
OS_IntQRePost() 函数的定义也位于“os_int.c”:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
void OS_IntQRePost (void) { CPU_TS ts; OS_ERR err; switch (OSIntQOutPtr->Type) { //根据内核对象类型分类处理 case OS_OBJ_TYPE_FLAG: //如果对象类型是事件标志 #if OS_CFG_FLAG_EN > 0u //如果使能了事件标志,则发布事件标志 (void)OS_FlagPost((OS_FLAG_GRP *) OSIntQOutPtr->ObjPtr, (OS_FLAGS ) OSIntQOutPtr->Flags, (OS_OPT ) OSIntQOutPtr->Opt, (CPU_TS ) OSIntQOutPtr->TS, (OS_ERR *)&err); #endif break; //跳出 case OS_OBJ_TYPE_Q: //如果对象类型是消息队列 #if OS_CFG_Q_EN > 0u //如果使能了消息队列,则发布消息队列 OS_QPost((OS_Q *) OSIntQOutPtr->ObjPtr, (void *) OSIntQOutPtr->MsgPtr, (OS_MSG_SIZE) OSIntQOutPtr->MsgSize, (OS_OPT ) OSIntQOutPtr->Opt, (CPU_TS ) OSIntQOutPtr->TS, (OS_ERR *)&err); #endif break; //跳出 case OS_OBJ_TYPE_SEM: //如果对象类型是多值信号量 #if OS_CFG_SEM_EN > 0u //如果使能了多值信号量,则发布多值信号量 (void)OS_SemPost((OS_SEM *) OSIntQOutPtr->ObjPtr, (OS_OPT ) OSIntQOutPtr->Opt, (CPU_TS ) OSIntQOutPtr->TS, (OS_ERR *)&err); #endif break; //跳出 case OS_OBJ_TYPE_TASK_MSG: //如果对象类型是任务消息 #if OS_CFG_TASK_Q_EN > 0u //如果使能了任务消息,则发布任务消息 OS_TaskQPost((OS_TCB *) OSIntQOutPtr->ObjPtr, (void *) OSIntQOutPtr->MsgPtr, (OS_MSG_SIZE) OSIntQOutPtr->MsgSize, (OS_OPT ) OSIntQOutPtr->Opt, (CPU_TS ) OSIntQOutPtr->TS, (OS_ERR *)&err); #endif break; //跳出 case OS_OBJ_TYPE_TASK_RESUME://如果对象类型是恢复任务 #if OS_CFG_TASK_SUSPEND_EN > 0u //如果使能了函数OSTaskResume(),恢复该任务 (void)OS_TaskResume((OS_TCB *) OSIntQOutPtr->ObjPtr, (OS_ERR *)&err); #endif break; //跳出 case OS_OBJ_TYPE_TASK_SIGNAL://如果对象类型是任务信号量 (void)OS_TaskSemPost((OS_TCB *) OSIntQOutPtr->ObjPtr,//发布任务信号量 (OS_OPT ) OSIntQOutPtr->Opt, (CPU_TS ) OSIntQOutPtr->TS, (OS_ERR *)&err); break; //跳出 case OS_OBJ_TYPE_TASK_SUSPEND://如果对象类型是挂起任务 #if OS_CFG_TASK_SUSPEND_EN > 0u //如果使能了函数 OSTaskSuspend(),挂起该任务 (void)OS_TaskSuspend((OS_TCB *) OSIntQOutPtr->ObjPtr, (OS_ERR *)&err); #endif break; //跳出 case OS_OBJ_TYPE_TICK: //如果对象类型是时钟节拍 #if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u //如果使能了时间片轮转调度, OS_SchedRoundRobin(&OSRdyList[OSPrioSaved]); //轮转调度进中断前优先级任务 #endif (void)OS_TaskSemPost((OS_TCB *)&OSTickTaskTCB, //发送信号量给时钟节拍任务 (OS_OPT ) OS_OPT_POST_NONE, (CPU_TS ) OSIntQOutPtr->TS, (OS_ERR *)&err); #if OS_CFG_TMR_EN > 0u //如果使能了软件定时器,发送信号量给定时器任务 OSTmrUpdateCtr--; if (OSTmrUpdateCtr == (OS_CTR)0u) { OSTmrUpdateCtr = OSTmrUpdateCnt; ts = OS_TS_GET(); (void)OS_TaskSemPost((OS_TCB *)&OSTmrTaskTCB, (OS_OPT ) OS_OPT_POST_NONE, (CPU_TS ) ts, (OS_ERR *)&err); } #endif break; //跳出 default: //如果内核对象类型超出预期 break; //直接跳出 } }
OSIntEnter ()
任务在进入中断服务函数时需要首先调用 OSIntEnter () 函数标记进入中断,方便中断嵌套管理。OSIntEnter () 函数的信息如下表所示
OSIntEnter () 函数的定义位于“os_core.c”:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
void OSIntEnter (void) { if (OSRunning != OS_STATE_OS_RUNNING) { //如果操作系统还没运行 return; //返回,停止执行 } /* 如果操作系统已经运行 */ if (OSIntNestingCtr >= (OS_NESTING_CTR)250u) { //如果中断嵌套已经达到250层 return; //返回,停止执行 } /* 如果中断嵌套尚未达到250层 */ OSIntNestingCtr++; //中断嵌套数目加1 }
OSIntExit ()
与 OSIntEnter () 函数相对应,任务在退出中断服务函数时需要首调用 OSIntExit () 函数标记退出中断,方便中断嵌套管理。OSIntExit () 函数的信息如下表所示
OSIntExit () 函数的定义位于“os_core.c”:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
void OSIntExit (void) { CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必需该宏,该宏声明和 //定义一个局部变量,用于保存关中断前的 CPU 状态寄存器 // SR(临界段关中断只需保存SR),开中断时将该值还原。 if (OSRunning != OS_STATE_OS_RUNNING) { //如果任务尚未运行 return; //返回,停止执行 } CPU_INT_DIS(); //关中断 if (OSIntNestingCtr == (OS_NESTING_CTR)0) { //如果该函数不是在中断内 CPU_INT_EN(); //开中断 return; //返回,停止执行 } OSIntNestingCtr--; //如果该函数在中断内,中断嵌套数减1 if (OSIntNestingCtr > (OS_NESTING_CTR)0) { //如果中断仍被嵌套 CPU_INT_EN(); //开中断,执行上一层中断服务 return; //返回,停止执行 } /* 如果中断已经完全解嵌(没有上一层中断) */ if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) { //如果调度器被锁 CPU_INT_EN(); //开中断 return; //返回,停止执行 } /* 如果调度器未被锁 */ OSPrioHighRdy = OS_PrioGetHighest(); //获取就绪的最高优先级 OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;//提取该优先级就绪列表的头个任务 if (OSTCBHighRdyPtr == OSTCBCurPtr) { //如果该任务是进中断前运行的任务 CPU_INT_EN(); //开中断 return; //直接返回便会继续执行该任务 } /* 如果该任务不是进中断前运行的任务 */ #if OS_CFG_TASK_PROFILE_EN > 0u //如果使能了任务控制块的简况变量 OSTCBHighRdyPtr->CtxSwCtr++; //该任务被切换的次数加1 #endif OSTaskCtxSwCtr++; //总的任务切换次数加1 #if defined(OS_CFG_TLS_TBL_SIZE) && (OS_CFG_TLS_TBL_SIZE > 0u) OS_TLS_TaskSw(); #endif OSIntCtxSw(); //中断级任务调度,调度该任务运行 CPU_INT_EN(); //开中断 }
CPU_IntDisMeasMaxGet ()
关中断时间是嵌入式程序设计一个很重要的参数,uC/OS 系统也提供了测量关中断时间的机制。要使用测量关中断时间机制,必须事先使能该机制(位于“cpu_cfg.h”),如下所示
#if 1 // Modified by fire (原是 0) //使能/禁用关中断时间测量 #define CPU_CFG_INT_DIS_MEAS_EN #endif
要测量关中断时间,除了要使能测量关中断时间功能外,还须在程序初始化时调用CPU_Init() 函数,该函数里面包括用于初始化测量关中断时间的 CPU_IntDisMeasInit() 函数。CPU_Init() 函数一般在起始任务的初始化部分调用
CPU_IntDisMeasMaxGet () 函数中调用了 CPU_IntDisMeasMaxCalc() 函数将测量的最大关中断减去测量时间,得到更加纯净的最大关中断时,
CPU_IntDisMeasMaxCalc() 函数的定义也位于“cpu_core.c”。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#ifdef CPU_CFG_INT_DIS_MEAS_EN //如果使能了关中断时间测量 static CPU_TS_TMR CPU_IntDisMeasMaxCalc (CPU_TS_TMR time_tot_cnts) { CPU_TS_TMR time_max_cnts; /* 将尚未处理的最大关中断时间(包括测量时间)减去测量时间 */ time_max_cnts = time_tot_cnts; if (time_max_cnts > CPU_IntDisMeasOvrhd_cnts) { time_max_cnts -= CPU_IntDisMeasOvrhd_cnts; } else { time_max_cnts = 0u; } return (time_max_cnts); } #endif
CPU_IntDisMeasMaxCurGet ()
与 CPU_IntDisMeasMaxCurReset () 函数相对应,CPU_IntDisMeasMaxCurGet() 函数是配合其完成测量程序段的最大关中断时间。CPU_IntDisMeasMaxCurGet() 函数用于结束程序段的最大关中断时间的测量,并返回测量的时间。CPU_IntDisMeasMaxCurGet() 函数的信息如下所示
CPU_IntDisMeasMaxCurGet () 函数的定义位于“cpu_core.c :
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#ifdef CPU_CFG_INT_DIS_MEAS_EN //如果使能了关中断时间测量 CPU_TS_TMR CPU_IntDisMeasMaxCurGet (void) //获取测量的程序段的最大关中断时间 { CPU_TS_TMR time_tot_cnts; CPU_TS_TMR time_max_cnts; CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必需该宏,该宏声明和 //定义一个局部变量,用于保存关中断前的 CPU 状态寄存器 // SR(临界段关中断只需保存SR),开中断时将该值还原。 CPU_INT_DIS(); //关中断 time_tot_cnts = CPU_IntDisMeasMaxCur_cnts; //获取未处理的程序段最大关中断时间 CPU_INT_EN(); //开中断 time_max_cnts = CPU_IntDisMeasMaxCalc(time_tot_cnts);//获取减去测量时间后的最大关中断时间 return (time_max_cnts); //返回程序段的最大关中断时间 } #endif
CPU_IntDisMeasMaxCurGet () 函 数 跟 CPU_IntDisMeasMaxGet () 函 数 一 样 调 用 了CPU_IntDisMeasMaxCalc() 函数将测量的最大关中断减去测量时间,得到更加纯净的最大关中断时,
CPU_IntDisMeasMaxCalc() 函数的定义位于“cpu_core.c”:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#ifdef CPU_CFG_INT_DIS_MEAS_EN //如果使能了关中断时间测量 static CPU_TS_TMR CPU_IntDisMeasMaxCalc (CPU_TS_TMR time_tot_cnts) { CPU_TS_TMR time_max_cnts; /* 将尚未处理的最大关中断时间(包括测量时间)减去测量时间 */ time_max_cnts = time_tot_cnts; if (time_max_cnts > CPU_IntDisMeasOvrhd_cnts) { time_max_cnts -= CPU_IntDisMeasOvrhd_cnts; } else { time_max_cnts = 0u; } return (time_max_cnts); } #endif