FreeRTOS从入门到精通 第十三章(信号量)

参考教程:【正点原子】手把手教你学FreeRTOS实时系统_哔哩哔哩_bilibili

一、信号量知识回顾

1、概述

(1)信号量是一种解决同步问题的机制,可以实现对共享资源的有序访问,FreeRTOS中使用的是二值信号量、计数型信号量与互斥信号量。

(2)以计数型信号量进行举例说明:

①计数值大于0,代表有信号量资源。当获取信号量,即将资源分配给一个任务时,信号量计数值(资源数)减一,也即可分配的资源数减一;当释放信号量,即任务将资源归还给OS时,信号量计数值(资源数)加一,也即可分配的资源数加一。

②信号量的计数值都有最大值限制,如果最大值被限定为1,那么它就是二值信号量,如果最大值不是1,它就是计数型信号量。

(3)队列与信号量的对比:

|------------------------------------------|-----------------------------------|
| 队列 | 信号量 |
| ①可以容纳多个数据 ②创建队列有两部分内存------队列结构体与队列项存储空间 | ①仅存放计数值,无法存放其数据 ②创建信号量,只需分配信号量结构体 |
| 写入队列:队列项数目++ 当队列满时,可阻塞 | 释放信号量:不可阻塞,计数值++ 当计数值为最大值时,返回失败信息 |
| 读取队列:队列项数目-- 当队列为空时,可阻塞 | 获取信号量:计数值-- 当没有资源时,可阻塞 |

(4)以下为操作系统理论关于信号量的理论知识,仅供参考,FreeRTOS并不全部涉及。

2、整型信号量

(1)整型信号量定义为一个用于表示资源数目的整型量S,它与一般整型量不同,除初始化外,仅能通过两个标准的原子操作wait(S)和signal(S)来访问,这两个操作分别称为P、V操作。

(2)只要是信号量S≤0,就会不断地测试,因此该机制并未遵循"让权等待"的准则,而是使进程处于"忙等"的状态。

3、记录型信号量

(1)用一个整型变量value表示资源数目,然后增加一个进程链表指针list,用于链接所有等待进程。上述两数据项可描述如下:

typedef struct

{

int value; //可用资源数目

struct process_control_block *list;

}semaphore;

(2)wait(S)和signal(S)操作的描述:

①对信号量的每次wait操作,意味着进程请求一个单位的该类资源,使系统中可供分配的该类资源数减少一个,因此描述为S->value--;当S->value<0时,表示该类资源已分配完毕,因此进程应调用block原语进行自我阻塞,放弃处理机,并插入到信号量链表S->list中,此时S->value的绝对值表示在该信号量链表中已阻塞进程的数目。可见,该机制遵循了"让权等待"准则。

②对信号量的每次signal操作表示执行进程释放一个单位资源,使系统中可供分配的该类资源数增加一个,故S->value++操作表示资源数目加1;若加1后仍是S-> value<=0,则表示在该信号量链表中仍有等待该资源的进程被阻塞,故还应调用wakeup原语,将S -> list链表中的第一个等待进程唤醒。

③如果S-> value 的初值为1,表示只允许一个进程访问临界资源,此时的信号量转化为互斥信号量,用于进程互斥。

4、AND型信号量

(1)假定现有两个进程A和B,它们都要求访问共享数据D和E,当然,共享数据都应作为临界资源,为此,可为这两个数据分别设置用于互斥的信号量Dmutex和Emutex,并令它们的初值都是1,相应地,在两个进程中都要包含两个对Dmutex和Emutex 的操作,即

如果进程A访问D之后,切换为进程B访问E,那么之后进程A需要访问E,而E此时正在被进程B访问,进程A无法继续进行,但是进程B需要访问的D此时又被A占用,进程B也无法继续进行,而它们也不释放自己已占用的资源,于是二者容易发生进程死锁。显然,当进程同时要求的共享资源越多,发生进程死锁的可能性就越大。

(2)AND同步机制的基本思想是:将进程在整个运行过程中需要的所有资源一次性全部地分配给进程,待进程使用完后再一起释放,只要尚有一个资源未能分配给进程,其它所有可能为之分配的资源也不分配给它。亦即,对若干个临界资源的分配采取原子操作方式,要么把它所请求的资源全部分配到进程,要么一个也不分配。

