Skip to content

信号量与互斥量

6.1 信号量的本质

信号量本质上时特殊的队列,下面时对应的差异:

队列

  • 读队列:① 拷贝数据。 ② CNT-- 。 ③唤醒等待接收数据者。
  • 写队列:① 拷贝数据。 ② CNT++ 。 ③唤醒等待接收数据者。
  • 读队列时,队列为空 可以阻塞
  • 写队列时,队列已满 可以阻塞

信号量

  • 给出资源(Give):① CNT++。 ② 唤醒等待者。
  • 获得资源(Take):① CNT--。(没有唤醒操作)
  • Give:计数值已经达到最大时返回失败, 不阻塞
  • Take:没有资源获取时, 可以阻塞

6.2 信号量函数

6.2.1 创建信号量

两种信号量差异:

二进制信号量 计数型信号量
只有 0 或 1 两个值,被创建时初始值为 0 计数值自定义,且被创建时初始值可以设定
其他操作是一样的 其他操作是一样的

计数型信号量

C
/* Creates a new counting semaphore instance, and returns a handle by which the
 * new counting semaphore can be referenced. 
 */
SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount, UBaseType_t uxInitialCount )

二进制信号量

C
/* Creates a new binary semaphore instance, and returns a handle by which the
 * new semaphore can be referenced. 
 */
SemaphoreHandle_t xSemaphoreCreateBinary( void )

6.2.2 获取/释放信号量

获取信号量

C
/* to obtain a semaphore. */
xSemaphoreTake(
        SemaphoreHandle_t xSemaphore,
        TickType_t xBlockTime
               );
/* to obtain a semaphore from an ISR */   
xSemaphoreTakeFromISR(
        SemaphoreHandle_t xSemaphore,
        BaseType_t *pxHigherPriorityTaskWoken
                      );

释放信号量

C
/*  to release a semaphore. */
xSemaphoreGive( SemaphoreHandle_t xSemaphore );

/*  to release a semaphore from an ISR */
xSemaphoreGiveFromISR(
        SemaphoreHandle_t xSemaphore,
        BaseType_t *pxHigherPriorityTaskWoken
                      );

6.2.3 删除信号量

6.3 使用信号量

6.3.1 创建、获取与释放示例

在队列最后一个示例的基础上:

定义信号量句柄

C
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
static SemaphoreHandle_t g_xSemaphoreHandle;
/* USER CODE END Variables */

创建信号量,最大计数为 5 ,可用量为 2 ;并将任务注释一个

C
void MX_FREERTOS_Init(void) {
    /* USER CODE BEGIN Init */
    g_xSemaphoreHandle = xSemaphoreCreateCounting(4, 2);
    /* USER CODE END Init */
    ...
    /* USER CODE BEGIN RTOS_THREADS */
    /* add threads, ... */
    xTaskCreate(OLED_PrintTask, "Task1", 128, &g_car[0], osPriorityNormal, NULL);
    xTaskCreate(OLED_PrintTask, "Task2", 128, &g_car[1], osPriorityNormal, NULL);
    xTaskCreate(OLED_PrintTask, "Task3", 128, &g_car[2], osPriorityNormal, NULL);
//    xTaskCreate(OLED_PrintTask, "Task4", 128, &g_car[3], osPriorityNormal, NULL);

}

修改任务函数,取消队列,添加信号量获取与释放

C
void OLED_PrintTask(void *params) {
    Car *car = params;
    ShowCar(car);
    /* 获取信号量 */
    xSemaphoreTake(g_xSemaphoreHandle, portMAX_DELAY);
    while (1) {
        /* 读队 */
        HideCar(car);
        car->x += 1;
        if (car->x > 128 ) {car->x = 120;}
        ShowCar(car);
        vTaskDelay(100);
        if (car->x == 120) {
            /* 释放信号量 */
            xSemaphoreGive(g_xSemaphoreHandle);
            vTaskDelete(NULL);
        }
    }
}

实验现象,上两行字符先移动,到达终点释放信号量,第三行字符开始移动。

