1. 程式人生 > 其它 >ucos(9)互斥鎖和死鎖

ucos(9)互斥鎖和死鎖

一、概述

互斥鎖,亦稱:互斥訊號量。   在程式設計中,引入了物件互斥鎖的概念,來保證共享資料操作的完整性。每個物件都對應於一個可稱為“互斥鎖”的標記,這個標記用來保證在任一時刻,只能有一個任務(執行緒)訪問該物件(任務之間訪問到相同的函式、相同的全域性變數)。某個任務得到互斥鎖後,就可以訪問共享資源,其他任務等待該任務釋放互斥鎖才能進行訪問。   何時可以用普通訊號量替代互斥鎖?如果沒有任務對共享資源訪問有截止的時間,那麼普通訊號量可以替代互斥鎖;反之則必須使用互斥鎖。因為前者會造成無界優先順序反轉,後者卻不會。
void task(void *parg)
{
	while(1)
	{
		加鎖
		訪問共享資源
		解鎖(立即)

		.....
		加鎖
		訪問共享資源
		解鎖(立即)
		....

	}

}  

二、函式介面

1.建立互斥鎖

void  OSMutexCreate (OS_MUTEX  *p_mutex,
                     CPU_CHAR  *p_name,
                     OS_ERR    *p_err)
引數:
  • p_mutex,互斥鎖物件
  • p_name,互斥鎖名字
  • p_err,返回錯誤碼,沒有錯誤的就返回OS_ERR_NONE
返回值:無

2.等待互斥鎖

若等待成功,則鎖定共享資源
void OSMutexPend (OS_MUTEX *p_mutex, OS_TICK timeout, OS_OPT opt, CPU_TS *p_ts, OS_ERR *p_err)
引數:
  • p_mutex,互斥鎖物件
  • timeout,超時時間,預設寫0,一直等待
  • opt,設定當前等待互斥鎖的阻塞方式,預設寫OS_OPT_PEND_BLOCKING,阻塞等待。如果互斥鎖此時被另外一個任務佔用,且指定的阻塞型別為OS_OPT_PEND_NON_BLOCKING,則OSMutexPend就會直接返回而不再等待互斥鎖被釋放。
  • p_ts,用於記錄等待互斥鎖花了多長時間,預設寫NULL,不記錄。
  • p_err,返回錯誤碼,沒有錯誤的就返回OS_ERR_NONE
說明:
  • 如果佔有互斥鎖是一個較低優先順序多工,那麼UCOSIII就會臨時提升它的優先順序,使得其等於此時想要獲取互斥鎖的任務優先順序。

3.釋放互斥鎖,解鎖

void OSMutexPost (OS_MUTEX *p_mutex, OS_OPT opt, OS_ERR *p_err)

引數:

  • p_mutex,互斥鎖物件
  • opt,釋放互斥鎖後希望其他等待鎖的任務(最高優先順序且就緒)得到立即執行,填寫引數OS_OPT_POST_NONE,也是預設值。若使用了OS_OPT_POST_NO_SCHED這個引數,得到互斥鎖的任務不會立即執行。
  • p_err,返回錯誤碼,沒有錯誤的就返回OS_ERR_NONE。

三、死鎖(或抱死)

  死鎖(dead lock)也稱做抱死(deadly embrace),指兩個任務無限制地互相等待對方控制著的資源。 假設任務T1正獨佔資源R1,任務T2正獨佔資源R2,示例程式碼如下:
void T1(void *parg)
{
	while(1)
	{
		(1)等待事件發生
		
		(2)請求互斥鎖M1
		
		(3)訪問共享資源R1
		
    		:
    		:
		(4)-------   中斷!
    		:
    		:

		(8)請求互斥鎖M2
		
		(9)訪問共享資源R2			
	}
}


