天天看點

UCOS-Ⅲ:信号量UCOS-Ⅲ:信号量

文章目錄

  • UCOS-Ⅲ:信号量
    • 一、信号量基本概念
    • 二、調用API
      • 2.1 建立信号量函數OSSemCreate()
      • 2.2 信号量删除函數OSSemDel()
      • 2.3 信号量釋放函數OSSemPost()
      • 2.4 信号量擷取函數OSSemPend()
    • 三、信号量BUG-優先級反轉
    • 四、使用執行個體

UCOS-Ⅲ:信号量

一、信号量基本概念

信号量(Semaphore)是一種實作任務間通信的機制,可以實作任務之間同步或臨界資源的互斥通路 (臨界資源指同一時刻隻能有有限個通路),常用于協助一組互相競争的任務來通路臨界資源。運作機制可以了解為:信号量是一個正值,代表資源的可通路數目,當有任務通路時,這個數目減一,任務通路完成時,任務通路結束,釋放他,讓他加一,信号量為0時,其他任務則不能擷取他,選擇退出或者等待挂起,直到有信号量釋放後,按照優先級來擷取信号量,擷取後就緒,其運作流程大概如下圖:

UCOS-Ⅲ:信号量UCOS-Ⅲ:信号量

UCOS中信号量是核心對象,通過資料類型OS_SEM 定義,OS_SEM 源于結構體os_sem,UCOS中-Ⅲ的信号量相關的代碼都被放在OS_SEM.C 中,通過設定OS_CFG.H 中的OS_CFG_SEM_EN 為1 使能信号量

/* ----------------------------- 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() 函數
           

信号量的結構體如下:

struct  os_sem {                                            /* Semaphore                                              */
                                                            /* ------------------ GENERIC  MEMBERS ------------------ */
    OS_OBJ_TYPE          Type;                              /* Should be set to OS_OBJ_TYPE_SEM                       */
    CPU_CHAR            *NamePtr;                           /* Pointer to Semaphore Name (NUL terminated ASCII)       */
    OS_PEND_LIST         PendList;                          /* List of tasks waiting on semaphore                     */
                                                            /* ------------------ SPECIFIC MEMBERS ------------------ */
    OS_SEM_CTR           Ctr;
    CPU_TS               TS;
};
           

第一個變量是**“Type”域**:表明UCOS識别所定義的是一個信号量。其它的核心對象也有“Type”域作為結構體的第一個變量。如果函數要調用一個核心對象,UCOS會檢測所調用的核心對象的資料類型是否對應。例如,如果需要傳遞一個消息隊列OS_Q 給函數,但實際傳遞的是一個信号量OS_SEM,UCOS就會檢測出這是一個無效的參數,并傳回錯誤代号。

第二個指針指向核心對象的名字:每個核心對象都可以被賦予一個名字,名字有ASCII 字元串組成,但必須以空字元結尾。

第三個等待清單PendList:若有多個任務等待信号量,信号量就會将這些任務放入其挂起隊列中。

第四個包含一個信号量計數值:信号量計數值可以定義為8 位,16 位,或32 位,取決于OS_TYPE.H 中的OS_SEM_CTR是如何被定義的,這個值用于判斷信号量可用的資源數目**(此值的上限大于1為多值信号量,隻為0或者1則為二值信号量,UCOS-Ⅲ沒有對這兩個多做區分,全看如何使用)**

第五個CPU_TS時間戳:信号量中包含了一個時間戳變量,存儲了上一次信号量被送出時的時間戳。當信号量被送出時,CPU 的時間戳被讀取并存在信号量的時間戳變量中,當OSSemPend()被調用時就能讀取這個時間戳變量。

使用者代碼也不能直接通路信号量中的變量。必須通過UCOS-III 提供的API來進行通路!

二、調用API

UCOS中信号量的調用API有以下四個API,分别為建立、删除、擷取、釋放信号量

2.1 建立信号量函數OSSemCreate()

