Skip to content

同步互斥

4.1 同步互斥概念

对于资源C,使用者A和B。

同步 :当A正在使用C时,B必须等待A使用完,其中A、B有依赖关系,即B依赖A产生的数据完成自己的任务。

互斥 :当A正在使用C时,B不能使用C,强调的时资源C同一时间只能有一个任务使用。

4.2 有缺陷的同步示例

在创建FreeRTOS项目时,由于RTOS时钟使用的是SysTick,我选用的系统时钟为TIM6

image-20250113111220739

借鉴百问网时钟驱动程序,我们以TIM6为时基,创建一个获取系统时间的驱动:

timer.h

C
#ifndef ROBOT_TIMER_H
#define ROBOT_TIMER_H
#include "stm32f1xx_hal.h"

uint64_t system_get_ns(void);

#endif //ROBOT_TIMER_H

timer.c

C
#include "timer.h"


/**********************************************************************
 * 函数名称: system_get_ns
 * 功能描述: 获得系统时间(单位ns)
 * 输入参数: 无
 * 输出参数: 无
 * 返 回 值: 系统时间(单位ns)
 * 修改日期        版本号     修改人        修改内容
 * -----------------------------------------------
 * 2023/08/03        V1.0     韦东山         创建
 ***********************************************************************/
uint64_t system_get_ns(void)
{
    //extern uint32_t HAL_GetTick(void);
    extern TIM_HandleTypeDef        htim6;
    TIM_HandleTypeDef *hHalTim = &htim6;

    uint64_t ns = HAL_GetTick();
    uint64_t cnt;
    uint64_t reload;

    cnt = __HAL_TIM_GET_COUNTER(hHalTim);
    reload = __HAL_TIM_GET_AUTORELOAD(hHalTim);

    ns *= 1000000;
    ns += cnt * 1000000 / reload;
    return ns;
}

任务函数:

C
/* 同步示例 */
void Task_A(void *argument)
{
    uint32_t i;
    OLED_ShowStr(0, 0, "Waiting", 16);
    g_time = system_get_ns();
    for (i = 0 ; i < 10000000 ; i++ )
    {
        g_cnt += i;
    }
    g_time = system_get_ns() - g_time;
    g_count_end = 1;
    vTaskDelete(NULL);
}
void Task_B(void *argument)
{
    OLED_ShowStr(0, 0, "Waiting", 16);
//    vTaskDelay(3000);
    while (g_count_end ==0);
    OLED_Clear();
    OLED_ShowNum(0, 0, g_time/1000000, 11, 16);
    OLED_ShowNum(0, 2, g_cnt, 11, 16);
    vTaskDelete(NULL);
}
/* USER CODE END Application */

其中全局变量定义:

C
// 同步示例全局变量
static volatile uint8_t g_count_end = 0;
static uint32_t g_cnt=0;
static uint64_t g_time=0;
C
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
void Task_B(void *argument);
void Task_A(void *argument);
/* USER CODE END FunctionPrototypes */
C
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
xTaskCreate(Task_A, "TaskA", 128, NULL, osPriorityNormal, NULL);
xTaskCreate(Task_B, "TaskB", 128, NULL, osPriorityNormal, NULL);
/* USER CODE END RTOS_THREADS */

最后的输出结果:

Text Only
时间:5880 ms
计数值:1228070726412

Task_B中等待为死循环,不进入阻塞态,因此会频繁被切换到Task_B却只是做延时任务,这便是我们同步示例的缺陷。在死循环前加上vTaskDelay(3000);就会节省近一半时间:

C
void Task_B(void *argument)
{
    OLED_ShowStr(0, 0, "Waiting", 16);
    vTaskDelay(6000);
    while (g_count_end ==0);
    OLED_Clear();
    OLED_ShowNum(0, 0, g_time/1000000, 11, 16);
    OLED_ShowNum(0, 2, g_cnt, 11, 16);
    vTaskDelete(NULL);
}

输出:

Text Only
时间:2969 ms
计数值:1228070726412

4.3 有缺陷的互斥示例

下面是一个用全局变量g_status实现互斥的示例,但前面我们提到它是有缺陷的❓:

假设如下场景:有两个任务A、B都想调用OLED_Print_Task,任务A执行到第4行代码时发现g_status为1,可以进入if语句块,它还没执行第6句指令就被切换出去了;然后任务B也调用OLED_Print_Task,任务B执行到第4行代码时也发现bCanUse为1,也可以进入if语句块使用OLED。在这种情况下,使用全局变量并不能实现互斥操作。

C
void OLED_Print_Task(void *argument) {
    Params *p = argument;
    uint32_t cnt = 0;
    while (1) {
        if (g_status) {
            g_status = 0;
            OLED_ShowStr(p->x, p->y, p->str, 16);
            OLED_ShowNum(p->x + 6 * 8, p->y, cnt, 3, 16);
            cnt++;
            g_status = 1;
        }
        vTaskDelay(500);
    }
}

此方法不能保证一定实现互斥,原因在于:在判断过程中,被打断了。如果在确保在判断时不被中断打断,就可以解决这一问题,因此优化代码如下:

C
void OLED_Print_Task(void *argument) {
    Params *p = argument;
    uint32_t cnt = 0;
    while (1) {
        disable_irq();
        if (g_status) {
            g_status = 0;
            OLED_ShowStr(p->x, p->y, p->str, 16);
            OLED_ShowNum(p->x + 6 * 8, p->y, cnt, 3, 16);
            cnt++;
            g_status = 1;
        }
        enable_irq();
        vTaskDelay(500);
    }
}

然而,使用关闭中断的方法保护临界资源会出现浪费CPU资源情况:

假设,两个任务调用OLED_Print_Task,而每次都是A成功打印,B一直判断失败,此时B一直判断失败的时间内就会造成CPU浪费。

image-20250113133703505

我们期望的结果时,B判断失败后进入阻塞态,等A执行完OLED_Print_Task,唤醒B。

image-20250113134203586

4.4 FreeRTOS解决方法

图片来源:第10章 同步互斥与通信 | 百问网

能实现同步、互斥的内核方法有: 任务通知(Task Notification)队列(Queue)事件组(Event Group)信号量(Semaphore)互斥量(Mutex)

img