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