(3)AND同步机制在wait操作中增加了一个"AND"条件,那么Swait(Simultaneous wait)和Ssignal(Simultaneous signal)的定义如下:

Swait(S1, S2, ..., Sn)

{

while(TRUE)

{

if(Si >= 1 && ... && Sn >= 1) //资源全部空闲才能进行分配

{

for(i = 1; i <= n; i++) //资源逐一分配

Si--;

break; //结束等待资源的循环

}

else

{

Place the process in the waiting queue associated with the first Si found with

Si < 1, and set the progress count of this process to the beginning of Swait operation

}

}

}
Ssignal(S1, S2, ..., Sn)

{

while(TRUE)

{

for(i = 1; i <= n; i++) //资源逐一释放

{

Si++;

Remove all the process waiting in the queue associated

with Si into the ready queue

}

}

}

5、信号量集

(1)对AND信号量机制加以扩充,对进程所申请的所有资源以及类资源不同的资源需求量,在一次P、V 原语操作中完成申请或释放。++++进程对信号量的测试值不再是1,而是该资源的分配下限值,即要求,否则不予分配;一旦允许分配,进程对该资源的需求值为,即表示资源占用量,进行++++ ++++操作++++,由此形成一般化的"信号量集"机制。

(2)"信号量集"机制对应的Swait和Ssignal格式:

Swait(S1, t1, d1, ..., Sn, tn, dn)

{

while(TRUE)

{

if(Si >= ti && ... && Sn >= ti) //待足够空闲资源之后才能进行分配

{

for(i = 1; i <= n; i++) //资源分配(每次循环分配一种资源)

{

Si = Si - di;

}

}

else

{

Place the executing process in the waiting queue of the first Si with

Si < ti, and set its program counter to the beginning of the Swait operation

}

}

}
Ssignal(S1, t1, d1, ..., Sn, tn, dn)

{

while(TRUE)

{

for(i = 1; i <= n; i++) //资源释放(每次循环释放一种资源)

{

Si = Si + di;

Remove all the process waiting in the queue associated

with Si into the ready queue

}

}

}

(3)一般"信号量集"的几种特殊情况:

①Swait(S, d, d),只有一个信号量S,允许每次申请d个资源,若现有资源数少于d,不予分配。

②Swait(S, 1, 1),蜕化为一般的记录型信号量(S>1时)或互斥信号量(S=1时)。

③Swait(S, 1, 0),当S>=1时,允许多个进程进入某特定区,当S变为0后,阻止任何进程进入某特定区,相当于可控开关。

二、二值信号量

1、二值信号量概述

(1)二值信号量的本质是一个队列长度为1的队列 ,该队列就只有空和满两种情况。

(2)使用二值信号量的过程:创建二值信号量→释放二值信号量→获取二值信号量。

(3)二值信号量通常用于互斥访问或任务同步,与互斥信号量比较类似,但是二值信号量有可能会导致优先级翻转的问题,所以二值信号量更适合用于同步。

2、二值信号量相关API函数

(1)二值信号量相关API函数概览:

|--------------------------------|---------------|
| 函数 | 描述 |
| xSemaphoreCreateBinary() | 使用动态方式创建二值信号量 |
| xSemaphoreCreateBinaryStatic() | 使用静态方式创建二值信号量 |
| xSemaphoreGive() | 释放信号量 |
| xSemaphoreGiveFromISR() | 在中断中释放信号量 |
| xSemaphoreTake() | 获取信号量 |
| xSemaphoreTakeFromISR() | 在中断中获取信号量 |

(2)xSemaphoreCreateBinary函数:

①函数定义:

cpp 复制代码
#define xSemaphoreCreateBinary( )   						\
xQueueGenericCreate(1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE)    //创建一个长度为1的信号量型队列
#define semSEMAPHORE_QUEUE_ITEM_LENGTH  ( (uint8_t) 0U )  //队列项的大小为0

②返回值:

|------|----------------|
| 返回值 | 描述 |
| NULL | 创建失败 |
| 其它值 | 创建成功返回二值信号量的句柄 |

(3)xSemaphoreGive函数:

①函数定义:

