freeRTOS互斥量(mutex)

目录

前言

一、互斥量概述

二、互斥量函数

1.创建

2.其他函数

三、优先级反转示例

1.概念

2.代码示例

四、优先级继承 

1.概念

2.代码示例

五、递归锁

1.死锁的概念

2.自我死锁

3.函数

4.递归锁代码示例


前言

在之前的信号量中,我们想要实现互斥的效果,即独占的享用临界资源,假设厕所临界资源,怎么独占?自己开门上锁,完事了自己开锁。

但是freeRTOS的信号量并没有实现自己上的锁只能自己开这个功能,要想实现互斥,必须要有个前提条件:

        \bullet 没有坏人:别的任务不会"give"信号量(不撬门)

在我之前的文章关于Linux系统的互斥锁中,举得代码例子也是凭空拿了一把锁,这就说明要想通过信号量来实现互斥,需要程序员自己约定。


一、互斥量概述

可以看到,使用信号量确实也可以实现互斥访问,但是不完美。

使用互斥量可以解决这个问题,互斥量的名字取得很好:
        \bullet 量:值为0、1
        \bullet 互斥:用来实现互斥访问
它的核心在于:谁上锁,就只能由谁开锁。

很奇怪的是,FreeRTOS的互斥锁,并没有在代码上实现这点:
        \bullet 即使任务A获得了互斥锁,任务B竟然也可以释放互斥锁。
        \bullet 谁上锁、谁释放:只是约定。

互斥锁解决的核心问题其实是优先级反转优先级继承

互斥量其实就是一种特殊的二进制信号量,只不过它能解决优先级反转和实现优先级继承。


二、互斥量函数

1.创建

使用互斥量时,先创建、然后去获得、释放它。使用句柄来表示一个互斥量。

创建互斥量的函数有2种:动态分配内存,静态分配内存,函数原型如下:

/* 创建一个互斥量,返回它的句柄。
* 此函数内部会分配互斥量结构体
* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateMutex( void );

/* 创建一个互斥量,返回它的句柄。
* 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针
* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer
);

要想使用互斥量,需要在配置文件FreeRTOSConfig.h中定义:

#define configUSE_MUTEXES 1

注意:二级制信号量初始值是0,创建后需要Give一次;互斥量初始值是1,创建后不需要Give一次。 

2.其他函数

要注意的是,互斥量不能在ISR中使用。

各类操作函数,比如删除、give/take,跟一般是信号量是一样的。

/*
* xSemaphore: 信号量句柄,你要删除哪个信号量, 互斥量也是一种信号量
*/
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );

/* 释放 */
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );

/* 获得 */
BaseType_t xSemaphoreTake(
    SemaphoreHandle_t xSemaphore,
    TickType_t xTicksToWait
);

三、优先级反转示例

1.概念

假设任务A、B都想使用串口,A优先级比较低:
        \bullet 任务A获得了串口的互斥量
        \bullet 任务B也想使用串口,它将会阻塞、等待A释放互斥量
        \bullet 高优先级的任务,被低优先级的任务延迟,这被称为"优先级反转"(priority inversion)

互斥量可以通过"优先级继承",可以很大程度解决"优先级反转"的问题,这也是FreeRTOS中互斥量和二进制信号量的差别。

2.代码示例

main函数创建了3个任务:LPTask/MPTask/HPTask(低/中/高优先级任务),代码如下:

