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函数:
①函数定义:
#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函数:
①函数定义:
#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函数:
①函数定义:
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资源的信号量)。
#include "semphr.h"QueueHandle_t LED1_resources; //LED1信号量的队列句柄
QueueHandle_t LED2_resources; //LED2信号量的队列句柄
(3)在FreeRTOS_Test函数中需要创建LED1资源的信号量和LED2资源的信号量,与它们的句柄一一对应,并且创建完毕后要先释放它们。
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_taskxTaskCreate((TaskFunction_t)start_task, //指向任务函数的指针"start_task", //任务名字START_TASK_STACK_SIZE, //任务堆栈大小,单位为字NULL, //传递给任务函数的参数START_TASK_PRIO, //任务优先级(TaskHandle_t *) &start_task_handler //任务句柄,就是任务的任务控制块);//开启任务调度器vTaskStartScheduler();
}
(4)更改task1、task2和task3函数的实现。
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函数。

(2)测试函数全流程:
①创建LED1资源的信号量和LED2资源的信号量(下图未示出)。
②创建任务start_task。
③开启任务调度器。

(3)多任务调度执行阶段(发生在开启任务调度器以后):
①程序刚开始运行,还没按下任意按键时,task1、task2和task3像过往的实验程序一样正常被调度、被执行、自我阻塞(下面的图未示出执行自我阻塞动作的函数,且为简单起见,task1和task2合并在一起绘制,不过实际上它们并不能在同一时刻一起被执行)……
②在task3任务执行时,按下按键1,LED1和LED2的硬件资源将被task3占用,并且task3没有释放这些硬件资源,当task1和task2阻塞结束后再次执行,将会因为无法获取LED硬件资源而进入无限阻塞,直到可以获取LED硬件资源为止。(显然,当任务不需要用某个资源时需要及时释放,否则很可能会影响其它任务的执行)

③基于上述情况,在task3任务执行时,按下按键2,task3占用的LED1和LED2的硬件资源将被释放,当task3进入阻塞后,task2的优先级较高,故task2先执行,它将获取LED2的硬件资源,然后完成状态翻转动作,再将LED2硬件资源释放,接着进入自我阻塞,紧接着task1再执行,它将获取LED1的硬件资源,然后完成状态翻转动作,再将LED1硬件资源释放,接着也进入自我阻塞。

四、计数型信号量
1、计数型信号量概述
(1)计数型信号量相当于队列长度大于1的队列,因此计数型信号量能够容纳多个资源,这在计数型信号量被创建的时候确定的。
(2)使用计数型信号量的过程:创建计数型信号量→释放计数型信号量→获取计数型信号量。
(3)计数型信号量适用场合:
①事件计数:当每次事件发生后,在事件处理函数中释放计数型信号量(计数值+1),其它任务会获取计数型信号量(计数值-1),这种场合一般在创建时将初始计数值设置为0。
②资源管理:信号量可用于表示有效的资源数目。
[1]任务必须先获取信号量(信号量计数值-1)才能获取资源控制权。
[2]当计数值减为零时表示没有的资源。
[3]当任务使用完资源后,必须释放信号量(信号量计数值+1)。
[4]信号量创建时计数值应等于最大资源数目
2、计数型信号量相关API函数
(1)计数型信号量相关API函数概览(获取信号量和释放信号量的函数与二值信号量相同):
| 函数 | 描述 |
| xSemaphoreCreateCounting() | 使用动态方法创建计数型信号量 |
| xSemaphoreCreateCountingStatic() | 使用静态方法创建计数型信号量 |
| uxSemaphoreGetCount() | 获取信号量的计数值 |
(2)xSemaphoreCreateCounting函数:
①函数定义:
#define xSemaphoreCreateCounting(uxMaxCount, uxInitialCount) \xQueueCreateCountingSemaphore((uxMaxCount), (uxInitialCount))
②函数参数:
| 形参 | 描述 |
| uxMaxCount | 计数值的最大值限定 |
| uxInitialCount | 计数值的初始值 |
③返回值:
| 返回值 | 描述 |
| NULL | 创建失败 |
| 其它值 | 创建成功返回计数型信号量的句柄 |
(3)xSemaphoreCreateCountingStatic函数:
①函数定义:
#define uxSemaphoreGetCount(xSemaphore) \uxQueueMessagesWaiting((QueueHandle_t) (xSemaphore))
②函数参数:
| 形参 | 描述 |
| xSemaphore | 信号量句柄 |
③返回值:
| 返回值 | 描述 |
| 整数 | 当前信号量的计数值大小(空闲资源数目) |
五、计数型信号量实验
1、原理图与实验目标
(1)原理图:

