多值信号量是 uC/OS 操作系统的一个内核对象,主要用于标志事件的发生和共享资源管理。
如果想要使用多值信号量,就必须事先使能多值信号量。多值信号量的使能位于“os_cfg.h”
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/* ----------------------------- SEMAPHORES ---------------------------- */ #define OS_CFG_SEM_EN 1u //使能或禁用多值信号量 #define OS_CFG_SEM_DEL_EN 1u //使能或禁用 OSSemDel() 函数 #define OS_CFG_SEM_PEND_ABORT_EN 1u //使能或禁用 OSSemPendAbort() 函数 #define OS_CFG_SEM_SET_EN 1u //使能或禁用 OSSemSet() 函数
OSSemCreate ()
要使用 uC/OS 的多值信号量必须先声明和创建多值信号量,调用 OSSemCreate () 函数可以创建一个多值信号量。OSSemCreate () 函数的信息如下表所示。
OSSemCreate () 函数的定义位于“os_sem.c”:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
void OSSemCreate (OS_SEM *p_sem, //多值信号量控制块指针 CPU_CHAR *p_name, //多值信号量名称 OS_SEM_CTR cnt, //资源数目或事件是否发生标志 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 #ifdef OS_SAFETY_CRITICAL_IEC61508 //如果使能(默认禁用)了安全关键 if (OSSafetyCriticalStartFlag == DEF_TRUE) { //如果是在调用 OSSafetyCriticalStart() 后创建该多值信号量 *p_err = OS_ERR_ILLEGAL_CREATE_RUN_TIME; //错误类型为“非法创建内核对象” return; //返回,不继续执行 } #endif #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u //如果使能(默认使能)了中断中非法调用检测 if (OSIntNestingCtr > (OS_NESTING_CTR)0) { //如果该函数是在中断中被调用 *p_err = OS_ERR_CREATE_ISR; //错误类型为“在中断函数中定时” return; //返回,不继续执行 } #endif #if OS_CFG_ARG_CHK_EN > 0u //如果使能(默认使能)了参数检测 if (p_sem == (OS_SEM *)0) { //如果参数 p_sem 为空 *p_err = OS_ERR_OBJ_PTR_NULL; //错误类型为“多值信号量对象为空” return; //返回,不继续执行 } #endif OS_CRITICAL_ENTER(); //进入临界段 p_sem->Type = OS_OBJ_TYPE_SEM; //初始化多值信号量指标 p_sem->Ctr = cnt; p_sem->TS = (CPU_TS)0; p_sem->NamePtr = p_name; OS_PendListInit(&p_sem->PendList); //初始化该多值信号量的等待列表 #if OS_CFG_DBG_EN > 0u //如果使能(默认使能)了调试代码和变量 OS_SemDbgListAdd(p_sem); //将该定时添加到多值信号量双向调试链表 #endif OSSemQty++; //多值信号量个数加1 OS_CRITICAL_EXIT_NO_SCHED(); //退出临界段(无调度) *p_err = OS_ERR_NONE; //错误类型为“无错误” }
其中,调用了 OS_PendListInit() 函数初始化了多值信号量的等待列表。每个多值信号量都有一个等待列表,凡是等待该多值信号量的的任务都会被插入到这个等待列表,方便高效管理。
OS_PendListInit() 函数的定义位于“os_core.c” :
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
void OS_PendListInit (OS_PEND_LIST *p_pend_list) { p_pend_list->HeadPtr = (OS_PEND_DATA *)0; //复位等待列表的所有成员 p_pend_list->TailPtr = (OS_PEND_DATA *)0; p_pend_list->NbrEntries = (OS_OBJ_QTY )0; }
如果使能了 OS_CFG_DBG_EN (位于“ os_cfg.h”),创建多值信号量时还会调用OS_SemDbgListAdd() 函数将该多值信号量插入到一个多值信号量调试列表,是为方便调试所设。
OS_SemDbgListAdd() 函数的定义位于“os_sem.c”:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#if OS_CFG_DBG_EN > 0u //如果使能(默认使能)了调试代码和变量 void OS_SemDbgListAdd (OS_SEM *p_sem) //将该多值信号量插入到多值信号量列表的最前端 { p_sem->DbgNamePtr = (CPU_CHAR *)((void *)" "); //先不指向任何任务的名称 p_sem->DbgPrevPtr = (OS_SEM *)0; //将该信号量作为列表的最前端 if (OSSemDbgListPtr == (OS_SEM *)0) { //如果列表为空 p_sem->DbgNextPtr = (OS_SEM *)0; //该信号量的下一个元素也为空 } else { //如果列表非空 p_sem->DbgNextPtr = OSSemDbgListPtr; //列表原来的首元素作为该信号量的下一个元素 OSSemDbgListPtr->DbgPrevPtr = p_sem; //原来的首元素的前面变为了该信号量 } OSSemDbgListPtr = p_sem; //该信号量成为列表的新首元素 }
OSSemPost ()
OSSemPost () 函数用于发布多值信号量。OSSemPost () 函数的信息如下表所示。
OSSemPost () 函数的定义也位于“os_sem.c”:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
OS_SEM_CTR OSSemPost (OS_SEM *p_sem, //多值信号量控制块指针 OS_OPT opt, //选项 OS_ERR *p_err) //返回错误类型 { OS_SEM_CTR ctr; CPU_TS ts; #ifdef OS_SAFETY_CRITICAL //如果使能(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) { //如果错误类型实参为空 OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return ((OS_SEM_CTR)0); //返回0(有错误),不继续执行 } #endif #if OS_CFG_ARG_CHK_EN > 0u //如果使能(默认使能)了参数检测功能 if (p_sem == (OS_SEM *)0) { //如果 p_sem 为空 *p_err = OS_ERR_OBJ_PTR_NULL; //返回错误类型为“内核对象指针为空” return ((OS_SEM_CTR)0); //返回0(有错误),不继续执行 } switch (opt) { //根据选项情况分类处理 case OS_OPT_POST_1: //如果选项在预期内,不处理 case OS_OPT_POST_ALL: case OS_OPT_POST_1 | OS_OPT_POST_NO_SCHED: case OS_OPT_POST_ALL | OS_OPT_POST_NO_SCHED: break; default: //如果选项超出预期 *p_err = OS_ERR_OPT_INVALID; //返回错误类型为“选项非法” return ((OS_SEM_CTR)0u); //返回0(有错误),不继续执行 } #endif #if OS_CFG_OBJ_TYPE_CHK_EN > 0u //如果使能了对象类型检测 if (p_sem->Type != OS_OBJ_TYPE_SEM) { //如果 p_sem 的类型不是多值信号量类型 *p_err = OS_ERR_OBJ_TYPE; //返回错误类型为“对象类型错误” return ((OS_SEM_CTR)0); //返回0(有错误),不继续执行 } #endif ts = OS_TS_GET(); //获取时间戳 #if OS_CFG_ISR_POST_DEFERRED_EN > 0u //如果使能了中断延迟发布 if (OSIntNestingCtr > (OS_NESTING_CTR)0) { //如果该函数是在中断中被调用 OS_IntQPost((OS_OBJ_TYPE)OS_OBJ_TYPE_SEM, //将该信号量发布到中断消息队列 (void *)p_sem, (void *)0, (OS_MSG_SIZE)0, (OS_FLAGS )0, (OS_OPT )opt, (CPU_TS )ts, (OS_ERR *)p_err); return ((OS_SEM_CTR)0); //返回0(尚未发布),不继续执行 } #endif ctr = OS_SemPost(p_sem, //将信号量按照普通方式处理 opt, ts, p_err); return (ctr); //返回信号的当前计数值 }
其实,不管是否使能了中断延迟发布,最终都是调用 OS_SemPost() 函数进行发布信号。只是使能了中断延迟发布的发布过程会比较曲折,中间会有许多插曲,这是中断管理范畴的内容,留到后面再作介绍。
OS_SemPost() 函数的定义位于“os_sem.c”:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
OS_SEM_CTR OS_SemPost (OS_SEM *p_sem, //多值信号量指针 OS_OPT opt, //选项 CPU_TS ts, //时间戳 OS_ERR *p_err) //返回错误类型 { OS_OBJ_QTY cnt; OS_SEM_CTR ctr; OS_PEND_LIST *p_pend_list; OS_PEND_DATA *p_pend_data; OS_PEND_DATA *p_pend_data_next; OS_TCB *p_tcb; CPU_SR_ALLOC(); CPU_CRITICAL_ENTER(); //关中断 p_pend_list = &p_sem->PendList; //取出该信号量的等待列表 if (p_pend_list->NbrEntries == (OS_OBJ_QTY)0) { //如果没有任务在等待该信号量 switch (sizeof(OS_SEM_CTR)) { //判断是否将导致该信号量计数值溢出, case 1u: //如果溢出,则开中断,返回错误类型为 if (p_sem->Ctr == DEF_INT_08U_MAX_VAL) { //“计数值溢出”,返回0(有错误), CPU_CRITICAL_EXIT(); //不继续执行。 *p_err = OS_ERR_SEM_OVF; return ((OS_SEM_CTR)0); } break; case 2u: if (p_sem->Ctr == DEF_INT_16U_MAX_VAL) { CPU_CRITICAL_EXIT(); *p_err = OS_ERR_SEM_OVF; return ((OS_SEM_CTR)0); } break; case 4u: if (p_sem->Ctr == DEF_INT_32U_MAX_VAL) { CPU_CRITICAL_EXIT(); *p_err = OS_ERR_SEM_OVF; return ((OS_SEM_CTR)0); } break; default: break; } p_sem->Ctr++; //信号量计数值不溢出则加1 ctr = p_sem->Ctr; //获取信号量计数值到 ctr p_sem->TS = ts; //保存时间戳 CPU_CRITICAL_EXIT(); //则开中断 *p_err = OS_ERR_NONE; //返回错误类型为“无错误” return (ctr); //返回信号量的计数值,不继续执行 } OS_CRITICAL_ENTER_CPU_EXIT(); //加锁调度器,但开中断 if ((opt & OS_OPT_POST_ALL) != (OS_OPT)0) { //如果要将信号量发布给所有等待任务 cnt = p_pend_list->NbrEntries; //获取等待任务数目到 cnt } else { //如果要将信号量发布给优先级最高的等待任务 cnt = (OS_OBJ_QTY)1; //将要操作的任务数为1,cnt 置1 } p_pend_data = p_pend_list->HeadPtr; //获取等待列表的首个任务到 p_pend_data while (cnt > 0u) { //逐个处理要发布的任务 p_tcb = p_pend_data->TCBPtr; //取出当前任务 p_pend_data_next = p_pend_data->NextPtr; //取出下一个任务 OS_Post((OS_PEND_OBJ *)((void *)p_sem), //发布信号量给当前任务 p_tcb, (void *)0, (OS_MSG_SIZE)0, ts); p_pend_data = p_pend_data_next; //处理下一个任务 cnt--; } ctr = p_sem->Ctr; //获取信号量计数值到 ctr OS_CRITICAL_EXIT_NO_SCHED(); //减锁调度器,但不执行任务调度 if ((opt & OS_OPT_POST_NO_SCHED) == (OS_OPT)0) { //如果 opt 没选择“发布时不调度任务” OSSched(); //任务调度 } *p_err = OS_ERR_NONE; //返回错误类型为“无错误” return (ctr); //返回信号量的当前计数值 }
当没有任务正在等待当发布的多值信号量,多值信号量的资源计数变量 Ctr 就会加 1,如果有任务正在等待要发布的多值信号量,那么多值信号量的资源计数变量 Ctr 就不加 1,而是直接将信号量发布给正在等待的任务,解除它们的等待状态。如果选项 opt 选择了OS_OPT_POST_1 , 就 只 解 除 所 有 等 待 任 务 中 优 先 级 最 高 的 那 个 任 务 ;如 果 选 择 了OS_OPT_POST_ALL,就解除所有等待任务。在插入等待任务时,如果等待列表中已有同一优先级的任务,会将该任务插在这些同优先级任务的后面。也就是说,如果选项 opt 选择了OS_OPT_POST_1,就只解除优先级最高任务中最早插入的那个任务。
在 OS_SemPost() 函数中,又会调用 OS_Post() 函数发布内核对象。OS_Post() 函数是一个底层的发布函数,它不仅仅用来发布多值信号量,还可以发布互斥信号量、消息队列、事件标志组、任务消息队列和任务信号量。
OS_Post() 函数的定义位于“os_core.c”:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
void OS_Post (OS_PEND_OBJ *p_obj, //内核对象类型指针 OS_TCB *p_tcb, //任务控制块 void *p_void, //消息 OS_MSG_SIZE msg_size, //消息大小 CPU_TS ts) //时间戳 { switch (p_tcb->TaskState) { //根据任务状态分类处理 case OS_TASK_STATE_RDY: //如果任务处于就绪状态 case OS_TASK_STATE_DLY: //如果任务处于延时状态 case OS_TASK_STATE_SUSPENDED: //如果任务处于挂起状态 case OS_TASK_STATE_DLY_SUSPENDED: //如果任务处于延时中被挂起状态 break; //不用处理,直接跳出 case OS_TASK_STATE_PEND: //如果任务处于无期限等待状态 case OS_TASK_STATE_PEND_TIMEOUT: //如果任务处于有期限等待状态 if (p_tcb->PendOn == OS_TASK_PEND_ON_MULTI) { //如果任务在等待多个信号量或消息队列 OS_Post1(p_obj, //标记哪个内核对象被发布 p_tcb, p_void, msg_size, ts); } else { //如果任务不是在等待多个信号量或消息队列 #if (OS_MSG_EN > 0u) //如果使能了任务队列或消息队列 p_tcb->MsgPtr = p_void; //保存消息到等待任务 p_tcb->MsgSize = msg_size; #endif p_tcb->TS = ts; //保存时间戳到等待任务 } if (p_obj != (OS_PEND_OBJ *)0) { //如果内核对象为空 OS_PendListRemove(p_tcb); //从等待列表移除该等待任务 #if OS_CFG_DBG_EN > 0u //如果使能了调试代码和变量 OS_PendDbgNameRemove(p_obj, //移除内核对象的调试名 p_tcb); #endif } OS_TaskRdy(p_tcb); //让该等待任务准备运行 p_tcb->TaskState = OS_TASK_STATE_RDY; //任务状态改为就绪状态 p_tcb->PendStatus = OS_STATUS_PEND_OK; //清除等待状态 p_tcb->PendOn = OS_TASK_PEND_ON_NOTHING; //标记不再等待 break; case OS_TASK_STATE_PEND_SUSPENDED: //如果任务在无期限等待中被挂起 case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED: //如果任务在有期限等待中被挂起 if (p_tcb->PendOn == OS_TASK_PEND_ON_MULTI) { //如果任务在等待多个信号量或消息队列 OS_Post1(p_obj, //标记哪个内核对象被发布 p_tcb, p_void, msg_size, ts); } else { //如果任务不在等待多个信号量或消息队列 #if (OS_MSG_EN > 0u) //如果使能了调试代码和变量 p_tcb->MsgPtr = p_void; //保存消息到等待任务 p_tcb->MsgSize = msg_size; #endif p_tcb->TS = ts; //保存时间戳到等待任务 } OS_TickListRemove(p_tcb); //从节拍列表移除该等待任务 if (p_obj != (OS_PEND_OBJ *)0) { //如果内核对象为空 OS_PendListRemove(p_tcb); //从等待列表移除该等待任务 #if OS_CFG_DBG_EN > 0u //如果使能了调试代码和变量 OS_PendDbgNameRemove(p_obj, //移除内核对象的调试名 p_tcb); #endif } p_tcb->TaskState = OS_TASK_STATE_SUSPENDED; //任务状态改为被挂起状态 p_tcb->PendStatus = OS_STATUS_PEND_OK; //清除等待状态 p_tcb->PendOn = OS_TASK_PEND_ON_NOTHING; //标记不再等待 break; default: //如果任务状态超出预期 break; //直接跳出 } }
OSSemPend ()
与 OSSemPost () 多值信号量发布函数相对应,OSSemPend() 函数用于等待一个多值信号量。
OSSemPend() 函数的定义也位于“os_sem.c”:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
OS_SEM_CTR OSSemPend (OS_SEM *p_sem, //多值信号量指针 OS_TICK timeout, //等待超时时间 OS_OPT opt, //选项 CPU_TS *p_ts, //等到信号量时的时间戳 OS_ERR *p_err) //返回错误类型 { OS_SEM_CTR ctr; OS_PEND_DATA pend_data; CPU_SR_ALLOC(); #ifdef OS_SAFETY_CRITICAL //如果使能(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) { //如果错误类型实参为空 OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return ((OS_SEM_CTR)0); //返回0(有错误),不继续执行 } #endif #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u //如果使能了中断中非法调用检测 if (OSIntNestingCtr > (OS_NESTING_CTR)0) { //如果该函数在中断中被调用 *p_err = OS_ERR_PEND_ISR; //返回错误类型为“在中断中等待” return ((OS_SEM_CTR)0); //返回0(有错误),不继续执行 } #endif #if OS_CFG_ARG_CHK_EN > 0u //如果使能了参数检测 if (p_sem == (OS_SEM *)0) { //如果 p_sem 为空 *p_err = OS_ERR_OBJ_PTR_NULL; //返回错误类型为“内核对象为空” return ((OS_SEM_CTR)0); //返回0(有错误),不继续执行 } switch (opt) { //根据选项分类处理 case OS_OPT_PEND_BLOCKING: //如果选择“等待不到对象进行堵塞” case OS_OPT_PEND_NON_BLOCKING: //如果选择“等待不到对象不进行堵塞” break; //直接跳出,不处理 default: //如果选项超出预期 *p_err = OS_ERR_OPT_INVALID; //返回错误类型为“选项非法” return ((OS_SEM_CTR)0); //返回0(有错误),不继续执行 } #endif #if OS_CFG_OBJ_TYPE_CHK_EN > 0u //如果使能了对象类型检测 if (p_sem->Type != OS_OBJ_TYPE_SEM) { //如果 p_sem 不是多值信号量类型 *p_err = OS_ERR_OBJ_TYPE; //返回错误类型为“内核对象类型错误” return ((OS_SEM_CTR)0); //返回0(有错误),不继续执行 } #endif if (p_ts != (CPU_TS *)0) { //如果 p_ts 非空 *p_ts = (CPU_TS)0; //初始化(清零)p_ts,待用于返回时间戳 } CPU_CRITICAL_ENTER(); //关中断 if (p_sem->Ctr > (OS_SEM_CTR)0) { //如果资源可用 p_sem->Ctr--; //资源数目减1 if (p_ts != (CPU_TS *)0) { //如果 p_ts 非空 *p_ts = p_sem->TS; //获取该信号量最后一次发布的时间戳 } ctr = p_sem->Ctr; //获取信号量的当前资源数目 CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_NONE; //返回错误类型为“无错误” return (ctr); //返回信号量的当前资源数目,不继续执行 } if ((opt & OS_OPT_PEND_NON_BLOCKING) != (OS_OPT)0) { //如果没有资源可用,而且选择了不堵塞任务 ctr = p_sem->Ctr; //获取信号量的资源数目到 ctr CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_PEND_WOULD_BLOCK; //返回错误类型为“等待渴求堵塞” return (ctr); //返回信号量的当前资源数目,不继续执行 } else { //如果没有资源可用,但选择了堵塞任务 if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) { //如果调度器被锁 CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_SCHED_LOCKED; //返回错误类型为“调度器被锁” return ((OS_SEM_CTR)0); //返回0(有错误),不继续执行 } } OS_CRITICAL_ENTER_CPU_EXIT(); //锁调度器,并重开中断 OS_Pend(&pend_data, //堵塞等待任务,将当前任务脱离就绪列表, (OS_PEND_OBJ *)((void *)p_sem), //并插入到节拍列表和等待列表。 OS_TASK_PEND_ON_SEM, timeout); OS_CRITICAL_EXIT_NO_SCHED(); //开调度器,但不进行调度 OSSched(); //找到并调度最高优先级就绪任务 /* 当前任务(获得信号量)得以继续运行 */ CPU_CRITICAL_ENTER(); //关中断 switch (OSTCBCurPtr->PendStatus) { //根据当前运行任务的等待状态分类处理 case OS_STATUS_PEND_OK: //如果等待状态正常 if (p_ts != (CPU_TS *)0) { //如果 p_ts 非空 *p_ts = OSTCBCurPtr->TS; //获取信号被发布的时间戳 } *p_err = OS_ERR_NONE; //返回错误类型为“无错误” break; case OS_STATUS_PEND_ABORT: //如果等待被终止中止 if (p_ts != (CPU_TS *)0) { //如果 p_ts 非空 *p_ts = OSTCBCurPtr->TS; //获取等待被中止的时间戳 } *p_err = OS_ERR_PEND_ABORT; //返回错误类型为“等待被中止” break; case OS_STATUS_PEND_TIMEOUT: //如果等待超时 if (p_ts != (CPU_TS *)0) { //如果 p_ts 非空 *p_ts = (CPU_TS )0; //清零 p_ts } *p_err = OS_ERR_TIMEOUT; //返回错误类型为“等待超时” break; case OS_STATUS_PEND_DEL: //如果等待的内核对象被删除 if (p_ts != (CPU_TS *)0) { //如果 p_ts 非空 *p_ts = OSTCBCurPtr->TS; //获取内核对象被删除的时间戳 } *p_err = OS_ERR_OBJ_DEL; //返回错误类型为“等待对象被删除” break; default: //如果等待状态超出预期 *p_err = OS_ERR_STATUS_INVALID; //返回错误类型为“等待状态非法” CPU_CRITICAL_EXIT(); //开中断 return ((OS_SEM_CTR)0); //返回0(有错误),不继续执行 } ctr = p_sem->Ctr; //获取信号量的当前资源数目 CPU_CRITICAL_EXIT(); //开中断 return (ctr); //返回信号量的当前资源数目 }
OSSemPend() 函数会调用一个更加底层的等待函数来执行当前任务对多值信号量的等待,该函数就是 OS_Pend()。与 OS_Post() 函数一样,OS_Pend() 函数不仅仅用来等待多值信号量,还可以等待互斥信号量、消息队列、事件标志组、任务消息队列和任务信号量。
OS_Pend()函数的定义位于“os_core.c” :
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
void OS_Pend (OS_PEND_DATA *p_pend_data, //待插入等待列表的元素 OS_PEND_OBJ *p_obj, //等待的内核对象 OS_STATE pending_on, //等待哪种对象内核 OS_TICK timeout) //等待期限 { OS_PEND_LIST *p_pend_list; OSTCBCurPtr->PendOn = pending_on; //资源不可用,开始等待 OSTCBCurPtr->PendStatus = OS_STATUS_PEND_OK; //正常等待中 OS_TaskBlock(OSTCBCurPtr, //阻塞当前运行任务, timeout); //如果 timeout 非0,把任务插入的节拍列表 if (p_obj != (OS_PEND_OBJ *)0) { //如果等待对象非空 p_pend_list = &p_obj->PendList; //获取对象的等待列表到 p_pend_list p_pend_data->PendObjPtr = p_obj; //保存要等待的对象 OS_PendDataInit((OS_TCB *)OSTCBCurPtr, //初始化 p_pend_data(待插入等待列表) (OS_PEND_DATA *)p_pend_data, (OS_OBJ_QTY )1); OS_PendListInsertPrio(p_pend_list, //按优先级将 p_pend_data 插入到等待列表 p_pend_data); } else { //如果等待对象为空 OSTCBCurPtr->PendDataTblEntries = (OS_OBJ_QTY )0; //清零当前任务的等待域数据 OSTCBCurPtr->PendDataTblPtr = (OS_PEND_DATA *)0; } #if OS_CFG_DBG_EN > 0u //如果使能了调试代码和变量 OS_PendDbgNameAdd(p_obj, //更新信号量的 DbgNamePtr 元素为其等待 OSTCBCurPtr); //列表中优先级最高的任务的名称。 #endif }
OSSemPendAbort ()
OSSemPendAbort() 函 数 用 于 中 止 任 务 对 一 个 多 值 信 号 量 的 等 待 。 要 使 用OSSemPendAbort () 函数,还得事先使能 OS_CFG_SEM_PEND_ABORT_EN(位于“os_cfg.h”)
#define OS_CFG_SEM_PEND_ABORT_EN 1u //使能或禁用 OSSemPendAbort() 函数
OSSemPendAbort () 函数的信息如下表所示。
OSSemPendAbort () 函数的定义位于“os_sem.c”:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#if OS_CFG_SEM_PEND_ABORT_EN > 0u //如果使能了 OSSemPendAbort() 函数 OS_OBJ_QTY OSSemPendAbort (OS_SEM *p_sem, //多值信号量指针 OS_OPT opt, //选项 OS_ERR *p_err) //返回错误类型 { OS_PEND_LIST *p_pend_list; OS_TCB *p_tcb; CPU_TS ts; OS_OBJ_QTY nbr_tasks; CPU_SR_ALLOC(); #ifdef OS_SAFETY_CRITICAL //如果使能(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) { //如果错误类型实参为空 OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return ((OS_OBJ_QTY)0u); //返回0(有错误),不继续执行 } #endif #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u //如果使能了中断中非法调用检测 if (OSIntNestingCtr > (OS_NESTING_CTR)0u) { //如果该函数在中断中被调用 *p_err = OS_ERR_PEND_ABORT_ISR; //返回错误类型为“在中断中中止等待” return ((OS_OBJ_QTY)0u); //返回0(有错误),不继续执行 } #endif #if OS_CFG_ARG_CHK_EN > 0u //如果使能了参数检测 if (p_sem == (OS_SEM *)0) { //如果 p_sem 为空 *p_err = OS_ERR_OBJ_PTR_NULL; //返回错误类型为“内核对象为空” return ((OS_OBJ_QTY)0u); //返回0(有错误),不继续执行 } switch (opt) { //根据选项分类处理 case OS_OPT_PEND_ABORT_1: //如果选项在预期内 case OS_OPT_PEND_ABORT_ALL: case OS_OPT_PEND_ABORT_1 | OS_OPT_POST_NO_SCHED: case OS_OPT_PEND_ABORT_ALL | OS_OPT_POST_NO_SCHED: break; //不处理,直接跳出 default: //如果选项超出预期 *p_err = OS_ERR_OPT_INVALID; //返回错误类型为“选项非法” return ((OS_OBJ_QTY)0u); //返回0(有错误),不继续执行 } #endif #if OS_CFG_OBJ_TYPE_CHK_EN > 0u //如果使能了对象类型检测 if (p_sem->Type != OS_OBJ_TYPE_SEM) { //如果 p_sem 不是多值信号量类型 *p_err = OS_ERR_OBJ_TYPE; //返回错误类型为“内核对象类型错误” return ((OS_OBJ_QTY)0u); //返回0(有错误),不继续执行 } #endif CPU_CRITICAL_ENTER(); //关中断 p_pend_list = &p_sem->PendList; //获取 p_sem 的等待列表到 p_pend_list if (p_pend_list->NbrEntries == (OS_OBJ_QTY)0u) { //如果没有任务在等待 p_sem CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_PEND_ABORT_NONE; //返回错误类型为“没有任务在等待” return ((OS_OBJ_QTY)0u); //返回0(有错误),不继续执行 } OS_CRITICAL_ENTER_CPU_EXIT(); //加锁调度器,并开中断 nbr_tasks = 0u; ts = OS_TS_GET(); //获取时间戳 while (p_pend_list->NbrEntries > (OS_OBJ_QTY)0u) { //如果有任务在等待 p_sem p_tcb = p_pend_list->HeadPtr->TCBPtr; //获取优先级最高的等待任务 OS_PendAbort((OS_PEND_OBJ *)((void *)p_sem), //中止该任务对 p_sem 的等待 p_tcb, ts); nbr_tasks++; if (opt != OS_OPT_PEND_ABORT_ALL) { //如果不是选择了中止所有等待任务 break; //立即跳出,不再继续中止 } } OS_CRITICAL_EXIT_NO_SCHED(); //减锁调度器,但不调度 if ((opt & OS_OPT_POST_NO_SCHED) == (OS_OPT)0u) { //如果选择了任务调度 OSSched(); //进行任务调度 } *p_err = OS_ERR_NONE; //返回错误类型为“无错误” return (nbr_tasks); //返回被中止的任务数目 } #endif
OSSemPendAbort () 函数会调用一个更加底层的中止等待函数来执行当前任务对多值信号量的等待,该函数就是 OS_PendAbort()。OS_PendAbort() 函数不仅仅用来中止对多值信号量的等待,还可以中止对互斥信号量、消息队列、事件标志组、任务消息队列或任务信号量的等待。
OS_PendAbort() 函数的定义位于“os_core.c”:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
void OS_PendAbort (OS_PEND_OBJ *p_obj, //被等待对象的类型 OS_TCB *p_tcb, //任务控制块指针 CPU_TS ts) //等待被中止时的时间戳 { switch (p_tcb->TaskState) { //根据任务状态分类处理 case OS_TASK_STATE_RDY: //如果任务是就绪状态 case OS_TASK_STATE_DLY: //如果任务是延时状态 case OS_TASK_STATE_SUSPENDED: //如果任务是挂起状态 case OS_TASK_STATE_DLY_SUSPENDED: //如果任务是在延时中被挂起 break; //这些情况均与等待无关,直接跳出 case OS_TASK_STATE_PEND: //如果任务是无期限等待状态 case OS_TASK_STATE_PEND_TIMEOUT: //如果任务是有期限等待状态 if (p_tcb->PendOn == OS_TASK_PEND_ON_MULTI) { //如果任务在等待多个信号量或消息队列 OS_PendAbort1(p_obj, //强制解除任务对某一对象的等待 p_tcb, ts); } #if (OS_MSG_EN > 0u) //如果使能了任务队列或消息队列 p_tcb->MsgPtr = (void *)0; //清除(复位)任务的消息域 p_tcb->MsgSize = (OS_MSG_SIZE)0u; #endif p_tcb->TS = ts; //保存等待被中止时的时间戳到任务控制块 if (p_obj != (OS_PEND_OBJ *)0) { //如果等待对象非空 OS_PendListRemove(p_tcb); //将任务从所有等待列表中移除 } OS_TaskRdy(p_tcb); //让任务进准备运行 p_tcb->TaskState = OS_TASK_STATE_RDY; //修改任务状态为就绪状态 p_tcb->PendStatus = OS_STATUS_PEND_ABORT; //标记任务的等待被中止 p_tcb->PendOn = OS_TASK_PEND_ON_NOTHING; //标记任务目前没有等待任何对象 break; //跳出 case OS_TASK_STATE_PEND_SUSPENDED: //如果任务在无期限等待中被挂起 case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED: //如果任务在有期限等待中被挂起 if (p_tcb->PendOn == OS_TASK_PEND_ON_MULTI) { //如果任务在等待多个信号量或消息队列 OS_PendAbort1(p_obj, //强制解除任务对某一对象的等待 p_tcb, ts); } #if (OS_MSG_EN > 0u) //如果使能了任务队列或消息队列 p_tcb->MsgPtr = (void *)0; //清除(复位)任务的消息域 p_tcb->MsgSize = (OS_MSG_SIZE)0u; #endif p_tcb->TS = ts; //保存等待被中止时的时间戳到任务控制块 if (p_obj != (OS_PEND_OBJ *)0) { //如果等待对象非空 OS_PendListRemove(p_tcb); //将任务从所有等待列表中移除 } OS_TickListRemove(p_tcb); //让任务脱离节拍列表 p_tcb->TaskState = OS_TASK_STATE_SUSPENDED; //修改任务状态为挂起状态 p_tcb->PendStatus = OS_STATUS_PEND_ABORT; //标记任务的等待被中止 p_tcb->PendOn = OS_TASK_PEND_ON_NOTHING; //标记任务目前没有等待任何对象 break; //跳出 default: //如果任务状态超出预期 break; //不需处理,直接跳出 } }
OSSemDel()
OSSemDel () 函数用于删除一个多值信号量。要使用 OSSemDel () 函数,还得事先使能OS_CFG_SEM_DEL_EN(位于“os_cfg.h”),
#define OS_CFG_SEM_DEL_EN 1u //使能或禁用 OSSemDel() 函数
OSSemDel () 函数的信息如下表所示。
OSSemDel () 函数的定义位于“os_sem.c”:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#if OS_CFG_SEM_DEL_EN > 0u //如果使能了 OSSemDel() 函数 OS_OBJ_QTY OSSemDel (OS_SEM *p_sem, //多值信号量指针 OS_OPT opt, //选项 OS_ERR *p_err) //返回错误类型 { OS_OBJ_QTY cnt; OS_OBJ_QTY nbr_tasks; OS_PEND_DATA *p_pend_data; OS_PEND_LIST *p_pend_list; OS_TCB *p_tcb; CPU_TS ts; CPU_SR_ALLOC(); #ifdef OS_SAFETY_CRITICAL //如果使能(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) { //如果错误类型实参为空 OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return ((OS_OBJ_QTY)0); //返回0(有错误),不继续执行 } #endif #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u //如果使能了中断中非法调用检测 if (OSIntNestingCtr > (OS_NESTING_CTR)0) { //如果该函数在中断中被调用 *p_err = OS_ERR_DEL_ISR; //返回错误类型为“在中断中删除” return ((OS_OBJ_QTY)0); //返回0(有错误),不继续执行 } #endif #if OS_CFG_ARG_CHK_EN > 0u //如果使能了参数检测 if (p_sem == (OS_SEM *)0) { //如果 p_sem 为空 *p_err = OS_ERR_OBJ_PTR_NULL; //返回错误类型为“内核对象为空” return ((OS_OBJ_QTY)0); //返回0(有错误),不继续执行 } switch (opt) { //根据选项分类处理 case OS_OPT_DEL_NO_PEND: //如果选项在预期之内 case OS_OPT_DEL_ALWAYS: break; //直接跳出 default: //如果选项超出预期 *p_err = OS_ERR_OPT_INVALID; //返回错误类型为“选项非法” return ((OS_OBJ_QTY)0); //返回0(有错误),不继续执行 } #endif #if OS_CFG_OBJ_TYPE_CHK_EN > 0u //如果使能了对象类型检测 if (p_sem->Type != OS_OBJ_TYPE_SEM) { //如果 p_sem 不是多值信号量类型 *p_err = OS_ERR_OBJ_TYPE; //返回错误类型为“内核对象类型错误” return ((OS_OBJ_QTY)0); //返回0(有错误),不继续执行 } #endif CPU_CRITICAL_ENTER(); //关中断 p_pend_list = &p_sem->PendList; //获取信号量的等待列表到 p_pend_list cnt = p_pend_list->NbrEntries; //获取等待该信号量的任务数 nbr_tasks = cnt; switch (opt) { //根据选项分类处理 case OS_OPT_DEL_NO_PEND: //如果只在没有任务等待的情况下删除信号量 if (nbr_tasks == (OS_OBJ_QTY)0) { //如果没有任务在等待该信号量 #if OS_CFG_DBG_EN > 0u //如果使能了调试代码和变量 OS_SemDbgListRemove(p_sem); //将该信号量从信号量调试列表移除 #endif OSSemQty--; //信号量数目减1 OS_SemClr(p_sem); //清除信号量内容 CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_NONE; //返回错误类型为“无错误” } else { //如果有任务在等待该信号量 CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_TASK_WAITING; //返回错误类型为“有任务在等待该信号量” } break; case OS_OPT_DEL_ALWAYS: //如果必须删除信号量 OS_CRITICAL_ENTER_CPU_EXIT(); //锁调度器,并开中断 ts = OS_TS_GET(); //获取时间戳 while (cnt > 0u) { //逐个移除该信号量等待列表中的任务 p_pend_data = p_pend_list->HeadPtr; p_tcb = p_pend_data->TCBPtr; OS_PendObjDel((OS_PEND_OBJ *)((void *)p_sem), p_tcb, ts); cnt--; } #if OS_CFG_DBG_EN > 0u //如果使能了调试代码和变量 OS_SemDbgListRemove(p_sem); //将该信号量从信号量调试列表移除 #endif OSSemQty--; //信号量数目减1 OS_SemClr(p_sem); //清除信号量内容 OS_CRITICAL_EXIT_NO_SCHED(); //减锁调度器,但不进行调度 OSSched(); //任务调度,执行最高优先级的就绪任务 *p_err = OS_ERR_NONE; //返回错误类型为“无错误” break; default: //如果选项超出预期 CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_OPT_INVALID; //返回错误类型为“选项非法” break; } return ((OS_OBJ_QTY)nbr_tasks); //返回删除信号量前等待其的任务数 } #endif
OSSemDel () 函数会调用一个更加底层的删除等待对象的函数来执行对互斥信号量的删除,该函数就是 OS_PendObjDel ()。OS_PendObjDel () 函数不仅仅用来删除互斥信号量,还可以删除多值信号量、消息队列、事件标志组、任务消息队列或任务信号量。
OS_PendObjDel() 函数的定义位于“os_core.c”:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
void OS_PendObjDel (OS_PEND_OBJ *p_obj, //被删除对象的类型 OS_TCB *p_tcb, //任务控制块指针 CPU_TS ts) //信号量被删除时的时间戳 { switch (p_tcb->TaskState) { //根据任务状态分类处理 case OS_TASK_STATE_RDY: //如果任务是就绪状态 case OS_TASK_STATE_DLY: //如果任务是延时状态 case OS_TASK_STATE_SUSPENDED: //如果任务是挂起状态 case OS_TASK_STATE_DLY_SUSPENDED: //如果任务是在延时中被挂起 break; //这些情况均与等待无关,直接跳出 case OS_TASK_STATE_PEND: //如果任务是无期限等待状态 case OS_TASK_STATE_PEND_TIMEOUT: //如果任务是有期限等待状态 if (p_tcb->PendOn == OS_TASK_PEND_ON_MULTI) { //如果任务在等待多个信号量或消息队列 OS_PendObjDel1(p_obj, //强制解除任务对某一对象的等待 p_tcb, ts); } #if (OS_MSG_EN > 0u) //如果使能了任务队列或消息队列 p_tcb->MsgPtr = (void *)0; //清除(复位)任务的消息域 p_tcb->MsgSize = (OS_MSG_SIZE)0u; #endif p_tcb->TS = ts; //保存等待被中止时的时间戳到任务控制块 OS_PendListRemove(p_tcb); //将任务从所有等待列表中移除 OS_TaskRdy(p_tcb); //让任务进准备运行 p_tcb->TaskState = OS_TASK_STATE_RDY; //修改任务状态为就绪状态 p_tcb->PendStatus = OS_STATUS_PEND_DEL; //标记任务的等待对象被删除 p_tcb->PendOn = OS_TASK_PEND_ON_NOTHING; //标记任务目前没有等待任何对象 break; //跳出 case OS_TASK_STATE_PEND_SUSPENDED: //如果任务在无期限等待中被挂起 case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED: //如果任务在有期限等待中被挂起 if (p_tcb->PendOn == OS_TASK_PEND_ON_MULTI) {//如果任务在等待多个信号量或消息队列 OS_PendObjDel1(p_obj, //强制解除任务对某一对象的等待 p_tcb, ts); } #if (OS_MSG_EN > 0u) //如果使能了任务队列或消息队列 p_tcb->MsgPtr = (void *)0; //清除(复位)任务的消息域 p_tcb->MsgSize = (OS_MSG_SIZE)0u; #endif p_tcb->TS = ts; //保存等待被中止时的时间戳到任务控制块 OS_TickListRemove(p_tcb); //让任务脱离节拍列表 OS_PendListRemove(p_tcb); //将任务从所有等待列表中移除 p_tcb->TaskState = OS_TASK_STATE_SUSPENDED; //修改任务状态为挂起状态 p_tcb->PendStatus = OS_STATUS_PEND_DEL; //标记任务的等待对象被删除 p_tcb->PendOn = OS_TASK_PEND_ON_NOTHING; //标记任务目前没有等待任何对象 break; //跳出 default: //如果任务状态超出预期 break; //不需处理,直接跳出 } }
OSSemSet()
OSSemSet () 函数用于设置多值信号量的计数值。要使用 OSSemSet () 函数,还得事先使能 OS_CFG_SEM_SET_EN(位于“os_cfg.h”),
#define OS_CFG_SEM_SET_EN 1u //使能或禁用 OSSemSet() 函数
OSSemSet () 函数的信息如下表所示。
OSSemSet () 函数的定义位于“os_sem.c”:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#if OS_CFG_SEM_SET_EN > 0u //如果使能 OSSemSet() 函数 void OSSemSet (OS_SEM *p_sem, //多值信号量指针 OS_SEM_CTR cnt, //信号量计数值 OS_ERR *p_err) //返回错误类型 { OS_PEND_LIST *p_pend_list; CPU_SR_ALLOC(); #ifdef OS_SAFETY_CRITICAL //如果使能(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) { //如果错误类型实参为空 OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return; //返回0(有错误),不继续执行 } #endif #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u //如果使能了中断中非法调用检测 if (OSIntNestingCtr > (OS_NESTING_CTR)0) { //如果该函数在中断中被调用 *p_err = OS_ERR_SET_ISR; //返回错误类型为“在中断中设置” return; //返回0(有错误),不继续执行 } #endif #if OS_CFG_ARG_CHK_EN > 0u //如果使能了参数检测 if (p_sem == (OS_SEM *)0) { //如果 p_sem 为空 *p_err = OS_ERR_OBJ_PTR_NULL; //返回错误类型为“内核对象为空” return; //返回0(有错误),不继续执行 } #endif #if OS_CFG_OBJ_TYPE_CHK_EN > 0u //如果使能了对象类型检测 if (p_sem->Type != OS_OBJ_TYPE_SEM) { //如果 p_sem 不是多值信号量类型 *p_err = OS_ERR_OBJ_TYPE; //返回错误类型为“内核对象类型错误” return; //返回0(有错误),不继续执行 } #endif *p_err = OS_ERR_NONE; //返回错误类型为“无错误” CPU_CRITICAL_ENTER(); //关中断 if (p_sem->Ctr > (OS_SEM_CTR)0) { //如果信号量计数值>0,说明没 p_sem->Ctr = cnt; //被等待可以直接设置计数值。 } else { //如果信号量计数值=0 p_pend_list = &p_sem->PendList; //获取信号量的等待列表 if (p_pend_list->NbrEntries == (OS_OBJ_QTY)0) {//如果没任务在等待信号量 p_sem->Ctr = cnt; //可以直接设置信号计数值 } else { //如果有任务在等待信号量 *p_err = OS_ERR_TASK_WAITING; //返回错误类型为“有任务等待” } } CPU_CRITICAL_EXIT(); } #endif