void T2(void *parg)
{
	while(1)
	{
		等待事件發生
		
		(5)請求互斥鎖M2
		
		(6)訪問共享資源R2
		
    		:
    		:

		(7)請求互斥鎖M1
		
		訪問共享資源R1			
	}
}
(1)假設任務T1具有最高優先順序,且其等待的事件發生了,所以任務1開始執行。 (2)任務T1執行並請求獲得互斥鎖M1 (3)任務T1獲得M1並訪問共享資源R1 (4)一箇中斷髮生了,導致具有比任務T1更高優先順序的T2獲得了CPU的使用權。 (5)該中斷是任務T2等待的事件,故任務T2繼續執行。 (6)任務T2繼續執行,請請求獲得互斥鎖M2以訪問共享資源R2。 (7)任務T2想要獲得互斥鎖M1,但此時UCOSIII知道此時M1被任務T1佔用著。 (8)任務T2無法繼續執行,UCOSIII做任務切換轉而執行任務T1. (9)任務T1想要獲取互斥鎖M2,但M2卻被任務T2佔有了。此時兩個任務便死鎖了,誰也無法繼續執行,因為誰也無法獲取對方的資源。 避免出現死鎖的方法,讓每個任務都:
  • 先得到全部需要的資源,再做下一個動作
  • 用相同的順序申請多個資源
  • 在呼叫請求互斥鎖的函式時設定超時時間
以相同的順序先得到全部需要的資源來避免死鎖的問題,示例程式碼1如下:
void T1(void *parg)
{
	while(1)
	{
		等待事件發生
		
		請求互斥鎖M1
		
		請求互斥鎖M2	
		
		訪問共享資源R1

		訪問共享資源R2	
  
  		釋放互斥鎖M1
		
		釋放互斥鎖M2	  		
	}
}


void T2(void *parg)
{
	while(1)
	{
		等待事件發生
		
		請求互斥鎖M1
		
		請求互斥鎖M2	
		
		訪問共享資源R1

		訪問共享資源R2	
  
		釋放互斥鎖M1
		
		釋放互斥鎖M2		
	}
}
以相同的順序獲取資源來避免死鎖的問題, 示例程式碼2如下:
void T1(void *parg)
{
	while(1)
	{
		等待事件發生
		
		請求互斥鎖M1
		
		訪問共享資源R1
		
 		釋放互斥鎖M1
    
		請求互斥鎖M2	

		訪問共享資源R2	
  
 		釋放互斥鎖M2   		
	}
}

void T2(void *parg)
{
	while(1)
	{
		等待事件發生
		
		請求互斥鎖M1
		
		訪問共享資源R1
		
 		釋放互斥鎖M1
    
		請求互斥鎖M2	

		訪問共享資源R2	
  
 		釋放互斥鎖M2  		
	}
} 

四、例項例程

1、死鎖

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "includes.h"

//任務1控制塊
OS_TCB Task1_TCB;

void task1(void *parg);

CPU_STK task1_stk[128];			//任務1的任務堆疊,大小為128字,也就是512位元組



//任務2控制塊
OS_TCB Task2_TCB;

void task2(void *parg);

CPU_STK task2_stk[128];			//任務2的任務堆疊,大小為128字,也就是512位元組


OS_MUTEX	g_mutex_1;			//互斥鎖1

OS_MUTEX	g_mutex_2;			//互斥鎖1


void res1(void)
{
	volatile uint32_t i=0x50;
	
	while(i--)
	{
		delay_ms(10);
	}
}


void res2(void)
{
	volatile uint32_t i=0x50;
	
	while(i--)
	{
		delay_ms(10);
	}
}