(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。
#define configUSE_COUNTING_SEMAPHORES 1
(3)在FreeRTOS_experiment.c文件中定义一个队列句柄(LED资源的信号量)。
QueueHandle_t LED_resources; //LED信号量的队列句柄
(4)在FreeRTOS_Test函数中需要创建LED资源的信号量,与它的句柄一一对应,并且创建完毕后要先释放资源。
void FreeRTOS_Test(void)
{LED_resources = xSemaphoreCreateCounting(2,2); //创建信号量LED_resources(如果返回值为NULL说明创建失败,可以进行后处理)xSemaphoreGive(LED_resources); //创建完毕后先释放LED的硬件资源//创建任务start_taskxTaskCreate((TaskFunction_t)start_task, //指向任务函数的指针"start_task", //任务名字START_TASK_STACK_SIZE, //任务堆栈大小,单位为字NULL, //传递给任务函数的参数START_TASK_PRIO, //任务优先级(TaskHandle_t *) &start_task_handler //任务句柄,就是任务的任务控制块);//开启任务调度器vTaskStartScheduler();
}
(5)更改task1、task2和task3函数的实现。
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函数。

(2)测试函数全流程:
①创建LED资源的信号量(下图未示出)。
②创建任务start_task。
③开启任务调度器。

(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)。

图1

图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资源而进入无限阻塞。

图3

图4
③基于上述两种情况的同一结果,在task3任务执行时,再次按下按键1,这时task1和task2均处于阻塞态,task3将再占用1个LED资源,这将导致task2也无法再申请LED资源。

图5
④当LED资源全被task3占用时,按下按键2,task3将释放一个LED资源,由于task2的优先级高于task1,task2一定会先成功申请到LED资源,而task1仍处于死等资源的状态。

图6
⑤基于图6描述的情形,执行task3任务时再次按下按键2,task3将释放一个LED资源,此时task1和task2不再存在LED资源不够用的情况,二者均可正常工作。

图7
六、优先级翻转
1、概述
(1)优先级翻转是指——高优先级的任务靠后执行,低优先级的任务反而优先执行。
(2)优先级翻转在抢占式内核中是非常常见的,但是在实时操作系统中是不允许出现优先级翻转的,因为优先级翻转会破坏任务的预期顺序,可能会导致未知的严重后果。
(3)在使用二值信号量做进程互斥的时候,经常会遇到优先级翻转的问题。
2、举例
(1)如下图所示,任务优先级由高到低依次是H、M、L,任务H和任务L会使用同一个二值信号量。

①首先,任务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)优先级继承:当一个互斥信号量正在被一个低优先级的任务持有时,如果此时有个高优先级的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级(如下图所示,它是对上例的改善)。