cpp 复制代码
#define xSemaphoreGive (xSemaphore)    						\
xQueueGenericSend((QueueHandle_t) (xSemaphore), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK)   //xSemaphore为要释放的信号量句柄(由于不需要写数据,故数据参数传入NULL即可)
#define semGIVE_BLOCK_TIME    ((TickType_t )0U)   //阻塞时间为零

②返回值:

|---------------|---------|
| 返回值 | 描述 |
| pdPASS | 释放信号量成功 |
| errQUEUE_FULL | 释放信号量失败 |

(4)xSemaphoreTake函数:

①函数定义:

cpp 复制代码
BaseType_t xSemaphoreTake
(
    xSemaphore,     //要获取的信号量句柄
    xBlockTime     //阻塞时间(与从队列中读数据的阻塞时间意义相同)
)

②返回值:

|---------|------------|
| 返回值 | 描述 |
| pdTRUE | 获取信号量成功 |
| pdFALSE | 超时,获取信号量失败 |

三、二值信号量实验

1、原理图与实验目标

(1)原理图:

(2)实验目标:

①设计4个任务------start_task、task1、task2、task3:

1\]start_task:用于创建task1、task2和task3任务。 \[2\]task1:当获取到LED1的硬件资源后,控制LED1约每500ms完成亮暗翻转的状态切换,每完成一次即将资源释放。 \[3\]task2:当获取到LED2的硬件资源后,控制LED2约每1000ms完成亮暗翻转的状态切换,每完成一次即将资源释放。 \[4\]task3:按下按键1,获取(或者说霸占)LED1和LED2的硬件资源;按下按键2,释放LED1和LED2的硬件资源。 ③预期实验现象: \[1\]程序下载到板子上后,两个LED灯闪烁。 \[2\]按下按键1,LED1和LED2停止闪烁。 \[3\]按下按键2,LED1和LED2恢复闪烁。 ### 2、实验步骤 (1)将"任务创建和删除的动态方法实验"的工程文件夹复制一份,在拷贝版中进行实验。 (2)在FreeRTOS_experiment.c文件中添加头文件semphr.h,并定义两个队列句柄(分别为LED1资源的信号量和LED2资源的信号量)。 ```cpp #include "semphr.h" QueueHandle_t LED1_resources; //LED1信号量的队列句柄 QueueHandle_t LED2_resources; //LED2信号量的队列句柄 ``` (3)在FreeRTOS_Test函数中需要创建LED1资源的信号量和LED2资源的信号量,与它们的句柄一一对应,并且创建完毕后要先释放它们。 ```cpp void FreeRTOS_Test(void) { LED1_resources = xSemaphoreCreateBinary(); //创建信号量LED1_resources(如果返回值为NULL说明创建失败,可以进行后处理) LED2_resources = xSemaphoreCreateBinary(); //创建信号量LED2_resources(如果返回值为NULL说明创建失败,可以进行后处理) xSemaphoreGive(LED1_resources); //创建完毕后先释放LED1的硬件资源 xSemaphoreGive(LED2_resources); //创建完毕后先释放LED2的硬件资源 //创建任务start_task xTaskCreate((TaskFunction_t)start_task, //指向任务函数的指针 "start_task", //任务名字 START_TASK_STACK_SIZE, //任务堆栈大小,单位为字 NULL, //传递给任务函数的参数 START_TASK_PRIO, //任务优先级 (TaskHandle_t *) &start_task_handler //任务句柄,就是任务的任务控制块 ); //开启任务调度器 vTaskStartScheduler(); } ``` (4)更改task1、task2和task3函数的实现。 ```cpp void task1(void) { while(1) { xSemaphoreTake(LED1_resources, portMAX_DELAY); //获取LED1的硬件资源 LED1_Turn(); //LED1状态翻转 xSemaphoreGive(LED1_resources); //释放LED1的硬件资源 vTaskDelay(500); //先释放再阻塞,否则task3很难"抢占"资源 } } void task2(void) { while(1) { xSemaphoreTake(LED2_resources, portMAX_DELAY); //获取LED2的硬件资源 LED2_Turn(); //LED2状态翻转 xSemaphoreGive(LED2_resources); //释放LED2的硬件资源 vTaskDelay(1000); //先释放再阻塞,否则task3很难"抢占"资源 } } void task3(void) { uint8_t key = 0; while(1) { key = Key_GetNum(); //读取按键键值 if(key == 1) { //获取LED1与LED2的硬件资源 xSemaphoreTake(LED1_resources, portMAX_DELAY); xSemaphoreTake(LED2_resources, portMAX_DELAY); } if(key == 2) { xSemaphoreGive(LED1_resources); //释放LED1的硬件资源 xSemaphoreGive(LED2_resources); //释放LED2的硬件资源 } vTaskDelay(10); //延时(自我阻塞)10ms } } ``` (5)程序完善好后点击"编译",然后将程序下载到开发板上,根据程序注释进行调试。 ### 3、程序执行流程 (1)main函数全流程: ①初始化OLED模块、按键模块、LED模块。 ②调用FreeRTOS_Test函数。 ![](https://i-blog.csdnimg.cn/direct/2ecd6d37210d4c85bf5811e9611ca0c5.png) (2)测试函数全流程: ①创建LED1资源的信号量和LED2资源的信号量(下图未示出)。 ②创建任务start_task。 ③开启任务调度器。 ![](https://i-blog.csdnimg.cn/direct/dc3aa00b379042cf80fe6f8170cf1596.png) (3)多任务调度执行阶段(发生在开启任务调度器以后): ①程序刚开始运行,还没按下任意按键时,task1、task2和task3像过往的实验程序一样正常被调度、被执行、自我阻塞(下面的图未示出执行自我阻塞动作的函数)...... ②在task3任务执行时,按下按键1,LED1和LED2的硬件资源将被task3占用,并且task3没有释放这些硬件资源,当task1和task2阻塞结束后再次执行,将会因为无法获取LED硬件资源而进入无限阻塞,直到可以获取LED硬件资源为止。(显然,当任务不需要用某个资源时需要及时释放,否则很可能会影响其它任务的执行) ![](https://i-blog.csdnimg.cn/direct/c45e0b90f8fc49c58aaf05ca616aed1c.png) ③基于上述情况,在task3任务执行时,按下按键2,task3占用的LED1和LED2的硬件资源将被释放,当task3进入阻塞后,task2的优先级较高,故task2先执行,它将获取LED2的硬件资源,然后完成状态翻转动作,再将LED2硬件资源释放,接着进入自我阻塞,紧接着task1再执行,它将获取LED1的硬件资源,然后完成状态翻转动作,再将LED1硬件资源释放,接着也进入自我阻塞。 ![](https://i-blog.csdnimg.cn/direct/36a5ac7025eb46a18159ff1e002d0b35.png) ## 四、计数型信号量 ### 1、计数型信号量概述 (1)计数型信号量相当于队列长度大于1的队列,因此计数型信号量能够容纳多个资源,这在计数型信号量被创建的时候确定的。 (2)使用计数型信号量的过程:创建计数型信号量→释放计数型信号量→获取计数型信号量。 (3)计数型信号量适用场合: ①事件计数:当每次事件发生后,在事件处理函数中释放计数型信号量(计数值+1),其它任务会获取计数型信号量(计数值-1),这种场合一般在创建时将初始计数值设置为0。 ②资源管理:信号量可用于表示有效的资源数目。 \[1\]任务必须先获取信号量(信号量计数值-1)才能获取资源控制权。 \[2\]当计数值减为零时表示没有的资源。 \[3\]当任务使用完资源后,必须释放信号量(信号量计数值+1)。 \[4\]信号量创建时计数值应等于最大资源数目 ### 2、计数型信号量相关API函数 (1)计数型信号量相关API函数概览(获取信号量和释放信号量的函数与二值信号量相同): |----------------------------------|----------------| | 函数 | 描述 | | xSemaphoreCreateCounting() | 使用动态方法创建计数型信号量 | | xSemaphoreCreateCountingStatic() | 使用静态方法创建计数型信号量 | | uxSemaphoreGetCount() | 获取信号量的计数值 | (2)xSemaphoreCreateCounting函数: ①函数定义: ```cpp #define xSemaphoreCreateCounting(uxMaxCount, uxInitialCount) \ xQueueCreateCountingSemaphore((uxMaxCount), (uxInitialCount)) ``` ②函数参数: |----------------|-----------| | 形参 | 描述 | | uxMaxCount | 计数值的最大值限定 | | uxInitialCount | 计数值的初始值 | ③返回值: |------|-----------------| | 返回值 | 描述 | | NULL | 创建失败 | | 其它值 | 创建成功返回计数型信号量的句柄 | (3)xSemaphoreCreateCountingStatic函数: ①函数定义: ```cpp #define uxSemaphoreGetCount(xSemaphore) \ uxQueueMessagesWaiting((QueueHandle_t) (xSemaphore)) ``` ②函数参数: |------------|-------| | 形参 | 描述 | | xSemaphore | 信号量句柄 | ③返回值: |-----|---------------------| | 返回值 | 描述 | | 整数 | 当前信号量的计数值大小(空闲资源数目) | ## 五、计数型信号量实验 ### 1、原理图与实验目标 (1)原理图: ![](https://i-blog.csdnimg.cn/direct/f0df1d0b8e92484da020aca2eafe74dc.png) (2)实验目标: ①设计4个任务------start_task、task1、task2、task3: \[1\]start_task:用于创建task1、task2和task3任务。 \[2\]task1:当获取到LED的硬件资源后,控制LED1约每500ms完成亮暗翻转的状态切换,每完成一次即将资源释放。 \[3\]task2:当获取到LED的硬件资源后,控制LED2约每1000ms完成亮暗翻转的状态切换,每完成一次即将资源释放。 \[4\]task3:按下按键1,获取一个LED硬件资源;按下按键2,释放一个LED硬件资源。 ③预期实验现象(以下并未将所有情况概括进来): \[1\]程序下载到板子上后,两个LED灯闪烁。 \[2\]按下一次按键1,LED1停止闪烁;再按下一次按键1,没有LED再闪烁。 \[3\]基于上一步,按下一次按键2,LED2恢复闪烁;再按下一次按键1,两个LED都恢复闪烁。 ### 2、实验步骤 (1)将"二值信号量实验"的工程文件夹复制一份,在拷贝版中进行实验。 (2)在FreeRTOSConfig.h文件中将宏configUSE_COUNTING_SEMAPHORES配置为1。 ```cpp #define configUSE_COUNTING_SEMAPHORES 1 ``` (3)在FreeRTOS_experiment.c文件中定义一个队列句柄(LED资源的信号量)。 ```cpp QueueHandle_t LED_resources; //LED信号量的队列句柄 ``` (4)在FreeRTOS_Test函数中需要创建LED资源的信号量,与它的句柄一一对应,并且创建完毕后要先释放资源。 ```cpp void FreeRTOS_Test(void) { LED_resources = xSemaphoreCreateCounting(2,2); //创建信号量LED_resources(如果返回值为NULL说明创建失败,可以进行后处理) xSemaphoreGive(LED_resources); //创建完毕后先释放LED的硬件资源 //创建任务start_task xTaskCreate((TaskFunction_t)start_task, //指向任务函数的指针 "start_task", //任务名字 START_TASK_STACK_SIZE, //任务堆栈大小,单位为字 NULL, //传递给任务函数的参数 START_TASK_PRIO, //任务优先级 (TaskHandle_t *) &start_task_handler //任务句柄,就是任务的任务控制块 ); //开启任务调度器 vTaskStartScheduler(); } ``` (5)更改task1、task2和task3函数的实现。 ```cpp void task1(void) { while(1) { xSemaphoreTake(LED_resources, portMAX_DELAY); //获取LED的硬件资源(等不到就死等) LED1_Turn(); //LED1状态翻转 vTaskDelay(500); //先阻塞再释放,否则task1和task2可以共用一个资源 xSemaphoreGive(LED_resources); //释放LED的硬件资源 } } void task2(void) { while(1) { xSemaphoreTake(LED_resources, portMAX_DELAY); //获取LED的硬件资源(等不到就死等) LED2_Turn(); //LED2状态翻转 vTaskDelay(1000); //先阻塞再释放,否则task1和task2可以共用一个资源 xSemaphoreGive(LED_resources); //释放LED的硬件资源 } } void task3(void) { uint8_t key = 0; while(1) { key = Key_GetNum(); //读取按键键值 if(key == 1) { xSemaphoreTake(LED_resources,portMAX_DELAY);//获取一个LED硬件资源 } if(key == 2) { xSemaphoreGive(LED_resources); //释放一个LED的硬件资源 } vTaskDelay(10); //延时(自我阻塞)10ms } } ``` (6)程序完善好后点击"编译",然后将程序下载到开发板上,根据程序注释进行调试。 ### 3、程序执行流程 (1)main函数全流程: ①初始化OLED模块、按键模块、LED模块。 ②调用FreeRTOS_Test函数。 ![](https://i-blog.csdnimg.cn/direct/9c0c37d14bf24f3dbf7bca5535d678d5.png) (2)测试函数全流程: ①创建LED资源的信号量(下图未示出)。 ②创建任务start_task。 ③开启任务调度器。 ![](https://i-blog.csdnimg.cn/direct/1e8d662ccd3242cf9cc315e24ec66d81.png) (3)多任务调度执行阶段(发生在开启任务调度器以后): ①程序刚开始运行,还没按下任意按键时,task1、task2和task3像过往的实验程序一样正常被调度、被执行、自我阻塞...... ②在task3任务执行时,按下按键1,这时task1和task2均处于阻塞态,task3将占用1个LED资源,此时基本可分为如下两种情况(不考虑task1/task2释放LED资源后的一刻正好被其它任务打断的情况): \[1\]task1和task2均未释放LED资源,task1的剩余阻塞时间少于task2,这时task3将暂时进入阻塞态,接着task1运行,马上task1就会释放LED资源,紧接着task3就会将这个LED资源抢占,待task1继续运行时,将会因为没有LED资源而进入无限阻塞(因为task2的优先级比task1高,task1无法从其手上抢夺LED资源,后续无法将LED资源抢回,具体见下图2)。 ![](https://i-blog.csdnimg.cn/direct/b4ce2a83836846f78c95b541ba8cecc5.png) 图1 ![](https://i-blog.csdnimg.cn/direct/e5725d27abbb4700b543c71ce2294de7.png) 图2 \[2\]task1和task2均未释放LED资源,task1的剩余阻塞时间多于task2,这时task3将暂时进入阻塞态,接着task2运行,马上task2就会释放LED资源,紧接着task3就会将这个LED资源抢占(图3有示出,图4未示出),然后task3进入阻塞,此时task2因为无法获取LED资源而进行死等,待task1继续运行时,task1会释放LED资源,这时task2便会将这个LED资源抢占,轮到task1因为没有LED资源而进入无限阻塞。 ![](https://i-blog.csdnimg.cn/direct/0d7798edfa1d4cf58c27b6aac6ecb889.png) 图3 ![](https://i-blog.csdnimg.cn/direct/5a96fba35c4a426786da6baec42b98fb.png) 图4 ③基于上述两种情况的同一结果,在task3任务执行时,再次按下按键1,这时task1和task2均处于阻塞态,task3将再占用1个LED资源,这将导致task2也无法再申请LED资源。 ![](https://i-blog.csdnimg.cn/direct/22a80a758a694af8963593c2578d6ce7.png) 图5 ④当LED资源全被task3占用时,按下按键2,task3将释放一个LED资源,由于task2的优先级高于task1,task2一定会先成功申请到LED资源,而task1仍处于死等资源的状态。 ![](https://i-blog.csdnimg.cn/direct/2893e920ea2945189a2846d2169c19a4.png) 图6 ⑤基于图6描述的情形,执行task3任务时再次按下按键2,task3将释放一个LED资源,此时task1和task2不再存在LED资源不够用的情况,二者均可正常工作。 ![](https://i-blog.csdnimg.cn/direct/f6aa90d76c2c4436b62545729205ba51.png) 图7 ## 六、优先级翻转 ### 1、概述 (1)优先级翻转是指------高优先级的任务靠后执行,低优先级的任务反而优先执行。 (2)优先级翻转在抢占式内核中是非常常见的,但是在实时操作系统中是不允许出现优先级翻转的,因为优先级翻转会破坏任务的预期顺序,可能会导致未知的严重后果。 (3)在使用二值信号量做进程互斥的时候,经常会遇到优先级翻转的问题。 ### 2、举例 (1)如下图所示,任务优先级由高到低依次是H、M、L,任务H和任务L会使用同一个二值信号量。 ![](https://i-blog.csdnimg.cn/direct/2ea258b69b2445918584830f4aae125a.png) ①首先,任务L获取信号量,接着任务L执行一段时间,然后任务H就绪,由于任务H的优先级较高,它会抢占任务调度器,使得任务L被迫退回就绪态。 ②任务H运行一段时间后,需要获取信号量,但由于信号量已被任务L占用,任务H被迫进入无限阻塞。 ③任务L再运行一段时间后,任务M阻塞结束,由于优先级高于任务L,任务M抢占任务L。 ④任务M执行完毕后将CPU让给任务L。 ⑤任务L运行一段时间后释放信号量,这时死等信号量的任务L可成功获取信号量,然后继续运行。 (2)在上例中,任务M在任务H未执行完成的情况下占用了CPU,虽然当时任务H处于阻塞态,但论紧急程度来说,任务M的紧急程度更高,它进入阻塞态完全是受任务L"拖累",为了避免这种情况发生,可以开辟特殊通道,让任务L的优先级提升至任务H同等水平,这是为了保证任务M能尽快执行,并不违背任务优先级的初衷。 ## 七、互斥信号量 ### 1、互斥信号量概述 (1)互斥信号量其实就是一个拥有优先级继承的二值信号量,在同步的应用中二值信号量最适合,互斥信号量适合用于那些需要互斥访问的应用中。 (2)优先级继承:当一个互斥信号量正在被一个低优先级的任务持有时,如果此时有个高优先级的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级(如下图所示,它是对上例的改善)。 ![](https://i-blog.csdnimg.cn/direct/5dd20df179194286a5d174c9ccf22337.png) (3)优先级继承并不能完全的消除优先级翻转的问题,它只是尽可能的降低优先级翻转带来的影响。 (4)互斥信号量不能用于中断服务函数中,原因如下: ①互斥信号量有任务优先级继承的机制,但是中断不是任务,没有任务优先级,所以互斥信号量只能用与任务中,不能用于中断服务函数。 ②中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态。 (5)使用互斥信号量的过程:创建互斥信号量→获取互斥信号量→释放互斥信号量。 ### 2、互斥信号量相关API函数 (1)互斥信号量相关API函数概览(获取信号量和释放信号量的函数与二值信号量相同): |-------------------------------|---------------| | 函数 | 描述 | | xSemaphoreCreateMutex() | 使用动态方法创建互斥信号量 | | xSemaphoreCreateMutexStatic() | 使用静态方法创建互斥信号量 | (2)xSemaphoreCreateMutex函数: ①函数定义: ```cpp #define xSemaphoreCreateMutex() xQueueCreateMutex(queueQUEUE_TYPE_MUTEX) ``` ②返回值: |------|----------------| | 返回值 | 描述 | | NULL | 创建失败 | | 其它值 | 创建成功返回互斥信号量的句柄 |