6.3.2 信号量分配优先级

  1. 优先级高的,后到可以排前面。
  2. 优先级一样高的,按创建任务的先后分配信号量,后创建的等待信号量释放。

6.3.3 优先级反转

什么时优先级反转,就是低优先级的任务卡住了高优先级任务,使其无法运行,如下面的例子:

① 低优先级任务:占用唯一一个信号量实验。
② 中优先级任务:没有阻塞态,一直处于可运行状态。(一旦此任务运行,则低优先级任务任务没有运行机会)
③ 高优先级任务:需要信号量,得不到信号量就阻塞。

复现上诉案例:

使用二进制信号量,并修改任务优先级

C
void MX_FREERTOS_Init(void) {
    /* USER CODE BEGIN Init */
    g_xSemaphoreHandle = xSemaphoreCreateBinary();
    // 释放一个信号量
    xSemaphoreGive(g_xSemaphoreHandle);
    /* USER CODE END Init */
    ...
    /* USER CODE BEGIN RTOS_THREADS */
    /* add threads, ... */
    xTaskCreate(OLED_PrintTask1, "Task1", 128, &g_car[0], osPriorityNormal, NULL);
    xTaskCreate(OLED_PrintTask2, "Task2", 128, &g_car[1], osPriorityNormal+1, NULL);
    xTaskCreate(OLED_PrintTask3, "Task3", 128, &g_car[2], osPriorityNormal+2, NULL);
    /* USER CODE END RTOS_THREADS */
    ...
}

修改任务函数

C
void OLED_PrintTask1(void *params) {
    Car *car = params;
    ShowCar(car);
    xSemaphoreTake(g_xSemaphoreHandle, portMAX_DELAY);
    while (1) {
        /* 读队 */
        HideCar(car);
        car->x += 1;
        if (car->x > 128 ) {car->x = 120;}
        ShowCar(car);
        vTaskDelay(100);
        if (car->x == 120) {
            xSemaphoreGive(g_xSemaphoreHandle);
            vTaskDelete(NULL);
        }
    }
}

void OLED_PrintTask2(void *params) {
    Car *car = params;
    ShowCar(car);
    vTaskDelay(1000);
//    xSemaphoreTake(g_xSemaphoreHandle, portMAX_DELAY);
    while (1) {
        /* 读队 */
        HideCar(car);
        car->x += 1;
        if (car->x > 128 ) {car->x = 120;}
        ShowCar(car);
//        vTaskDelay(100);
        HAL_Delay(100);
        if (car->x == 120) {
//            xSemaphoreGive(g_xSemaphoreHandle);
            vTaskDelete(NULL);
        }
    }
}
void OLED_PrintTask3(void *params) {
    Car *car = params;
    ShowCar(car);
    vTaskDelay(2000);
    xSemaphoreTake(g_xSemaphoreHandle, portMAX_DELAY);
    while (1) {
        /* 读队 */
        HideCar(car);
        car->x += 1;
        if (car->x > 128 ) {car->x = 120;}
        ShowCar(car);
        vTaskDelay(100);
        if (car->x == 120) {
            xSemaphoreGive(g_xSemaphoreHandle);
            vTaskDelete(NULL);
        }
    }
}

6.4 互斥量

6.4.1 使用场景

  1. 解决优先级反转问题,临时提拔低优先级任务的优先级和高优先级一样。
  2. 保护临界资源。

6.4.2 互斥量函数

C
/* 创建 */
SemaphoreHandle_t xSemaphoreCreateMutex( void );
C
/* 释放 */
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
C
/* 获得 */
BaseType_t xSemaphoreTake(
                   SemaphoreHandle_t xSemaphore,
                   TickType_t xTicksToWait
               );

6.4.3 解决优先级反转

将二进制信号量的创建改成互斥量即可:

C
void MX_FREERTOS_Init(void) {
    /* USER CODE BEGIN Init */
    g_xSemaphoreHandle = xSemaphoreCreateMutex();
    xSemaphoreGive(g_xSemaphoreHandle);
}

6.4.4 保护临界资源

待补充

推荐阅读:第13章 互斥量(mutex) | 百问网