ucos(九)互斥锁和死锁

一、概述

互斥锁,亦称:互斥信号量。
  在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个任务(线程)访问该对象(任务之间访问到相同的函数、相同的全局变量)。某个任务得到互斥锁后,就可以访问共享资源,其他任务等待该任务释放互斥锁才能进行访问。
  何时可以用普通信号量替代互斥锁?如果没有任务对共享资源访问有截止的时间,那么普通信号量可以替代互斥锁;反之则必须使用互斥锁。因为前者会造成无界优先级反转,后者却不会。
0
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执行,如此循环..。

 

 

 

 

 

 

 

 
 
 
 
 
posted @ 2021-09-20 11:20  轻轻的吻  阅读(932)  评论(0编辑  收藏  举报