(3)优先级继承并不能完全的消除优先级翻转的问题,它只是尽可能的降低优先级翻转带来的影响。
(4)互斥信号量不能用于中断服务函数中,原因如下:
①互斥信号量有任务优先级继承的机制,但是中断不是任务,没有任务优先级,所以互斥信号量只能用与任务中,不能用于中断服务函数。
②中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态。
(5)使用互斥信号量的过程:创建互斥信号量→获取互斥信号量→释放互斥信号量。
2、互斥信号量相关API函数
(1)互斥信号量相关API函数概览(获取信号量和释放信号量的函数与二值信号量相同):
| 函数 | 描述 |
| xSemaphoreCreateMutex() | 使用动态方法创建互斥信号量 |
| xSemaphoreCreateMutexStatic() | 使用静态方法创建互斥信号量 |
(2)xSemaphoreCreateMutex函数:
①函数定义:
#define xSemaphoreCreateMutex() xQueueCreateMutex(queueQUEUE_TYPE_MUTEX)
②返回值:
| 返回值 | 描述 |
| NULL | 创建失败 |
| 其它值 | 创建成功返回互斥信号量的句柄 |
相关文章:
FreeRTOS从入门到精通 第十三章(信号量)
参考教程:【正点原子】手把手教你学FreeRTOS实时系统_哔哩哔哩_bilibili 一、信号量知识回顾 1、概述 (1)信号量是一种解决同步问题的机制,可以实现对共享资源的有序访问,FreeRTOS中使用的是二值信号量、计数型信号…...
pstricks PGFTikz 在CTeX套装中绘图Transparency或Opacity失效的问题
我在CTeX中画图的时候,习惯用Geogebra先画好,然后生成pstricks或PGFTikz代码: 这样不用插入eps或pdf之类的图片,也是一种偷懒的方法。以前往arXiv.org上面传论文也是这样:代码出图,就不用另外上传一幅eps或…...
FPGA学习篇——开篇之作
今天正式开始学FPGA啦,接下来将会编写FPGA学习篇来记录自己学习FPGA 的过程! 今天是大年初六,简单学一下FPGA的相关概念叭叭叭! 一:数字系统设计流程 一个数字系统的设计分为前端设计和后端设计。在我看来࿰…...
C++底层学习预备:模板初阶
文章目录 1.编程范式2.函数模板2.1 函数模板概念2.2 函数模板原理2.3 函数模板实例化2.3.1 隐式实例化2.3.2 显式实例化 2.4 模板参数的匹配原则 3.类模板希望读者们多多三连支持小编会继续更新你们的鼓励就是我前进的动力! 进入STL库学习之前我们要先了解有关模板的…...
llama.cpp LLM_CHAT_TEMPLATE_DEEPSEEK_3
llama.cpp LLM_CHAT_TEMPLATE_DEEPSEEK_3 1. LLAMA_VOCAB_PRE_TYPE_DEEPSEEK3_LLM2. static const std::map<std::string, llm_chat_template> LLM_CHAT_TEMPLATES3. LLM_CHAT_TEMPLATE_DEEPSEEK_3References 不宜吹捧中国大语言模型的同时,又去贬低美国大语言…...
【玩转 Postman 接口测试与开发2_014】第11章:测试现成的 API 接口(下)——自动化接口测试脚本实战演练 + 测试集合共享
《API Testing and Development with Postman》最新第二版封面 文章目录 3 接口自动化测试实战3.1 测试环境的改造3.2 对列表查询接口的测试3.3 对查询单个实例的测试3.4 对新增接口的测试3.5 对修改接口的测试3.6 对删除接口的测试 4 测试集合的共享操作4.1 分享 Postman 集合…...
Linux03——常见的操作命令
root用户以及权限 Linux系统的超级管理员用户是:root用户 su命令 可以切换用户,语法:su [-] [用户名]- 表示切换后加载环境变量,建议带上用户可以省略,省略默认切换到root su命令是用于账户切换的系统命令ÿ…...
w188校园商铺管理系统设计与实现
🙊作者简介:多年一线开发工作经验,原创团队,分享技术代码帮助学生学习,独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取,记得注明来意哦~🌹赠送计算机毕业设计600个选题excel文…...
leetcode——二叉树的最近公共祖先(java)
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的…...
基于FPGA的BT656编解码
概述 BT656全称为“ITU-R BT.656-4”或简称“BT656”,是一种用于数字视频传输的接口标准。它规定了数字视频信号的编码方式、传输格式以及接口电气特性。在物理层面上,BT656接口通常包含10根线(在某些应用中可能略有不同,但标准配置为10根)。这些线分别用于传输视频数据、…...
解锁数据结构密码:层次树与自引用树的设计艺术与API实践
1. 引言:为什么选择层次树和自引用树? 数据结构是编程中的基石之一,尤其是在处理复杂关系和层次化数据时,树形结构常常是最佳选择。层次树(Hierarchical Tree)和自引用树(Self-referencing Tree…...
本地快速部署DeepSeek-R1模型——2025新年贺岁
一晃年初六了,春节长假余额马上归零了。今天下午在我的电脑上成功部署了DeepSeek-R1模型,抽个时间和大家简单分享一下过程: 概述 DeepSeek模型 是一家由中国知名量化私募巨头幻方量化创立的人工智能公司,致力于开发高效、高性能…...
WAWA鱼2024年终总结,关键词:成长
前言 本来想着偷懒一下,不写2024年终总结了,因为24年上半年还在忙毕业,下半年在忙转正,其实没什么太多好写的。结果被an_da和学弟催更了,哈哈哈,感谢大家对我近况的关注,学校内容基本都忘的差不…...
使用VCS进行单步调试的步骤
使用VCS对SystemVerilog进行单步调试的步骤如下: 1. 编译设计 使用-debug_all或-debug_pp选项编译设计,生成调试信息。 我的4个文件: 1.led.v module led(input clk,input rst_n,output reg led );reg [7:0] cnt;always (posedge clk) beg…...
【Elasticsearch】硬件资源优化
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编…...
Elasticsearch 指南 [8.17] | Search APIs
Search API 返回与请求中定义的查询匹配的搜索结果。 http GET /my-index-000001/_search Request GET /<target>/_search GET /_search POST /<target>/_search POST /_search Prerequisites 如果启用了 Elasticsearch 安全功能,针对目标数据流…...
QT+mysql+python 效果:
# This Python file uses the following encoding: utf-8 import sysfrom PySide6.QtWidgets import QApplication, QWidget,QMessageBox from PySide6.QtGui import QStandardItemModel, QStandardItem # 导入需要的类# Important: # 你需要通过以下指令把 form.ui转为ui…...
Java 序列化和反序列化作用
Java 序列化和反序列化的核心作用是将对象转换为可存储或传输的字节流(序列化),以及从字节流恢复对象(反序列化)。以下是详细说明和示例: 作用 持久化存储 将对象保存到文件或数据库,重启后仍可…...
【4】阿里面试题整理
[1]. 介绍一下数据库死锁 数据库死锁是指两个或多个事务,由于互相请求对方持有的资源而造成的互相等待的状态,导致它们都无法继续执行。 死锁会导致事务阻塞,系统性能下降甚至应用崩溃。 比如:事务T1持有资源R1并等待R2&#x…...
回顾生化之父三上真司的游戏思想
1. 放养式野蛮成长路线,开创生存恐怖类型 三上进入capcom后,没有培训,没有师傅手把手的指导,而是每天摸索写策划书,老员工给出不行的评语后,扔掉旧的重写新的。 然后突然就成为游戏总监,进入开…...
Java循环操作哪个快
文章目录 Java循环操作哪个快一、引言二、循环操作性能对比1、普通for循环与增强for循环1.1、代码示例 2、for循环与while循环2.1、代码示例 3、循环优化技巧3.1、代码示例 三、循环操作的适用场景四、使用示例五、总结 Java循环操作哪个快 一、引言 在Java开发中,…...
Maven jar 包下载失败问题处理
Maven jar 包下载失败问题处理 1.配置好国内的Maven源2.重新下载3. 其他问题 1.配置好国内的Maven源 打开⾃⼰的 Idea 检测 Maven 的配置是否正确,正确的配置如下图所示: 检查项⼀共有两个: 确认右边的两个勾已经选中,如果没有请…...
【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】1.25 视觉风暴:NumPy驱动数据可视化
1.25 视觉风暴:NumPy驱动数据可视化 目录 #mermaid-svg-i3nKPm64ZuQ9UcNI {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-i3nKPm64ZuQ9UcNI .error-icon{fill:#552222;}#mermaid-svg-i3nKPm64ZuQ9UcNI …...
Baklib推动数字化内容管理解决方案助力企业数字化转型
内容概要 在当今信息爆炸的时代,数字化内容管理成为企业提升效率和竞争力的关键。企业在面对大量数据时,如何高效地存储、分类与检索信息,直接关系到其经营的成败。数字化内容管理不仅限于简单的文档存储,更是整合了文档、图像、…...
读书笔记 | 《最小阻力之路》:用结构思维重塑人生愿景
一、核心理念:结构决定行为轨迹 橡皮筋模型:愿景张力的本质 书中提出:人类行为始终沿着"现状"与"愿景"之间的张力路径运动,如同橡皮筋拉伸产生的动力。 案例:音乐家每日练习的坚持,不…...
React中使用箭头函数定义事件处理程序
React中使用箭头函数定义事件处理程序 为什么使用箭头函数?1. 传递动态参数2. 避免闭包问题3. 确保每个方块的事件处理程序是独立的4. 代码可读性和维护性 示例代码总结 在React开发中,处理事件是一个常见的任务。特别是当我们需要传递动态参数时&#x…...
高阶开发基础——快速入门C++并发编程6——大作业:实现一个超级迷你的线程池
目录 实现一个无返回的线程池 完全代码实现 Reference 实现一个无返回的线程池 实现一个简单的线程池非常简单,我们首先聊一聊线程池的定义: 线程池(Thread Pool) 是一种并发编程的设计模式,用于管理和复用多个线程…...
少样本提示词模板
文章目录 少样本提示词模板 少样本提示词模板 少样本提示是一种基于机器学习的技术,利用少量的样本(即提示词的示例部分)来引导模型对特定任务进行学习和执行。这些示例能让模型理解开发者期望它完成的任务的类型和风格。在给定的任务中&…...
SQLGlot:用SQLGlot解析SQL
几十年来,结构化查询语言(SQL)一直是与数据库交互的实际语言。在一段时间内,不同的数据库在支持通用SQL语法的同时演变出了不同的SQL风格,也就是方言。这可能是SQL被广泛采用和流行的原因之一。 SQL解析是解构SQL查询…...
代码随想录算法训练营Day35
第九章 动态规划part03 正式开始背包问题,背包问题还是挺难的,虽然大家可能看了很多背包问题模板代码,感觉挺简单,但基本理解的都不够深入。 如果是直接从来没听过背包问题,可以先看文字讲解慢慢了解 这是干什么的。 …...