OSSemCreate()函數進行建立一個信号量,跟消息隊列的建立差不多,我們知道,其實這裡的“建立信号量”指的就是對核心對象(信号量)的一些初始化。

函數入口:

void  OSSemCreate (OS_SEM      *p_sem,  //多值信号量控制塊指針
                   CPU_CHAR    *p_name, //多值信号量名稱
                   OS_SEM_CTR   cnt,    //資源數目或事件是否發生标志
                   OS_ERR      *p_err)  //傳回錯誤類型
           

介紹:

p_sem 指向信号量變量的指針。
p_name 指向信号量變量名字字元串的指針。
cnt 信号量的初始值,用作資源保護的信号量這個值通常跟資源的數量相同,用做标志事件發生的信号量這個值設定為0,标志事情還沒有發生。
p_err 指向傳回錯誤類型的指針。

p_err傳回錯誤标志,其具體的傳回值對應的情景如下

錯誤傳回值 錯誤類型
OS_ERR_CREATE_ISR 在中斷中建立信号量是不被允許的,傳回錯誤。
OS_ERR_ILLEGAL_CREATE_RUN_TIME 在定義OSSafetyCriticalStartFlag 為DEF_TRUE 後就不運作建立任何核心對象。
OS_ERR_NAME 參數p_name 是個空指針。
OS_ERR_OBJ_CREATED 信号量已經被建立(不過函數中并沒有涉及到這個錯誤的代碼)
OS_ERR_OBJ_PTR_NULL 參數p_sem 是個空指針。
OS_ERR_OBJ_TYPE 參數p_sem 被初始化為别的核心對象了。
OS_ERR_NONE 無錯誤,繼續執行

使用執行個體:

//定義結構體
OS_SEM SemOfKey;          //标志KEY1是否被按下的多值信号量

//使用前調用API初始化
任務體
{
    /* 建立多值信号量 SemOfKey */
    OSSemCreate((OS_SEM      *)&SemOfKey,    //指向信号量變量的指針
                (CPU_CHAR    *)"SemOfKey",    //信号量的名字
                (OS_SEM_CTR   )5,             //表示現有資源數目
                (OS_ERR      *)&err);         //錯誤類型
}
           

2.2 信号量删除函數OSSemDel()

OSSemDel()用于删除一個信号量,信号量删除函數是根據信号量結構(信号量句柄)直接删除的,删除之後這個信号量的所有資訊都會被系統清空,而且不能再次使用這個信号量了,但是需要注意的是,如果某個信号量沒有被定義,那也是無法被删除的,如果有任務阻塞在該信号量上,那麼盡量不要删除該信号量。使用之前首先要将OS_CFG_SEM_DEL_EN 這個宏置1,注意調用這個函數後,之前用信号量保護的資源将不再得到保護。

函數入口:

OS_OBJ_QTY  OSSemDel (OS_SEM  *p_sem,  //多值信号量指針
                      OS_OPT   opt,    //選項
                      OS_ERR  *p_err)  //傳回錯誤類型
           
參數名稱 參數作用
p_sem 指向信号量變量的指針。
opt 删除信号量時候的選項,有以下兩個選擇。
p_err 指向傳回錯誤類型的指針,有以下幾種可能。

參數選項選擇

選項 作用
OS_OPT_DEL_NO_PEND 當信号量的等待清單上面沒有相應的任務的時候才删除信号量。
OS_OPT_DEL_ALWAYS 不管信号量的等待清單是否有相應的任務都删除信号量。

錯誤類型

錯誤傳回值 錯誤類型
OS_ERR_DEL_ISR 企圖在中斷中删除信号量。
OS_ERR_OBJ_PTR_NULL 參數p_sem 是空指針。
OS_ERR_OBJ_TYPE 參數p_sem 指向的核心變量類型不是信号量
OS_ERR_OPT_INVALID opt 在給出的選項之外
OS_ERR_TASK_WAITING 在選項opt 是OS_OPT_DEL_NO_PEND 的時候,并且信号量等待清單上有等待的任務。

