ucos(九)互斥锁和死锁

一、概述

互斥锁,亦称:互斥信号量。
  在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个任务(线程)访问该对象(任务之间访问到相同的函数、相同的全局变量)。某个任务得到互斥锁后,就可以访问共享资源,其他任务等待该任务释放互斥锁才能进行访问。
  何时可以用普通信号量替代互斥锁?如果没有任务对共享资源访问有截止的时间,那么普通信号量可以替代互斥锁;反之则必须使用互斥锁。因为前者会造成无界优先级反转,后者却不会。
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void task(void *parg)
{
    while(1)
    {
        加锁
        访问共享资源
        解锁(立即)
 
        .....
        加锁
        访问共享资源
        解锁(立即)
        ....
 
    }
 
}  

二、函数接口

1.创建互斥锁

1
2
3
void  OSMutexCreate (OS_MUTEX  *p_mutex,
                     CPU_CHAR  *p_name,
                     OS_ERR    *p_err)
参数:
  • p_mutex,互斥锁对象
  • p_name,互斥锁名字
  • p_err,返回错误码,没有错误的就返回OS_ERR_NONE
 
返回值:无

 

2.等待互斥锁

 
若等待成功,则锁定共享资源
 
1
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.释放互斥锁,解锁

 
1
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,示例代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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、死锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
#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、避免死锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
#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 @   轻轻的吻  阅读(970)  评论(0编辑  收藏  举报
编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示