ucos(9)互斥鎖和死鎖
阿新 • • 發佈:2021-09-20
一、概述
互斥鎖,亦稱:互斥訊號量。 在程式設計中,引入了物件互斥鎖的概念,來保證共享資料操作的完整性。每個物件都對應於一個可稱為“互斥鎖”的標記,這個標記用來保證在任一時刻,只能有一個任務(執行緒)訪問該物件(任務之間訪問到相同的函式、相同的全域性變數)。某個任務得到互斥鎖後,就可以訪問共享資源,其他任務等待該任務釋放互斥鎖才能進行訪問。 何時可以用普通訊號量替代互斥鎖?如果沒有任務對共享資源訪問有截止的時間,那麼普通訊號量可以替代互斥鎖;反之則必須使用互斥鎖。因為前者會造成無界優先順序反轉,後者卻不會。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執行,如此迴圈..。