同時該函數有一個傳回值,傳回值的含義為:

删除信号量的時候,會将信号量等待清單上的任務脫離該信号量的等待清單。傳回值表示的就是脫離等待清單的任務個數。

使用執行個體

OS_SEM SemOfKey;; //聲明信号量

/* 删除信号量 sem*/
OSSemDel ((OS_SEM *)&SemOfKey, //指向信号量的指針
		  OS_OPT_DEL_NO_PEND,
		  (OS_ERR *)&err); //傳回錯誤類型
           

2.3 信号量釋放函數OSSemPost()

當信号量有值的時候,任務才能擷取信号量,有兩個方式使信号量有值,一個是在建立的時候進行初始化,将它可用的信号量個數設定一個初始值;如果該信号量用作二值信号量,那麼我們在建立信号量的時候其初始值的範圍是0~1,假如初始值為1個可用的信号量的話,被擷取一次就變得無效了,那就需要我們釋放信号量,uCOS 提供了信号量釋放函數,每調用一次該函數就釋放一個信号量。UCOS可以一直釋放信号量,但如果用作二值信号量的話,一直釋放信号量就達不到同步或者互斥通路的效果,雖然說uCOS 的信号量是允許一直釋放的,但是,信号量的範圍還需我們使用者自己根據需求進行決定,當用作二值信号量的時候,必須確定其可用值在0~1 範圍内;而用作計數信号量的話,其範圍是由使用者根據實際情況來決定的

函數入口:

OS_SEM_CTR  OSSemPost (OS_SEM  *p_sem,    //多值信号量控制塊指針
                       OS_OPT   opt,      //選項
                       OS_ERR  *p_err)    //傳回錯誤類型
           
參數名稱 參數作用
p_sem 指向要送出的信号量的指針
opt 釋出信号時的選項,可能有以下幾個選項
p_err 指向傳回錯誤類型的指針,錯誤的類型如下。(隻列了必要部分)

選項清單:

選項 功能
OS_OPT_POST_1 釋出給信号量等待清單中優先級最高的任務。
OS_OPT_POST_ALL 釋出給信号量等待清單中所有的任務。
OS_OPT_POST_NO_SCHED 送出信号量之後要不要進行任務排程,預設是要進行任務排程的,選擇該選項可能的原因是想繼續運作目前任務,因為釋出信号量可能讓那些等待信号量的任務就緒,這個選項沒有進行任務排程,釋出完信号量目前任務還是繼續運作。當任務想釋出多個信号量,最後同時排程的話也可以用這個選項。可以跟上面兩個選項之一相與做為參數。

錯誤值:

OS_ERR_SEM_OVF 信号量計數值已經達到最大範圍了,這次送出會引起信号量計數值溢出。

傳回值:

信号量計數值

使用執行個體:

OS_SEM SemOfKey; //标志KEY1 是否被按下的信号量

OSSemPost((OS_SEM *)&SemOfKey, 	  	//釋出SemOfKey
		(OS_OPT )OS_OPT_POST_ALL,   //釋出給所有等待任務
		(OS_ERR *)&err); 			//傳回錯誤類型
           

2.4 信号量擷取函數OSSemPend()

當任務擷取了某個信号量的時候,該信号量的可用個數就減一,當它減到0 的時候,任務就無法再擷取了,并且擷取的任務會進入阻塞态(假如使用者指定了阻塞逾時時間的話)。如果某個信号量中目前擁有1 個可用的信号量的話,被擷取一次就變得無效了,那麼此時另外一個任務擷取該信号量的時候,就會無法擷取成功,該任務便會進入阻塞态,阻塞時間由使用者指定。uCOS 支援系統中多個任務擷取同一個信号量,假如信号量中已有多個任務在等待,那麼這些任務會按照優先級順序進行排列,如果信号量在釋放的時候選擇隻釋放給一個任務,那麼在所有等待任務中最高優先級的任務優先獲得信号量,而如果信号量在釋放的時候選擇釋放給所有任務,則所有等待的任務都會擷取到信号量