相关推荐
YdaMooc9 小时前
STM32-FreeRTOS的详细配置
stm32·单片机·嵌入式硬件
云山工作室9 小时前
基于STM32的智能门禁系统
stm32·单片机·毕业设计·毕设
无处在10 小时前
STM32 四足机器人常见问题汇总
stm32·嵌入式硬件·机器人
南梦也要学习12 小时前
STM32江科大-----PWR电源控制
stm32·单片机·嵌入式硬件
时光の尘12 小时前
FreeRTOS菜鸟入门(五)·空闲任务与阻塞延时的实现
c语言·stm32·嵌入式硬件·mcu·物联网·freertos
正点原子14 小时前
【正点原子STM32MP257连载】第四章 ATK-DLMP257B功能测试——EEPROM、SPI FLASH测试 #AT24C64 #W25Q128
linux·stm32·单片机·嵌入式硬件·stm32mp257
光芒Shine17 小时前
【STM32-代码】
stm32·单片机·嵌入式硬件
无垠的广袤17 小时前
【树莓派 PICO 2 测评】采集 DS18B20 数据及 OLED 显示
单片机·嵌入式硬件·物联网
冻结的鱼17 小时前
两个 STM32G0 I2C 通信异常的案例分析
stm32·单片机·嵌入式硬件
总结所学17 小时前
arm_math.h、arm_const_structs.h 和 arm_common_tables.h
arm开发·单片机·嵌入式硬件