一,互斥信号量
互斥信号量是一个具有优先级继承的二值信号量,在同步的应用中二值信号量最合适。互斥信号量适合互斥访问的那些应用。在互斥访问中互斥信号量相当于一个钥匙,当一个任务使用这个资源,资源就会被上锁,防止其他资源去使用它,当这个任务使用完毕,就会开锁,然后别的任务就可以访问该资源。但这样存在一个任务优先级翻转的问题,需要优先级继承算法来解决。
二,优先级翻转
优先级继承算法指的是暂时提高某个占用某种资源的低优先级的任务,使得在等待该资源的任务中优先级最高的任务优先级相等,当原本占有资源的任务释放了资源后,优先级恢复为原来的设定值。在系统中,一般高优先级的任务就代表了这个任务越重要,越要优先执行,但在互斥访问中,高优先级任务访问的资源被上锁,导致变为挂起态,在freertos中,紧急的任务无法得到快速处理,则会对系统产生很大的危害。
当一个低优先级的任务占用一个资源,当前资源被上锁。当高优先级的任务来访问,因为资源被上锁,所以它要进入阻塞态【在这里选择阻塞态还是挂起态,其实要根据实际情况而定,当在多任务共享资源的情境下,当两个任务同时访问一个资源的时候,且当前的资源被另外一个任务占领,另外两个任务就会进入阻塞态,不断的进行自旋等待,不断的访问信号量,当信号量来了,再转为就绪态,这样可能会存在卡死的情况,如果进入的是挂起态,任务会释放CPU,当资源被释放的时候,任务再被唤醒,转为就绪态等待被调度,这样避免了无限扽等待以及卡死的情况,所以在多任务共享资源下,挂起态则是更优选】,此时优先级的继承机制就要发挥作用了,把当前占用资源的低优先级的任务提升到和高优先级任务一样的优先级。这样可以确保高优先级的任务提早进入阻塞态。
·任务L正常运行,占用资源。
·任务H想访问资源,任务H优先级高于L,但是呢,因为任务L将资源上锁,所以任务H只能进入阻塞态
·任务M因为优先级高于任务L,会抢占CPU的使用权,当任务M完成后,任务L继续运行
·当任务L运行结束,释放资源,任务H才开始运行,这相当于任务H的优先级低于任务M了,任务H的阻塞时间相当于是任务L的运行时间+任务M的运行时间。
优化:当任务H也需要使用这个共享资源的时候,将任务L的优先级提升到和任务H一样的优先级,这样任务L就不会被抢占,间接可以让任务H的阻塞时间变短,任务H的阻塞时间只有任务L的运行时间。
但我也有了一个疑问,当任务M的优先级高于任务H的时候,这样提升优先级的意义好像没啥必要,所以去问了一下chatGPT3.5。
额外注意的是,互斥量只可以用于任务之间,不可以在中断内使用。
三,互斥量的应用场景
1.共享资源:当多个任务访问同一资源,需要使用互斥量来保证只有一个任务来访问当前的资源。
2.临界区:当多个任务再进入代码的共享区域的时候,需要使用互斥量来保护临界区,确保同一时刻的临界区只可以被一个任务访问。
3.任务队列:当多个线程需要从同一个任务队列获取任务并且执行的时候,需要使用互斥量来保证同一时刻只有一个线程可以访问任务队列。
优先级翻转代码理解:
进入高优先级任务,但是立马就被阻塞了
//高优先级任务的任务函数
void high_task(void *pvParameters)
{
u8 num;
POINT_COLOR = BLACK;
LCD_DrawRectangle(5,110,115,314); //画一个矩形
LCD_DrawLine(5,130,115,130); //画线
POINT_COLOR = BLUE;
LCD_ShowString(6,111,110,16,16,"High Task");
while(1)
{
vTaskDelay(500); //延时500ms,也就是500个时钟节拍
num++;
printf("high task Pend Sem\r\n");
xSemaphoreTake(MutexSemaphore,portMAX_DELAY); //获取互斥信号量
printf("high task Running!\r\n");
LCD_Fill(6,131,114,313,lcd_discolor[num%14]); //填充区域
LED1=!LED1;
xSemaphoreGive(MutexSemaphore); //释放信号量
vTaskDelay(500); //延时500ms,也就是500个时钟节拍
}
}
CPU运行中优先级任务,在阻塞阶段运行低优先级任务,低优先级任务获取信号量,且未释放,高优先级任务抢占低优先级任务,却无法获得信号量进入阻塞阶段,导致不断的打印“middle task Running!”,只有在中优先级任务阻塞阶段,低优先级释放了信号量,高优先级才会被执行
//中等优先级任务的任务函数
void middle_task(void *pvParameters)
{
u8 num;
POINT_COLOR = BLACK;
LCD_DrawRectangle(125,110,234,314); //画一个矩形
LCD_DrawLine(125,130,234,130); //画线
POINT_COLOR = BLUE;
LCD_ShowString(126,111,110,16,16,"Middle Task");
while(1)
{
num++;
printf("middle task Running!\r\n");
LCD_Fill(126,131,233,313,lcd_discolor[13-num%14]); //填充区域
LED0=!LED0;
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
}
}
//低优先级任务的任务函数
void low_task(void *pvParameters)
{
static u32 times;
while(1)
{
xSemaphoreTake(MutexSemaphore,portMAX_DELAY); //获取互斥信号量
printf("low task Running!\r\n");
for(times=0;times<5000000;times++) //模拟低优先级任务占用互斥信号量
{
taskYIELD(); //发起任务调度
}
xSemaphoreGive(MutexSemaphore); //释放互斥信号量
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
}
}
互斥量实验代码理解:
虽然后才开始优先级高的先执行,但是在循环的时候进行了阻塞
//高优先级任务的任务函数
void high_task(void *pvParameters)
{
u8 num;
POINT_COLOR = BLACK;
LCD_DrawRectangle(5,110,115,314); //画一个矩形
LCD_DrawLine(5,130,115,130); //画线
POINT_COLOR = BLUE;
LCD_ShowString(6,111,110,16,16,"High Task");
while(1)
{
vTaskDelay(500); //延时500ms,也就是500个时钟节拍
num++;
printf("high task Pend Sem\r\n");
xSemaphoreTake(MutexSemaphore,portMAX_DELAY); //获取互斥信号量
printf("high task Running!\r\n");
LCD_Fill(6,131,114,313,lcd_discolor[num%14]); //填充区域
LED1=!LED1;
xSemaphoreGive(MutexSemaphore); //释放信号量
vTaskDelay(500); //延时500ms,也就是500个时钟节拍
}
}
所以先运行中优先级,在中优先级阻塞的时候,运行低优先级【此时高优先级还在延时等待】,低优先级获取信号量,此时高优先级转为就绪态,抢占低优先级,但低优先级的任务被提升到和互斥优先级任务一样的优先级,所以不会打印“middle task Running!”,当低优先级释放信号量后,高优先级直接开始运行。
//中等优先级任务的任务函数
void middle_task(void *pvParameters)
{
u8 num;
POINT_COLOR = BLACK;
LCD_DrawRectangle(125,110,234,314); //画一个矩形
LCD_DrawLine(125,130,234,130); //画线
POINT_COLOR = BLUE;
LCD_ShowString(126,111,110,16,16,"Middle Task");
while(1)
{
num++;
printf("middle task Running!\r\n");
LCD_Fill(126,131,233,313,lcd_discolor[13-num%14]); //填充区域
LED0=!LED0;
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
}
}
//低优先级任务的任务函数
void low_task(void *pvParameters)
{
static u32 times;
while(1)
{
xSemaphoreTake(MutexSemaphore,portMAX_DELAY); //获取互斥信号量
printf("low task Running!\r\n");
for(times=0;times<5000000;times++) //模拟低优先级任务占用互斥信号量
{
taskYIELD(); //发起任务调度
}
xSemaphoreGive(MutexSemaphore); //释放互斥信号量
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
}
}