/* 互斥量/二进制信号量句柄 */
SemaphoreHandle_t xLock;
int main( void )
{
    prvSetupHardware();
    /* 创建二进制信号量!!!!!! */
    xLock = xSemaphoreCreateBinary();
    if( xLock != NULL )
    {
        /* 创建3个任务: LP,MP,HP(低/中/高优先级任务)
        */
        xTaskCreate( vLPTask, "LPTask", 1000, NULL, 1, NULL );
        xTaskCreate( vMPTask, "MPTask", 1000, NULL, 2, NULL );
        xTaskCreate( vHPTask, "HPTask", 1000, NULL, 3, NULL );
        /* 启动调度器 */
        vTaskStartScheduler();
    }
    else
    {
        /* 无法创建互斥量/二进制信号量 */
    }
    /* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
    return 0;
}

注意:创建的是二进制信号量,用于演示优先级反转!!!

LPTask/MPTask/HPTask三个任务的代码和运行过程如下图所示:

\bullet A:HPTask优先级最高,它最先运行。在这里故意打印,这样才可以观察到flagHPTaskRun的脉          冲。
\bullet HP Delay:HPTask阻塞
\bullet B:MPTask开始运行。在这里故意打印,这样才可以观察到flagMPTaskRun的脉冲。
\bullet MP Delay:MPTask阻塞
\bullet C:LPTask开始运行,获得二进制信号量,然后故意打印很多字符
\bullet D:HP Delay时间到,HPTask恢复运行,它无法获得二进制信号量,一直阻塞等待
\bullet E:MP Delay时间到,MPTask恢复运行,它比LPTask优先级高,一直运行。导致LPTask无法             运行,自然无法释放二进制信号量,于是HPTask无法运行。

总结:
\bullet LPTask先持有二进制信号量
\bullet 但是MPTask抢占LPTask,LPTask一直无法运行也就无法释放信号量
\bullet 导致HPTask任务无法运行
\bullet 优先级最高的HPTask竟然一直无法运行!

程序运行的时序图如下:


四、优先级继承 

1.概念

上一个代码的问题在于,LPTask低优先级任务获得了锁,但是它优先级太低而无法运行。

如果能提升LPTask任务的优先级,让它能尽快运行、释放锁,"优先级反转"的问题不就解决了吗?

优先级继承:
        \bullet 假设持有互斥锁的是任务A,如果更高优先级的任务B也尝试获得这个锁
        \bullet 任务B说:你既然持有宝剑,又不给我,那就继承我的愿望吧
        ​​​​​​​\bullet 于是任务A就继承了任务B的优先级
        ​​​​​​​\bullet 这就叫:优先级继承
        ​​​​​​​\bullet 等任务A释放互斥锁时,它就恢复为原来的优先级
        \bullet 互斥锁内部就实现了优先级的提升、恢复

2.代码示例

基于上一个代码,我们只需要做一个简单的修改

int main( void )
{
    prvSetupHardware();
    /* 创建互斥量 */
    //xLock = xSemaphoreCreateBinary( );
    xLock = xSemaphoreCreateMutex();

创建一个互斥量即可,运行时序图如下图所示:

​​​​​​​\bullet A:HPTask执行 xSemaphoreTake(xLock, portMAX_DELAY); ,它的优先级被LPTask继承
​​​​​​​\bullet B:LPTask抢占MPTask,运行
​​​​​​​\bullet C:LPTask执行 xSemaphoreGive(xLock); ,它的优先级恢复为原来值
​​​​​​​\bullet D:HPTask得到互斥锁,开始运行
​​​​​​​\bullet 互斥锁的"优先级继承",可以减小"优先级反转"的影响 


五、递归锁

1.死锁的概念

在我之前的博客讲互斥锁的时候也讲过什么情况下会造成死锁,例如,线程 A 持有锁 L1 并请求锁 L2,而线程 B 持有锁 L2 并请求锁 L1,这种情况可能导致死锁。所以我们编写程序时要注意,不要写成死锁的情况,等以后写一些大型的代码,需要同时操控几个互斥量时,可能会犯这种错误。

假设有2个互斥量M1、M2,2个任务A、B:
        ​​​​​​​​​​​​​​\bullet A获得了互斥量M1
        ​​​​​​​​​​​​​​\bullet B获得了互斥量M2
        ​​​​​​​​​​​​​​\bullet A还要获得互斥量M2才能运行,结果A阻塞
        ​​​​​​​​​​​​​​\bullet B还要获得互斥量M1才能运行,结果B阻塞
        ​​​​​​​​​​​​​​\bullet A、B都阻塞,再无法释放它们持有的互斥量
        ​​​​​​​\bullet 死锁发生!

2.自我死锁

假设这样的场景:
        ​​​​​​​\bullet 任务A获得了互斥锁M
        ​​​​​​​​​​​​​​\bullet 它调用一个库函数
        ​​​​​​​​​​​​​​\bullet 库函数要去获取同一个互斥锁M,于是它阻塞:任务A休眠,等待任务A来释放互斥锁!
        ​​​​​​​​​​​​​​\bullet 死锁发生!
一个任务申请锁后调用其他函数时申请同一个锁,导致要释放锁的任务阻塞,从而无法释放锁。

3.函数

怎么解决这类问题?可以使用递归锁(Recursive Mutexes),它的特性如下:
        ​​​​​​​​​​​​​​\bullet 任务A获得递归锁M后,它还可以多次去获得这个锁
        ​​​​​​​​​​​​​​\bullet "take"了N次,要"give"N次,这个锁才会被释放

递归锁的函数一般互斥量的函数名不一样,参数类型一样,列表如下:

函数原型如下:

/* 创建一个递归锁,返回它的句柄。
* 此函数内部会分配互斥量结构体
* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void );

/* 释放 */
BaseType_t xSemaphoreGiveRecursive( SemaphoreHandle_t xSemaphore );

/* 获得 */
BaseType_t xSemaphoreTakeRecursive(
    SemaphoreHandle_t xSemaphore,
    TickType_t xTicksToWait
);

注意:要使用递归锁,需要定义配置项configUSE_RECURSIVE_MUTEXES!!!

 4.递归锁代码示例

递归锁实现了:谁上锁就由谁解锁。

main函数里创建了2个任务
        ​​​​​​​​​​​​​​\bullet 任务1:高优先级,一开始就获得递归锁,然后故意等待很长时间,让任务2运行
        ​​​​​​​​​​​​​​\bullet 任务2:低优先级,看看能否操作别人持有的锁
main函数代码如下:

/* 递归锁句柄 */
SemaphoreHandle_t xMutex;
int main( void )
{
    prvSetupHardware();
    /* 创建递归锁 */
    xMutex = xSemaphoreCreateRecursiveMutex( );
    if( xMutex != NULL )
    {
        /* 创建2个任务: 一个上锁, 另一个自己监守自盗(看看能否开别人的锁自己用)
        */
        xTaskCreate( vTakeTask, "Task1", 1000, NULL, 2, NULL );
        xTaskCreate( vGiveAndTakeTask, "Task2", 1000, NULL, 1, NULL );
        /* 启动调度器 */
        vTaskStartScheduler();
    }
    else
    {
        /* 无法创建递归锁 */
    }
    /* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
    return 0;
}

两个任务经过精细设计,代码和运行流程如下图所示:
        ​​​​​​​​​​​​​​\bullet A:任务1优先级最高,先运行,获得递归锁
        ​​​​​​​​​​​​​​\bullet B:任务1阻塞,让任务2得以运行
        ​​​​​​​​​​​​​​\bullet C:任务2运行,看看能否获得别人持有的递归锁:不能
        ​​​​​​​​​​​​​​\bullet D:任务2故意执行"give"操作,看看能否释放别人持有的递归锁:不能
        ​​​​​​​​​​​​​​\bullet E:任务2等待递归锁
        ​​​​​​​​​​​​​​\bullet F:任务1阻塞时间到后继续运行,使用循环多次获得、释放递归锁
        ​​​​​​​​​​​​​​\bullet 递归锁在代码上实现了:谁持有递归锁,必须由谁释放

程序运行结果如下图所示:

  • 24
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
FreeRTOS中的互斥Mutex)是一种用于保护共享资源的同步机制。它可以确保在任何给定时刻只有一个任务可以访问被保护资源,以避免竞争条件和数据损坏。 在FreeRTOS中,互斥通过以下API函数进行创建、获取和释放: 1. `xSemaphoreCreateMutex()`:用于创建一个互斥,并返回一个指向该互斥的句柄。 2. `xSemaphoreTake()`:用于获取(锁定)互斥。如果互斥当前未被锁定,则任务可以获取互斥并继续执行;否则,任务将被阻塞,直到互斥可用。 3. `xSemaphoreGive()`:用于释放(解锁)互斥。一旦任务完成了对共享资源的访问,应该调用此函数来释放互斥,以允许其他任务获取它。 使用互斥的示例代码如下: ```c // 创建互斥句柄 SemaphoreHandle_t mutex = xSemaphoreCreateMutex(); // 任务1 void Task1(void *pvParameters) { while (1) { // 获取互斥 if (xSemaphoreTake(mutex, portMAX_DELAY) == pdTRUE) { // 访问共享资源 // ... // 释放互斥 xSemaphoreGive(mutex); } } } // 任务2 void Task2(void *pvParameters) { while (1) { // 获取互斥 if (xSemaphoreTake(mutex, portMAX_DELAY) == pdTRUE) { // 访问共享资源 // ... // 释放互斥 xSemaphoreGive(mutex); } } } ``` 上述示例中,两个任务(Task1和Task2)通过获取互斥来保护共享资源的访问。只有当一个任务成功获取互斥时,才能执行对共享资源的访问操作,其他任务将被阻塞等待互斥的释放。一旦任务完成了对共享资源的访问,就应该释放互斥,以便其他任务可以获取它。 这就是FreeRTOS互斥的基本用法。通过使用互斥,可以有效地管理多个任务对共享资源的访问,确保数据的一致性和正确性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sakabu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值