//主函式
int main(void)
{
	OS_ERR err;

	systick_init();  													//時鐘初始化
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);						//中斷分組配置
	
	usart_init(9600);  				 									//串列埠初始化
	
	LED_Init();         												//LED初始化	


	//OS初始化,它是第一個執行的函式,初始化各種的全域性變數,例如中斷巢狀計數器、優先順序、儲存器
	OSInit(&err);


	//建立任務1
	OSTaskCreate(	(OS_TCB *)&Task1_TCB,									//任務控制塊,等同於執行緒id
					(CPU_CHAR *)"Task1",									//任務的名字,名字可以自定義的
					(OS_TASK_PTR)task1,										//任務函式,等同於執行緒函式
					(void *)0,												//傳遞引數,等同於執行緒的傳遞引數
					(OS_PRIO)6,											 	//任務的優先順序6		
					(CPU_STK *)task1_stk,									//任務堆疊基地址
					(CPU_STK_SIZE)128/10,									//任務堆疊深度限位,用到這個位置,任務不能再繼續使用
					(CPU_STK_SIZE)128,										//任務堆疊大小			
					(OS_MSG_QTY)0,											//禁止任務訊息佇列
					(OS_TICK)0,												//預設時間片長度																
					(void  *)0,												//不需要補充使用者儲存區
					(OS_OPT)OS_OPT_TASK_NONE,								//沒有任何選項
					&err													//返回的錯誤碼
				);
					
	if(err!=OS_ERR_NONE)
	{
		printf("task 1 create fail\r\n");
		
		while(1);
	
	}


	//建立任務2
	OSTaskCreate(	(OS_TCB *)&Task2_TCB,									//任務控制塊
					(CPU_CHAR *)"Task2",									//任務的名字
					(OS_TASK_PTR)task2,										//任務函式
					(void *)0,												//傳遞引數
					(OS_PRIO)6,											 	//任務的優先順序6		
					(CPU_STK *)task2_stk,									//任務堆疊基地址
					(CPU_STK_SIZE)128/10,									//任務堆疊深度限位,用到這個位置,任務不能再繼續使用
					(CPU_STK_SIZE)128,										//任務堆疊大小			
					(OS_MSG_QTY)0,											//禁止任務訊息佇列
					(OS_TICK)0,												//預設時間片長度																
					(void  *)0,												//不需要補充使用者儲存區
					(OS_OPT)OS_OPT_TASK_NONE,								//沒有任何選項
					&err													//返回的錯誤碼
				);
					
	if(err!=OS_ERR_NONE)
	{
		printf("task 2 create fail\r\n");
		
		while(1);
	
	}
	
	//建立互斥鎖1
	OSMutexCreate(&g_mutex_1,"g_mutex_1",&err);
	
	OSMutexCreate(&g_mutex_2,"g_mutex_2",&err);
	//啟動OS,進行任務排程
	OSStart(&err);
					
					
	printf(".......\r\n");
					
	while(1);
	
}


void task1(void *parg)
{

	OS_ERR err;

	printf("task1 is create ok\r\n");

	while(1)
	{

		OSMutexPend(&g_mutex_1,0,OS_OPT_PEND_BLOCKING,NULL,&err);
		
		printf("[task1]access res1 begin\r\n");
		res1();
		printf("[task1]access res1 end\r\n");
		
		OSMutexPend(&g_mutex_2,0,OS_OPT_PEND_BLOCKING,NULL,&err);		
		printf("[task1]access res2 begin\r\n");
		res2();
		printf("[task1]access res2 end\r\n");		

		OSMutexPost(&g_mutex_1,OS_OPT_POST_NONE,&err);
		OSMutexPost(&g_mutex_2,OS_OPT_POST_NONE,&err);		
	}
}


void task2(void *parg)
{
	OS_ERR err;
	
	printf("task2 is create ok\r\n");

	while(1)
	{
		OSMutexPend(&g_mutex_2,0,OS_OPT_PEND_BLOCKING,NULL,&err);
		
		printf("[task2]access res2 begin\r\n");
		res2();
		printf("[task2]access res2 end\r\n");
		
		OSMutexPend(&g_mutex_1,0,OS_OPT_PEND_BLOCKING,NULL,&err);	
		
		printf("[task2]access res1 begin\r\n");
		res1();
		printf("[task2]access res1 end\r\n");		

		OSMutexPost(&g_mutex_1,OS_OPT_POST_NONE,&err);
		OSMutexPost(&g_mutex_2,OS_OPT_POST_NONE,&err);
	}
} 
執行結果: 由列印資訊可知,程式處於死鎖狀態。

2、避免死鎖

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "includes.h"

//任務1控制塊
OS_TCB Task1_TCB;

void task1(void *parg);

CPU_STK task1_stk[128];			//任務1的任務堆疊,大小為128字,也就是512位元組



//任務2控制塊
OS_TCB Task2_TCB;

void task2(void *parg);

CPU_STK task2_stk[128];			//任務2的任務堆疊,大小為128字,也就是512位元組


OS_MUTEX	g_mutex_1;			//互斥鎖1

OS_MUTEX	g_mutex_2;			//互斥鎖1


void res1(void)
{
	volatile uint32_t i=0x50;
	
	while(i--)
	{
		delay_ms(10);
	}
}


void res2(void)
{
	volatile uint32_t i=0x50;
	
	while(i--)
	{
		delay_ms(10);
	}
}

