信号量在资源互斥共享 任务同步与通信中有着广泛的应用。信号量也是事件中的种,信号量的函数实现在ucos的os_sem.c文件中。
信号量是一个非负整数,当请求一个使用信号量来表示的资源时,进程需要先读取信号量的值来判断资源是否可用。大于0,资源可以请求,等于0,无资源可用,进程会进入睡眠状态直至资源可用。
当进程不再使用一个信号量控制的共享资源时,信号量的值+1,当进程成功请求到一个信号的时候,信号量的值就减一
关于信号量的函数,主要包括下8个函数,
1:信号创建函数OSSemCreate
信号创建函数的原型如下所示
OS_EVENT *OSSemCreate (INT16U cnt);
在系统刚开始,初始化之后,所有的事件控制块(ECB)都为空闲事件控制块。创建信号函数就是从空闲事件控制块链表中去出一个控制块,进行配置,使其具备有信号量的属性。并返回该任务控制块的指针,若返回的是空指针,则创建不成功。
其函数主体如下,前面都是一些参数检查,不再赘述:
pevent = OSEventFreeList;
if (OSEventFreeList != (OS_EVENT *)0) { //看链表是否空
OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;}
if (pevent != (OS_EVENT *)0) {
pevent->OSEventType = OS_EVENT_TYPE_SEM;//定义事件类型为信号量
pevent->OSEventCnt = cnt; //设置信号量的值
pevent->OSEventPtr = (void *)0; //该ECB不在任何的链表中了,所以该指针指向空地址
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?"; //任务控制块名字
#endif
OS_EventWaitListInit(pevent); //初始化时间等待表和数组。(就是将所有元素清零,因为该ECB可能之前被调用过)
}
return (pevent);
2:请求信号函数OSSemPend
信号请求函数原型为:void OSSemPend (OS_EVENT *pevent, INT32U timeout, INT8U *perr);
该函数的三个参数为对应信号的ECB指针OS_EVENT *pevent ,等待超时时间timeout,返回结果放在指向整数的整形指针。
该函数的主要功能是请求信号量,当信号量大于可用的时候,成功返回,当信号量不大于0不可用的时候,任务会阻塞,直到有信号量可用或者是时间超时了为止,然后返回状态。
函数的前面都是一些常见的参数检查,其功能主体的部分代码如下:
if (pevent->OSEventCnt > 0u) { //信号量的值大于0的时候,说明资源可用
pevent->OSEventCnt--; //将信号量减1
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE; //申请成功,返回
return;
}
//下面是若是信号量的值不大于0
OSTCBCur->OSTCBStat |= OS_STAT_SEM; // 将申请信号量任务的任务控制块成员OSTCBStat添加等待信号量状态
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK; //任务控制块事件等待标志置位
OSTCBCur->OSTCBDly = timeout; //设置超时时间
OS_EventTaskWait(pevent); //设置事件等待,任务在ECB事件表和数组中登记,任务阻塞
OS_EXIT_CRITICAL();
OS_Sched(); //执行调度,运行其他任务
OS_ENTER_CRITICAL();
switch (OSTCBCur->OSTCBStatPend) { //当任务不在阻塞再次执行 ,检查任务控制块事件等待状态
case OS_STAT_PEND_OK: //等待事件发生了,解除阻塞状态
*perr = OS_ERR_NONE;
break;
case OS_STAT_PEND_ABORT:
break;
case OS_STAT_PEND_ABORT:
*perr = OS_ERR_PEND_ABORT; //因为异常,解除阻塞状态
break;
case OS_STAT_PEND_TO: //超时
default:
OS_EventTaskRemove(OSTCBCur, pevent); //取消等待事件,更新ECB的事件表和事件组
*perr = OS_ERR_TIMEOUT; //超时
break;
}
}
OSTCBCur->OSTCBStat = OS_STAT_RDY; //更新任务状态
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK; //更新等待事件状态
OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; //事件控制块指针清零
3:提交信号量OSSemPost
当任务在对资源完成访问之后,应该讲信号量进行加1,表示资源释放,同时若是有其他任务在等待该信号量,则应该将等待任务唤醒。
提交信号量函数就是实现这个功能。
其函数的原型为:INT8U OSSemPost(OS_EVENT *pevent);参数为信号量的事件控制块指针
除了前面的参数检查之外,函数的主题功能代码如下:
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0u) { //查看该是否有任务等待该信号量
(void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM, OS_STAT_PEND_OK); //因为有有一个信号量释放,所以可以将该等待该信号量中的一个任务唤醒,运行状态转为就绪
OS_EXIT_CRITICAL();
OS_Sched(); //执行调度,运行最高优先级任务
return (OS_ERR_NONE); //程序返回
}
if (pevent->OSEventCnt < 65535u) { //若是没有其他任务等待该信号量,若是有,执行前面的if语句并返回了
pevent->OSEventCnt++; //信号量计数加1
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
}
return (OS_ERR_NONE);
}
OS_EXIT_CRITICAL();
4:
查询信号量OSSemQuery
函数的原型如下:INT8U OSSemQuery (OS_EVENT *pevent,OS_SEM_DATA *p_sem_data);
该函数查询信号量的信息,并且将信息存入到p_sem_data指针指向的OS_SEM_DATA的结构体内。
其主要功能代码实现如下:
OS_ENTER_CRITICAL();
p_sem_data->OSEventGrp = pevent->OSEventGrp; //等待信号量表
psrc = &pevent->OSEventTbl[0];
pdest = &p_sem_data->OSEventTbl[0];
for (i = 0u; i < OS_EVENT_TBL_SIZE; i++) {
*pdest++ = *psrc++; //等待信号量数组
}
p_sem_data->OSCnt = pevent->OSEventCnt; //信号量计数
OS_EXIT_CRITICAL();
5:无等待请求信号量OSSemAccept
该函数的功能顾名思义知道,请求信号量,若是信号量可用,就使用信号量。若是没有,不阻塞,继续执行其他代码
其原型为:INT16U OSSemAccept (OS_EVENT *pevent);根据函数返回的结果时候大于或等于0,判断是否申请成功。
其功能代码实现如下:
OS_ENTER_CRITICAL();
cnt = pevent->OSEventCnt;
cnt = pevent->OSEventCnt;
if (cnt > 0u) {
pevent->OSEventCnt--;
}
OS_EXIT_CRITICAL();
6:放弃等待信号量OSSemPendAbort
放弃等待信号量,需要注意的是,肯定不会是请求信号量的任务请求放弃,因为请求信号量的任务在阻塞状态中,根本得不得执行。根本不可能自己去放弃等待,要放弃等待也是其他任务来执行。
其函数原型如下:INT8U OSSemPendAbort (OS_EVENT *pevent, INT8U opt, INT8U *perr)
参数opt有两种处理情况,OS_PEND_OPT_NONE和OS_PEND_OPT_BROADCAST
OS_PEND_OPT_NONE 将等待该信号量的最高优先级任务就绪,其他不变
OS_PEND_OPT_BROADCAST 将等待该信号量的所有任务就绪
其功能代码实现如下:
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0u) { //是否有等待该信号量的任务
nbr_tasks = 0u;
switch (opt) {
case OS_PEND_OPT_BROADCAST: //若是OS_PEND_OPT_BROADCAST情况
while (pevent->OSEventGrp != 0u) { //将所有等待信号的任务的运行状态转为就绪状态
(void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM, OS_STAT_PEND_ABORT);
nbr_tasks++;
}
break;
nbr_tasks++;
}
break;
case OS_PEND_OPT_NONE: //若是OS_PEND_OPT_NONE
default:
(void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM, OS_STAT_PEND_ABORT); //将等待信号的最高优先级任务转为就绪状态
nbr_tasks++;
break;
}
break;
}
OS_EXIT_CRITICAL();
OS_Sched(); //执行调度,运行最高优先级就绪任务
*perr = OS_ERR_PEND_ABORT;
return (nbr_tasks);
}
return (nbr_tasks);
}
OS_EXIT_CRITICAL();
7:信号量值得设置OSSemSet
该函数的主要功能就是重新设置信号量计数的值,在一般情况下,不需要使用该函数,因为在创建该函数的时候,已经对信号量的计数值进行过赋值。
该函数非常简单,只需要修改ECB的OSEventCnt的值。
函数原型为:void OSSemSet(OS_EVENT *pevent,INT16U cnt,INT8U *perr);其中pevent为事件控制块指针,cnt为需要设置的信号量计数值,返回结果放在指向整数的整形指针perr。
其主体功能代码如下:
OS_ENTER_CRITICAL();
*perr = OS_ERR_NONE;
if (pevent->OSEventCnt > 0u) { //信号量计数值是否大于0
pevent->OSEventCnt = cnt;
} else {
if (pevent->OSEventGrp == 0u) { //该信号量是否有任务等待?有的话修改不成功
pevent->OSEventCnt = cnt;
} else {
*perr = OS_ERR_TASK_WAITING;
}
}
*perr = OS_ERR_TASK_WAITING;
}
}
OS_EXIT_CRITICAL();
8:删除信号量OSSemDel
如果信号量不用就尽快删除,否则一直占着。而ECB控制块数量有限(默认为十个啊),很有可能不够用。
其函数原型如下:OS_EVENT *OSSemDel (OS_EVENT *pevent, INT8U opt, INT8U *perr);
opt有两种情况OS_DEL_NO_PEND和OS_DEL_ALWAYS
OS_DEL_NO_PEND 若是没有等待信号量的任务,则删除该任务
OS_DEL_ALWAYS 不管有没有任务等待,始终删除
其主要代码如下:
if (pevent->OSEventGrp != 0u) { //是否有任务等待
tasks_waiting = OS_TRUE; //有的话tasks_waiting = OS_TRUE
} else {
tasks_waiting = OS_FALSE; //没有
}
switch (opt) {
case OS_DEL_NO_PEND: //opt为OS_DEL_NO_PEND的情况
if (tasks_waiting == OS_FALSE) { //没有任务等待,则下面执行信号量的删除
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?";
pevent->OSEventName = (INT8U *)(void *)"?";
#endif
pevent->OSEventType = OS_EVENT_TYPE_UNUSED;//事件类型为未使用
pevent->OSEventPtr = OSEventFreeList; //事件控制块加入到空闲事件控制链表中
pevent->OSEventCnt = 0u;
OSEventFreeList = pevent;
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
pevent_return = (OS_EVENT *)0;
} else { //若是有任务等待信号量的话,删除不成功
OS_EXIT_CRITICAL();
*perr = OS_ERR_TASK_WAITING;
pevent_return = pevent;
}
break;
*perr = OS_ERR_TASK_WAITING;
pevent_return = pevent;
}
break;
case OS_DEL_ALWAYS: //当opt为OS_DEL_ALWAYS
while (pevent->OSEventGrp != 0u) { //逐个将等待信号的任务运行状态转为就绪态
(void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM, OS_STAT_PEND_OK);
}
#if OS_EVENT_NAME_EN > 0u //下面的删除信号量的事件控制块跟上面一样
pevent->OSEventName = (INT8U *)(void *)"?";
#endif
pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent->OSEventPtr = OSEventFreeList; /* Return Event Control Block to free list */
pevent->OSEventCnt = 0u;
OSEventFreeList = pevent; /* Get next free event control block */
OS_EXIT_CRITICAL();
if (tasks_waiting == OS_TRUE) { /* Reschedule only if task(s) were waiting */
OS_Sched(); /* Find highest priority task ready to run */
}
*perr = OS_ERR_NONE;
pevent_return = (OS_EVENT *)0; /* Semaphore has been deleted */
break;
default:
OS_EXIT_CRITICAL();
*perr = OS_ERR_INVALID_OPT;
pevent_return = pevent;
break;
#endif
pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent->OSEventPtr = OSEventFreeList; /* Return Event Control Block to free list */
pevent->OSEventCnt = 0u;
OSEventFreeList = pevent; /* Get next free event control block */
OS_EXIT_CRITICAL();
if (tasks_waiting == OS_TRUE) { /* Reschedule only if task(s) were waiting */
OS_Sched(); /* Find highest priority task ready to run */
}
*perr = OS_ERR_NONE;
pevent_return = (OS_EVENT *)0; /* Semaphore has been deleted */
break;
default:
OS_EXIT_CRITICAL();
*perr = OS_ERR_INVALID_OPT;
pevent_return = pevent;
break;
}