函數入口:

OS_SEM_CTR  OSSemPend (OS_SEM   *p_sem,   //多值信号量指針
                       OS_TICK   timeout, //等待逾時時間
                       OS_OPT    opt,     //選項
                       CPU_TS   *p_ts,    //等到信号量時的時間戳
                       OS_ERR   *p_err)   //傳回錯誤類型
           

參數:

參數 作用
p_sem 指向要擷取的信号量變量的指針。
opt 可能是以下幾個選項之一。
timeout 這個參數是設定的是擷取不到信号量的時候等待的時間。如果這個值為0,表示一直等待下去,如果這個值不為0,則最多等待timeout 個時鐘節拍。
p_ts 指向等待的信号量被删除,等待被強制停止,等待逾時等情況時的時間戳的指針。
p_err 指向傳回錯誤類型的指針,有以下幾種類型。

功能選項:

功能 作用
OS_OPT_PEND_BLOCKING 如果不能即刻獲得信号量,選項表示要繼續等待。
OS_OPT_PEND_NON_BLOCKING 如果不能即刻獲得信号量,選項表示不等待信号量。

錯誤類型:

錯誤 類型
OS_ERR_OBJ_DEL 信号量已經被删除了。
OS_ERR_OBJ_PTR_NULL 輸入的信号量變量指針是空類型。
OS_ERR_OBJ_TYPE p_sem 指向的變量核心對象類型不是信号量。
OS_ERR_OPT_INVALID 參數opt 不符合要求。
OS_ERR_PEND_ABORT 等待過程,其他的任務調用了函數OSSemPendAbort 強制取消等待。
OS_ERR_PEND_ISR 企圖在中斷中等待信号量。
OS_ERR_PEND_WOULD_BLOCK 開始擷取不到信号量,且沒有要求等待。
OS_ERR_SCHED_LOCKED 排程器被鎖住。
OS_ERR_STATUS_INVALID 系統出錯,導緻任務控制塊的元素PendStatus 不在可能的範圍内。
OS_ERR_TIMEOUT 等待逾時。
OS_ERR_NONE 成功擷取

傳回值:

信号量計數值

使用執行個體:

ctr = OSSemPend ((OS_SEM   *)&SemOfKey,               //等待該信号量 SemOfKey
                 (OS_TICK   )0,                       //下面選擇不等待,該參無效
                 (OS_OPT    )OS_OPT_PEND_NON_BLOCKING,//如果沒信号量可用不等待
                 (CPU_TS   *)0,                       //不擷取時間戳
                 (OS_ERR   *)&err);                   //傳回錯誤類型

if(err == OS_ERR_NONE)
{
    //擷取成功
}   

           

三、信号量BUG-優先級反轉

優先級反轉是實時系統中的一個常見問題,存在于基于優先級的搶占式核心中,優先級反轉的原理如下:

下圖有三個排程任務L、M、H,任務H 的優先級高于任務M,任務M 的優先級高于任務L

UCOS-Ⅲ:信号量UCOS-Ⅲ:信号量

任務L最開始運作的時候擷取信号量,之後任務H開始執行,搶占了任務L,在任務H運作的時候,剛好需要擷取信号量,但此時信号量還在任務L的手裡,于是任務H進入挂起隊列,任務L繼續運作,在任務L沒有釋放信号量的時候,任務M過來搶占L運作,在任務L釋放信号量時,任務H才繼續執行,若任務M 需要執行很長時間,則任務H 會被延遲很長時間才執行,這叫做優先級反轉。

解決方法是臨時提高任務L的優先級,這一内容我們下一節互斥量再分析

四、使用執行個體

功能:信号量管理停車位資源,按鍵2按下釋放信号量,停車位+1,序列槽列印數目,按鍵1按下擷取信号量,停車位-1,序列槽顯示是否擷取成功

啟動任務建立信号量

