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,則在等待任務表中無論是否有等待任務都將立即刪除信號量。
注意:只能在任務中刪除信號量,不能再中斷服務程序中刪除。