一、概述
互斥锁,亦称:互斥信号量。 在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个任务(线程)访问该对象(任务之间访问到相同的函数、相同的全局变量)。某个任务得到互斥锁后,就可以访问共享资源,其他任务等待该任务释放互斥锁才能进行访问。 何时可以用普通信号量替代互斥锁?如果没有任务对共享资源访问有截止的时间,那么普通信号量可以替代互斥锁;反之则必须使用互斥锁。因为前者会造成*优先级反转,后者却不会。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占有了。此时两个任务便死锁了,谁也无法继续运行,因为谁也无法获取对方的资源。 避免出现死锁的方法,让每个任务都:
- 先得到全部需要的资源,再做下一个动作
- 用相同的顺序申请多个资源
- 在调用请求互斥锁的函数时设定超时时间
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执行,如此循环..。