/* 建立多值信号量 SemOfKey */
    OSSemCreate((OS_SEM      *)&SemOfKey,    //指向信号量變量的指針
               (CPU_CHAR    *)"SemOfKey",    //信号量的名字
               (OS_SEM_CTR   )5,             //表示現有資源數目
               (OS_ERR      *)&err);         //錯誤類型
           

再建立兩個任務

任務一主體:

/*
*********************************************************************************************************
*                                          KEY1 TASK
*********************************************************************************************************
*/
static  void  AppTaskKey1 ( void * p_arg )
{
	OS_ERR      err;
	OS_SEM_CTR  ctr;
	CPU_SR_ALLOC();  //使用到臨界段(在關/開中斷時)時必需該宏,該宏聲明和定義一個局部變
									 //量,用于儲存關中斷前的 CPU 狀态寄存器 SR(臨界段關中斷隻需儲存SR)
									//,開中斷時将該值還原。	
	uint8_t ucKey1Press = 0;
	
	
	(void)p_arg;

					 
	while (DEF_TRUE) {                                                         //任務體
		if( Key_Scan ( macKEY1_GPIO_PORT, macKEY1_GPIO_PIN, 1, & ucKey1Press ) ) //如果KEY1被按下
		{
			ctr = OSSemPend ((OS_SEM   *)&SemOfKey,               //等待該信号量 SemOfKey
								       (OS_TICK   )0,                       //下面選擇不等待,該參無效
								       (OS_OPT    )OS_OPT_PEND_NON_BLOCKING,//如果沒信号量可用不等待
								       (CPU_TS   *)0,                       //不擷取時間戳
								       (OS_ERR   *)&err);                   //傳回錯誤類型
			
			OS_CRITICAL_ENTER();                                  //進入臨界段
			
			if ( err == OS_ERR_NONE )                      
				printf ( "\r\nKEY1被按下:成功申請到停車位,剩下%d個停車位。\r\n", ctr );
			else if ( err == OS_ERR_PEND_WOULD_BLOCK )
				printf ( "\r\nKEY1被按下:不好意思,現在停車場已滿,請等待!\r\n" );
			
			OS_CRITICAL_EXIT(); 

		}
		
		OSTimeDlyHMSM ( 0, 0, 0, 20, OS_OPT_TIME_DLY, & err );  //每20ms掃描一次
		
	}
	
}

           

任務二主體:

static  void  AppTaskKey2 ( void * p_arg )
{
	OS_ERR      err;
	OS_SEM_CTR  ctr;
	CPU_SR_ALLOC();  //使用到臨界段(在關/開中斷時)時必需該宏,該宏聲明和定義一個局部變
									 //量,用于儲存關中斷前的 CPU 狀态寄存器 SR(臨界段關中斷隻需儲存SR)
									 //,開中斷時将該值還原。
	uint8_t ucKey2Press = 0;
	
	
	(void)p_arg;

					 
	while (DEF_TRUE) {                                                         //任務體
		if( Key_Scan ( macKEY2_GPIO_PORT, macKEY2_GPIO_PIN, 1, & ucKey2Press ) ) //如果KEY2被按下
		{
		  ctr = OSSemPost((OS_SEM  *)&SemOfKey,                                  //釋出SemOfKey
							        (OS_OPT   )OS_OPT_POST_ALL,                            //釋出給所有等待任務
							        (OS_ERR  *)&err);                                      //傳回錯誤類型
      
			OS_CRITICAL_ENTER();                                                   //進入臨界段
			
			printf ( "\r\nKEY2被按下:釋放1個停車位,剩下%d個停車位。\r\n", ctr );
			
			OS_CRITICAL_EXIT();
			
		}
		
		OSTimeDlyHMSM ( 0, 0, 0, 20, OS_OPT_TIME_DLY, & err );                    //每20ms掃描一次
		
	}
	
}
           

序列槽現象:

UCOS-Ⅲ:信号量UCOS-Ⅲ:信号量

繼續閱讀