從零開始學習UCOSII操作系統7--信號量
參考博客:@ http://blog.csdn.net/gatiemehttps://blog.csdn.net/gatieme/article/details/21071379
前言:這裏一定要分析清楚,因爲信號量分析清楚後,後面的郵箱等其他的通信的東西都是大同小異的。
1、信號量的組成
(1)一部分是16位無符號的整型信號量計數值(0~65536)
(2)另一部分是由等待信號量的任務組成的等待任務表。
(3)信號量的6個基本函數:
OSSemAccept(), OSSemCreate(),OSSemDel()
OSSemPend(),OSSemPost()以及OSSemQuery()
其中要使用這些信號量的函數,必須在OS_CFG.h中將配置常數置爲1,這樣UCOSII才能支持信號量。
當OS_SEM_EN設爲0的時候,所有的信號量都不能使用。
需要使用這些函數的時候,需要把這些宏定義置爲1;
2、信號量使用的函數
(1)OSSemAccept(),OSSemPost()以及OSSemQuery()函數可以由任務或者中斷服務子程序調用。
(2)OSSemDel()和OSSemPend()函數只能由任務程序調用。
3、信號量的兩個關鍵問題
(1)任務怎麼得到一個信號量的問題?
想要得到一個信號量的任務,必須執行等待的操作(pend)
如果信號量有效(非0),則信號量減1,任務繼續運行。
如果此時信號量無效,則等待信號量的任務就被列入等待信號量的任務表中。此時可以設置超時時間,如果等待了多少時間後,仍然沒有信號發生。
則此任務進入就緒態,準備運行,並且顯示出錯的代碼---等待超時失敗。
(2)任務對信號量的釋放問題
任務執行發信號的POST操作來釋放信號量,如果沒有任務等待該信號量,那麼這個信號量的值就是僅僅簡單的+1.則此時信號量大於0有效。
如果有任務等待該信號量,那麼就會有另一個任務進入就緒態,此時信號量就不+1了,因爲剛剛的信號量就用出去了。
4、信號量的有效與無效問題
信號量有效:信號量的計算器非0,信號量有效表示任務對資源可用。
信號量無效:信號量的計算器爲0,信號量無效表示任務對目前的資源不可用,需要等待其他的另一個任務或者中斷服務子程序發出該信號量。
5信號量的值OSEventCnt大小表示什麼?
二值信號量Mutext,表示任務可以獨佔共享資源
計數型的信號量Semaphore,用於某資源可同時爲N個任務所用。
二值信號量與互斥型信號量的區別:
互斥型信號量必須是同一個任務申請,同一個任務釋放,其他的任務釋放無效。
二值信號量,一個任務申請成功後,可以由另一個任務釋放,也可以由本任務釋放。
6、信號量是如何實現任務之間的通信的?
(1)信號量的建立必須在任務級中建立。
(2)信號量類型爲OS_EVENT,信號量值可以爲1和0(二值信號量),0~65536計數型的信號量,不同的值代表不同的意義。
(3)對於互斥型信號量來說,就兩個操作,請求和釋放。
(4)一個任務請求信號量的時候,如果被其他的任務佔用,則任務等待,同時導致任務切換。 如果沒有被其他任務佔用,則獲得,繼續執行。
(5)釋放信號量的時候,如果其他高優先級任務正在請求並且等待該信號量的時候,則導致任務切換。
(6)OSSemAccept(信號量)起到查詢信號量的作用,返回信號量的值。
(7)OSSemPend(Sem,timeout,&err);
timeout代表等待timeout個信號量後還沒有得到信號量,恢復到就緒的狀態,如果timeout=0,標誌等待無限信號量。
7、信號量的三個關鍵函數的代碼分析
(1)OSSemCreate()創建一個信號量(由任務或者啓動代碼操作)
創建工作必須在任務級代碼中或者多任務啓動之前完成。功能只要是先獲取一個事件控制塊ECB,寫入一些參數,其中調用了OS_EventWaitListInit()函數,對事件控制塊的等待任務列表進行初始化。
就是將事件控制塊定義爲信號量,然後把該填寫的量填寫。最後設置信號量的初始值
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) { /* 中斷服務不能創建信號量 */
return ((OS_EVENT *)0);
}
OS_ENTER_CRITICAL();
pevent = OSEventFreeList; /* 得到下一個空閒的事件空閒控制塊 */
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; /* 設置信號量的初始值 */
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);
}
(2)刪除一個信號量
1、刪除一個信號量的函數OSSemDel()的源代碼,當文件OS_CFG.h中的OS_SEM_DEL_EN爲1的時候,該代碼才被編譯。
2、總之,在刪除信號量之前,必須首先刪除操作該信號量的所有的任務。
3、進入switch中,當OPT爲OS_DEL_NO_PEND,並且沒有任務在等待該信號量的時候,OSSemDel()函數將事件控制塊ECB標誌爲未使用,並將其退回到空閒事件控制鏈表中,此操作允許該事件用於創建另一個信號量。
當OPT爲OS_DEL_ALWAYS時候,所有等待該信號的任務都將進入就緒態,每個任務都得到了該信號量,當然這樣可能會導致致命的後果,因爲採用信號量就是爲了防止對資源的多重訪問。
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) { /* 是否是指向某一個用於信號量的一個函數 */
*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) { /* 看是否有任務在等待信號量 */
tasks_waiting = OS_TRUE; /* 是的 */
} 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
(3)等待一個信號量OSSEMPend()
等待一個信號量函數的源代碼分析:
PS:設置該函數中的超時時間,真正實現的函數是在OSTimeTICK()函數中逐個的遞減。這個地方要注意下,對每個任務的任務控制塊TCB中的OSTCBDly做遞減操作。
最後的參數:perr就是指向一個錯誤的標誌。
void OSSemPend (OS_EVENT *pevent,
INT32U timeout,
INT8U *perr)
{
#ifdef OS_SAFETY_CRITICAL
if (perr == (INT8U *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* 判斷這個指針是否爲空指針 */
*perr = OS_ERR_PEVENT_NULL;
return;
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* 如果這個事件控制塊的類型不是信號量的話 */
*perr = OS_ERR_EVENT_TYPE;
return;
}
if (OSIntNesting > 0u) { /* 不在中斷中操作此函數 */
*perr = OS_ERR_PEND_ISR; /* ... can't PEND from an ISR */
return;
}
if (OSLockNesting > 0u) { /* See if called with scheduler locked ... */
*perr = OS_ERR_PEND_LOCKED; /* ... can't PEND when locked */
return;
}
OS_ENTER_CRITICAL();
if (pevent->OSEventCnt > 0u) { /* If sem. is positive, resource available ... */
pevent->OSEventCnt--; /* ... decrement semaphore only if positive. */
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
return;
}
/* Otherwise, must wait until event occurs */
OSTCBCur->OSTCBStat |= OS_STAT_SEM; /* 資源沒有異常的話,那麼就把資源定義爲信號量 */
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK;
OSTCBCur->OSTCBDly = timeout; /* 設置信號量的超時時間 */
OS_EventTaskWait(pevent); /* 使一個任務進入等待某事件發生狀態 */
OS_EXIT_CRITICAL();
OS_Sched(); /* Find next highest priority task ready */
OS_ENTER_CRITICAL();
switch (OSTCBCur->OSTCBStatPend) { /* See if we timed-out or aborted */
case OS_STAT_PEND_OK:
*perr = OS_ERR_NONE;
break;
case OS_STAT_PEND_ABORT:
*perr = OS_ERR_PEND_ABORT; /* Indicate that we aborted */
break;
case OS_STAT_PEND_TO:
default:
OS_EventTaskRemove(OSTCBCur, pevent);
*perr = OS_ERR_TIMEOUT; /* Indicate that we didn't get event within TO */
break;
}
OSTCBCur->OSTCBStat = OS_STAT_RDY; /* Set task status to ready */
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK; /* Clear pend status */
OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; /* Clear event pointers */
#if (OS_EVENT_MULTI_EN > 0u)
OSTCBCur->OSTCBEventMultiPtr = (OS_EVENT **)0;
#endif
OS_EXIT_CRITICAL();
}
(4)發出一個信號量:OSSEMPOST()
當任務發出一個等待的信號量之後,那麼它必須接收到任務或者中斷服務子程序給他發出相應的信號量,那麼他才能回到就緒態,不然的話,就會不斷的存在在事件控制等待列表中。
OS_EventTaskRdy()函數把優先級最高的任務從等待任務列表中去除,並使它進入就緒態,然後,調用OSSched()任務調用函數,因爲已經獲得信號量的任務,並且檢車一下是否是最高的優先級的就緒態任務,如果是優先級最高的就緒態任務,這時就要進行任務切換,準備執行該就緒態任務。如果得到這個信號量的任務不是最高優先級的任務,退回到這個函數,並繼續執行。
INT8U OSSemPost (OS_EVENT *pevent)
{
#if OS_ARG_CHK_EN > 0u
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 != 0u) { /* 查看是否是存在信號量 */
/* 檢查一下是否有任務在等待該信號量 */
(void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM, OS_STAT_PEND_OK);
OS_EXIT_CRITICAL();
OS_Sched(); /* Find HPT ready to run */
return (OS_ERR_NONE);
}
if (pevent->OSEventCnt < 65535u) { /* Make sure semaphore will not overflow */
pevent->OSEventCnt++; /* Increment semaphore count to register event */
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
}
OS_EXIT_CRITICAL(); /* Semaphore value has reached its maximum */
return (OS_ERR_SEM_OVF);
}
(5)無等待的請求一個信號量OSSemAccept()
有些書籍或者博主稱爲查詢信號量。
解釋:當一個任務請求一個信號量的時候,如果該信號量暫停無效,也可以讓該任務簡單的返回,而不是進入休眠的狀態,這種情況下面的操作是由OSSEMAccept()函數完成的。
也就是別人家稱爲很牛逼的詞語:非阻塞函數
有我就響應,沒有那麼我就繼續執行。
這個函數十分的簡單:從該信號量的事件控制塊中取出當前的計數值,並檢查該信號量是否有效(計數值是否爲非0值)
如果信號量有效的話,則信號量的計數值減1,
如果信號量無效的話,那麼就直接返回。
INT16U OSSemAccept (OS_EVENT *pevent)
{
INT16U cnt;
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* Validate 'pevent' */
return (0u);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* Validate event block type */
return (0u);
}
OS_ENTER_CRITICAL();
cnt = pevent->OSEventCnt;
if (cnt > 0u) { /* See if resource is available */
pevent->OSEventCnt--; /* Yes, decrement semaphore and notify caller */
}
OS_EXIT_CRITICAL();
return (cnt); /* Return semaphore count */
}
(6)查詢一個信號量的當前的狀態OSSemQuery()
這裏面使用的,內存拷貝的方法,不斷把源地址的內容,也就是源數據結構的成員,賦值到現在地址的成員中。
這段代碼可能使用到了很多高級應用,但是我覺得是不好的,因爲我們可以設置一個結構體,來對應事件控制塊的成員。
然後一一賦值,可讀性大大的提高了,做過開發的都知道,代碼又醜又長,可讀性沒有的話,維護的成本會大大的提高。我們不是追求省那麼一丁點的內存。而是後面的人維護起來比較爽。
INT8U OSSemQuery (OS_EVENT *pevent,
OS_SEM_DATA *p_sem_data)
{
INT8U i;
OS_PRIO *psrc;
OS_PRIO *pdest;
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* Validate 'pevent' */
return (OS_ERR_PEVENT_NULL);
}
if (p_sem_data == (OS_SEM_DATA *)0) { /* Validate 'p_sem_data' */
return (OS_ERR_PDATA_NULL);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* Validate event block type */
return (OS_ERR_EVENT_TYPE);
}
OS_ENTER_CRITICAL();
p_sem_data->OSEventGrp = pevent->OSEventGrp; /* Copy message mailbox wait list */
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; /* Get semaphore count */
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
}