UC/OS-II基础知识之信号量及其操作
1.信号量
当事件控制块成员OSEventType的值被设置成OS_EVENT_TYPE_SEM时,这个事件控制块描述的就是一个信号量,信号量由信号量计数器和等待任务表两部分组成,信号量使用事件控制块的成员OSEventCnt作为计数器,而用数组OSEventTbl【】来充当等待任务表。每当有任务申请信号量时,如果信号量计数器OSEventCnt的值大于0,则把OSEventCnt减1并使任务继续运行,如果信号量计数器OSEventCnt的值等于0,使任务处于等待状态。如果有正在使用信号量的任务释放了该信号量,则会在任务等待表中找出优先级别最高的等待任务,并使它进入就绪状态后调用任务调度器进行一次任务调度。如果任务等待表中已经没有等待任务,则信号量计数器就只简单加1.一个信号量的事件控制块结构如下所示。
2.创建信号量
在使用信号量之前,应用程序必须调用OSSemCreat()来创建一个信号量。函数返回已创建信号量的指针,原型如下:
OS_EVENT *OSSemCreate (INT16U cnt)
{
OS_EVENT *pevent;
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0;
#endif
if (OSIntNesting > 0) { /* See if called from ISR ... */
return ((OS_EVENT *)0); /* ... can't CREATE from an ISR */
}
OS_ENTER_CRITICAL();
pevent = OSEventFreeList; /* Get next free event control block */
if (OSEventFreeList != (OS_EVENT *)0) { /* See if pool of free ECB pool was empty */
OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;
}
OS_EXIT_CRITICAL();
if (pevent != (OS_EVENT *)0) { /* Get an event control block */
pevent->OSEventType = OS_EVENT_TYPE_SEM;
pevent->OSEventCnt = cnt; /* Set semaphore value */
pevent->OSEventPtr = (void *)0; /* Unlink from ECB free list */
#if OS_EVENT_NAME_SIZE > 1
pevent->OSEventName[0] = '?'; /* Unknown name */
pevent->OSEventName[1] = OS_ASCII_NUL;
#endif
OS_EventWaitListInit(pevent); /* Initialize to 'nobody waiting' on sem. */
}
return (pevent);
}
一个刚创建且计数器初值为10的信号量如下所示
3.请求信号量
任务通过调用函数OSSemPend()请求信号量,函数原型如下
void OSSemPend (OS_EVENT *pevent, INT16U timeout, INT8U *err)
{
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0;
#endif
#if OS_ARG_CHK_EN > 0
if (err == (INT8U *)0) { /* Validate 'err' */
return;
}
if (pevent == (OS_EVENT *)0) { /* Validate 'pevent' */
*err = OS_ERR_PEVENT_NULL;
return;
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* Validate event block type */
*err = OS_ERR_EVENT_TYPE;
return;
}
if (OSIntNesting > 0) { /* See if called from ISR ... */
*err = OS_ERR_PEND_ISR; /* ... can't PEND from an ISR */
return;
}
if (OSLockNesting > 0) { /* See if called with scheduler locked ... */
*err = OS_ERR_PEND_LOCKED; /* ... can't PEND when locked */
return;
}
OS_ENTER_CRITICAL();
if (pevent->OSEventCnt > 0) { /* If sem. is positive, resource available ... */
pevent->OSEventCnt--; /* ... decrement semaphore only if positive. */
OS_EXIT_CRITICAL();
*err = OS_NO_ERR;
return;
} /* Otherwise, must wait until event occurs */
OSTCBCur->OSTCBStat |= OS_STAT_SEM; /* Resource not available, pend on semaphore */
OSTCBCur->OSTCBPendTO = OS_FALSE;
OSTCBCur->OSTCBDly = timeout; /* Store pend timeout in TCB */
OS_EventTaskWait(pevent); /* Suspend task until event or timeout occurs */
OS_EXIT_CRITICAL();
OS_Sched(); /* Find next highest priority task ready */
OS_ENTER_CRITICAL();
if (OSTCBCur->OSTCBPendTO == OS_TRUE) { /* See if we timedout */
OS_EventTO(pevent);
OS_EXIT_CRITICAL();
*err = OS_TIMEOUT; /* Indicate that didn't get event within TO */
return;
}
OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0;
OS_EXIT_CRITICAL();
*err = OS_NO_ERR;
}
参数pevent是被请求信号量的指针,为了防止因的不到信号量而处于长期的等待状态,函数OSSemPend()允许用参数timeout设置一个等待时间的限制,当任务等待的时间超过timeout时可以结束等待状态而进入就绪状态。如果timeout被设置为0,则表明任务的等待时间为无限长。
当一个任务请求信号量时,如果希望在信号量无效时准许任务不进入等待状态而继续运行,则不调用OSSemPend(),而是调用OSSemAccept()来请求信号量,函数原型如下
INT16U OSSemAccept (OS_EVENT *pevent)
{
INT16U cnt;
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0;
#endif
#if OS_ARG_CHK_EN > 0
if (pevent == (OS_EVENT *)0) { /* Validate 'pevent' */
return (0);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* Validate event block type */
return (0);
}
OS_ENTER_CRITICAL();
cnt = pevent->OSEventCnt;
if (cnt > 0) { /* See if resource is available */
pevent->OSEventCnt--; /* Yes, decrement semaphore and notify caller */
}
OS_EXIT_CRITICAL();
return (cnt); /* Return semaphore count */
}
#endif
4.发送信号量
任务获得信号量,在访问共享资源之后,必须释放信号量,信号量的释放也叫发送信号量。发送信号量需调用OSSemPost()函数,OSSemPost()在对信号量的计数器操作之前首先要检查是否还有等待该信号量的任务,如果没有则将信号量计数器加1,否则调用调度器运行等待任务中优先级别最高的任务。函数运行如下:
INT8U OSSemPost (OS_EVENT *pevent)
{
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0;
#endif
#if OS_ARG_CHK_EN > 0
if (pevent == (OS_EVENT *)0) { /* Validate 'pevent' */
return (OS_ERR_PEVENT_NULL);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* Validate event block type */
return (OS_ERR_EVENT_TYPE);
}
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0) { /* See if any task waiting for semaphore*/
(void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM); /* Ready HPT waiting on event */
OS_EXIT_CRITICAL();
OS_Sched(); /* Find HPT ready to run */
return (OS_NO_ERR);
}
if (pevent->OSEventCnt < 65535u) { /* Make sure semaphore will not overflow */
pevent->OSEventCnt++; /* Increment semaphore count to register event */
OS_EXIT_CRITICAL();
return (OS_NO_ERR);
}
OS_EXIT_CRITICAL(); /* Semaphore value has reached its maximum */
return (OS_SEM_OVF);
}
5.删除信号量
如果应用程序不需要某个信号量,那么可以调用函数OSSemDel()来删除该信号量,函数原型如下所示:
OS_EVENT *OSSemDel (OS_EVENT *pevent, INT8U opt, INT8U *err)
{
BOOLEAN tasks_waiting;
OS_EVENT *pevent_return;
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0;
#endif
#if OS_ARG_CHK_EN > 0
if (err == (INT8U *)0) { /* Validate 'err' */
return (pevent);
}
if (pevent == (OS_EVENT *)0) { /* Validate 'pevent' */
*err = OS_ERR_PEVENT_NULL;
return (pevent);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* Validate event block type */
*err = OS_ERR_EVENT_TYPE;
return (pevent);
}
if (OSIntNesting > 0) { /* See if called from ISR ... */
*err = OS_ERR_DEL_ISR; /* ... can't DELETE from an ISR */
return (pevent);
}
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0) { /* See if any tasks waiting on semaphore */
tasks_waiting = OS_TRUE; /* Yes */
} else {
tasks_waiting = OS_FALSE; /* No */
}
switch (opt) {
case OS_DEL_NO_PEND: /* Delete semaphore only if no task waiting */
if (tasks_waiting == OS_FALSE) {
#if OS_EVENT_NAME_SIZE > 1
pevent->OSEventName[0] = '?'; /* Unknown name */
pevent->OSEventName[1] = OS_ASCII_NUL;
#endif
pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent->OSEventPtr = OSEventFreeList; /* Return Event Control Block to free list */
pevent->OSEventCnt = 0;
OSEventFreeList = pevent; /* Get next free event control block */
OS_EXIT_CRITICAL();
*err = OS_NO_ERR;
pevent_return = (OS_EVENT *)0; /* Semaphore has been deleted */
} else {
OS_EXIT_CRITICAL();
*err = OS_ERR_TASK_WAITING;
pevent_return = pevent;
}
break;
case OS_DEL_ALWAYS: /* Always delete the semaphore */
while (pevent->OSEventGrp != 0) { /* Ready ALL tasks waiting for semaphore */
(void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM);
}
#if OS_EVENT_NAME_SIZE > 1
pevent->OSEventName[0] = '?'; /* Unknown name */
pevent->OSEventName[1] = OS_ASCII_NUL;
#endif
pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent->OSEventPtr = OSEventFreeList; /* Return Event Control Block to free list */
pevent->OSEventCnt = 0;
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 */
}
*err = OS_NO_ERR;
pevent_return = (OS_EVENT *)0; /* Semaphore has been deleted */
break;
default:
OS_EXIT_CRITICAL();
*err = OS_ERR_INVALID_OPT;
pevent_return = pevent;
break;
}
return (pevent_return);
}
#endif
参数中opt用来指明信号量的删除条件,该参数有两个值可供选择,如果选择常数OS_DEL_NO_PEND,则当等待任务表中没有等待任务时才删除信号量,如果选择OS_DEL_ALWAYS,则在等待任务表中无论是否有等待任务都将立即删除信号量。
注意:只能在任务中删除信号量,不能再中断服务程序中删除。