//主函式
int main(void)
{
	OS_ERR err;

	systick_init();  													//時鐘初始化
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);						//中斷分組配置
	
	usart_init(9600);  				 									//串列埠初始化
	
	LED_Init();         												//LED初始化	


	//OS初始化,它是第一個執行的函式,初始化各種的全域性變數,例如中斷巢狀計數器、優先順序、儲存器
	OSInit(&err);


	//建立任務1
	OSTaskCreate(	(OS_TCB *)&Task1_TCB,									//任務控制塊,等同於執行緒id
					(CPU_CHAR *)"Task1",									//任務的名字,名字可以自定義的
					(OS_TASK_PTR)task1,										//任務函式,等同於執行緒函式
					(void *)0,												//傳遞引數,等同於執行緒的傳遞引數
					(OS_PRIO)6,											 	//任務的優先順序6		
					(CPU_STK *)task1_stk,									//任務堆疊基地址
					(CPU_STK_SIZE)128/10,									//任務堆疊深度限位,用到這個位置,任務不能再繼續使用
					(CPU_STK_SIZE)128,										//任務堆疊大小			
					(OS_MSG_QTY)0,											//禁止任務訊息佇列
					(OS_TICK)0,												//預設時間片長度																
					(void  *)0,												//不需要補充使用者儲存區
					(OS_OPT)OS_OPT_TASK_NONE,								//沒有任何選項
					&err													//返回的錯誤碼
				);
					
	if(err!=OS_ERR_NONE)
	{
		printf("task 1 create fail\r\n");
		
		while(1);
	
	}


	//建立任務2
	OSTaskCreate(	(OS_TCB *)&Task2_TCB,									//任務控制塊
					(CPU_CHAR *)"Task2",									//任務的名字
					(OS_TASK_PTR)task2,										//任務函式
					(void *)0,												//傳遞引數
					(OS_PRIO)6,											 	//任務的優先順序6		
					(CPU_STK *)task2_stk,									//任務堆疊基地址
					(CPU_STK_SIZE)128/10,									//任務堆疊深度限位,用到這個位置,任務不能再繼續使用
					(CPU_STK_SIZE)128,										//任務堆疊大小			
					(OS_MSG_QTY)0,											//禁止任務訊息佇列
					(OS_TICK)0,												//預設時間片長度																
					(void  *)0,												//不需要補充使用者儲存區
					(OS_OPT)OS_OPT_TASK_NONE,								//沒有任何選項
					&err													//返回的錯誤碼
				);
					
	if(err!=OS_ERR_NONE)
	{
		printf("task 2 create fail\r\n");
		
		while(1);
	
	}
	
	//建立互斥鎖1
	OSMutexCreate(&g_mutex_1,"g_mutex_1",&err);
	
	OSMutexCreate(&g_mutex_2,"g_mutex_2",&err);
	//啟動OS,進行任務排程
	OSStart(&err);
					
					
	printf(".......\r\n");
					
	while(1);
	
}


void task1(void *parg)
{

	OS_ERR err;

	printf("task1 is create ok\r\n");

	while(1)
	{

		OSMutexPend(&g_mutex_1,0,OS_OPT_PEND_BLOCKING,NULL,&err);
		OSMutexPend(&g_mutex_2,0,OS_OPT_PEND_BLOCKING,NULL,&err);	
		
		printf("[task1]access res1 begin\r\n");
		res1();
		printf("[task1]access res1 end\r\n");
		
			
		printf("[task1]access res2 begin\r\n");
		res2();
		printf("[task1]access res2 end\r\n");		

		OSMutexPost(&g_mutex_1,OS_OPT_POST_NONE,&err);
		OSMutexPost(&g_mutex_2,OS_OPT_POST_NONE,&err);		
	}
}


void task2(void *parg)
{
	OS_ERR err;
	
	printf("task2 is create ok\r\n");

	while(1)
	{
		
		OSMutexPend(&g_mutex_1,0,OS_OPT_PEND_BLOCKING,NULL,&err);
		OSMutexPend(&g_mutex_2,0,OS_OPT_PEND_BLOCKING,NULL,&err);
		printf("[task2]access res2 begin\r\n");
		res2();
		printf("[task2]access res2 end\r\n");
		
	
		
		printf("[task2]access res1 begin\r\n");
		res1();
		printf("[task2]access res1 end\r\n");		

		OSMutexPost(&g_mutex_1,OS_OPT_POST_NONE,&err);
		OSMutexPost(&g_mutex_2,OS_OPT_POST_NONE,&err);
	}
}

執行結果

task1執行完畢,task2執行,如此迴圈..。