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 原语操作中完成申请或释放。进程对信号量S_{i}的测试值不再是1,而是该资源的分配下限值t_{i},即要求S_{i}\geq t_{i},否则不予分配;一旦允许分配,进程对该资源的需求值为d_{i},即表示资源占用量,进行S_{i}=S_{i}-d_{i}操作,由此形成一般化的“信号量集”机制。

(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_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函数的实现。

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_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函数的实现。

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

创建失败

其它值

创建成功返回互斥信号量的句柄

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/963725.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

业务系统文件上传和互传如何做到高效又安全?

在当今数字化的商业环境中&#xff0c;企业越来越依赖于高效的业务系统来处理日常运营。无论是金融、医疗、能源还是政府机构&#xff0c;内部各类业务系统如OA、供应商管理系统等都需要终端用户上传各种类型的文件附件。然而&#xff0c;在确保这些文件能够快速而准确地上传和…

四、GPIO中断实现按键功能

4.1 GPIO简介 输入输出&#xff08;I/O&#xff09;是一个非常重要的概念。I/O泛指所有类型的输入输出端口&#xff0c;包括单向的端口如逻辑门电路的输入输出管脚和双向的GPIO端口。而GPIO&#xff08;General-Purpose Input/Output&#xff09;则是一个常见的术语&#xff0c…

数据结构:时间复杂度

文章目录 为什么需要时间复杂度分析&#xff1f;一、大O表示法&#xff1a;复杂度的语言1.1 什么是大O&#xff1f;1.2 常见复杂度速查表 二、实战分析&#xff1a;解剖C语言代码2.1 循环结构的三重境界单层循环&#xff1a;线性时间双重循环&#xff1a;平方时间动态边界循环&…

C++ Primer 自定义数据结构

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…

035 搜索之DFS基础

DFS&#xff1a;深度优先搜索——本质上是暴力枚举&#xff0c;尽可能一条路走到底&#xff0c;走不了回退 1.DFS与n重循环 例&#xff1a;给定一个数字x&#xff0c;将其拆分为3个正整数&#xff0c;后一个要求大于前一个&#xff0c;给出方案。 分析&#xff1a;这种情况下…

【系统迁移】将系统迁移到新硬盘中(G15 5520)

文章目录 前言问题描述解决步骤&#xff08;红色为 debug 步骤&#xff09;参考文献 前言 参数&#xff1a; 电脑 dell g15 5520硬盘&#xff1a;1T 自带硬盘 海力士 2230 -> 2T 西数蓝盘 2280 问题描述 电脑硬盘过小&#xff08;且只有一个接口&#xff09;&#xff0c;将…

大模型综合性能考题汇总

- K1.5长思考版本 一、创意写作能力 题目1&#xff1a;老爸笑话 要求&#xff1a;写五个原创的老爸笑话。 考察点&#xff1a;考察模型的幽默感和创意能力&#xff0c;以及对“原创”要求的理解和执行能力。 题目2&#xff1a;创意故事 要求&#xff1a;写一篇关于亚伯拉罕…

openeuler 22.03 lts sp4 使用 cri-o 和 静态 pod 的方式部署 k8s-v1.32.0 高可用集群

前情提要 整篇文章会非常的长…可以选择性阅读,另外,这篇文章是自己学习使用的,用于生产,还请三思和斟酌 静态 pod 的部署方式和二进制部署的方式是差不多的,区别在于 master 组件的管理方式是 kubectl 还是 systemctl有 kubeadm 工具,为什么还要用静态 pod 的方式部署?…

Pluto固件编译笔记

前段时间我已经做到在电脑上交叉编译一个简单的c/c程序&#xff0c;然后复制到pluto上运行。 要做到这一点&#xff0c;其实参考adi pluto官网的wiki就能做到了。 但这样有几个问题&#xff0c;只能做到简易程序&#xff0c;如果程序复杂&#xff0c;要调用更多库而SYSROOT里…

【产品经理学习案例——AI翻译棒出海业务】

前言&#xff1a; 本文主要讲述了硬件产品在出海过程中&#xff0c;翻译质量、翻译速度和本地化落地策略是硬件产品规划需要考虑的核心因素。针对不同国家&#xff0c;需要优化翻译质量和算法&#xff0c;关注市场需求和文化差异&#xff0c;以便更好地满足当地用户的需求。同…

星际智慧农业系统(SAS),智慧农业的未来篇章

新月人物传记&#xff1a;人物传记之新月篇-CSDN博客 相关文章&#xff1a;星际战争模拟系统&#xff1a;新月的编程之道-CSDN博客 新月智能护甲系统CMIA--未来战场的守护者-CSDN博客 “新月智能武器系统”CIWS&#xff0c;开启智能武器的新纪元-CSDN博客 目录 星际智慧农业…

【蓝桥杯嵌入式入门与进阶】4.初读启动文件:粗略阅读,经常翻阅,知己知彼,百战百胜

目录 1.二者差异 1. 1适用芯片型号不同 1.2中断向量表差异 1.2.1 中断数量和种类 1.2.2 部分中断处理函数命名差异 1.2.3. 复位处理描述差异 1.2.4代码注释中的功能描述差异 1.2.5 DMA 通道中断处理函数差异 示例代码对比片段 startup_stm32g431xx.s startup_stm32…

unity中的动画混合树

为什么需要动画混合树&#xff0c;动画混合树有什么作用&#xff1f; 在Unity中&#xff0c;动画混合树&#xff08;Animation Blend Tree&#xff09;是一种用于管理和混合多个动画状态的工具&#xff0c;包括1D和2D两种类型&#xff0c;以下是其作用及使用必要性的介绍&…

C语言 --- 分支

C语言 --- 分支 语句分支语句含义if...else语句单分支if语句语法形式 双分支 if-else 语句语法形式 悬空else含义问题描述 多分支 if-else 语句语法形式 switch...case语句含义语法形式 总结 &#x1f4bb;作者简介&#xff1a;曾与你一样迷茫&#xff0c;现以经验助你入门 C 语…

pytorch实现长短期记忆网络 (LSTM)

人工智能例子汇总&#xff1a;AI常见的算法和例子-CSDN博客 LSTM 通过 记忆单元&#xff08;cell&#xff09; 和 三个门控机制&#xff08;遗忘门、输入门、输出门&#xff09;来控制信息流&#xff1a; 记忆单元&#xff08;Cell State&#xff09; 负责存储长期信息&…

C++:抽象类习题

题目内容&#xff1a; 求正方体、球、圆柱的表面积&#xff0c;抽象出一个公共的基类Container为抽象类&#xff0c;在其中定义一个公共的数据成员radius(此数据可以作为正方形的边长、球的半径、圆柱体底面圆半径)&#xff0c;以及求表面积的纯虚函数area()。由此抽象类派生出…

GEE | 计算Sentinel-2的改进型土壤调整植被指数MSAVI

同学们好&#xff01;今天和大家分享的是 “改进型土壤调整植被指数MSAVI”&#xff0c;它能够更准确地反映植被生长状态&#xff0c;且广泛应用于植被覆盖监测、生态环境评估等领域。 1. MSAVI 改进型土壤调整植被指数&#xff08;MSAVI&#xff09;是一种针对植被覆盖区域土…

deepseek+vscode自动化测试脚本生成

近几日Deepseek大火,我这里也尝试了一下,确实很强。而目前vscode的AI toolkit插件也已经集成了deepseek R1,这里就介绍下在vscode中利用deepseek帮助我们完成自动化测试脚本的实践分享 安装AI ToolKit并启用Deepseek 微软官方提供了一个针对AI辅助的插件,也就是 AI Toolk…

CodeGPT使用本地部署DeepSeek Coder

目前NV和github都托管了DeepSeek&#xff0c;生成Key后可以很方便的用CodeGPT接入。CodeGPT有三种方式使用AI&#xff0c;分别时Agents&#xff0c;Local LLMs&#xff08;本地部署AI大模型&#xff09;&#xff0c;LLMs Cloud Model&#xff08;云端大模型&#xff0c;从你自己…

[c语言日寄]C语言类型转换规则详解

【作者主页】siy2333 【专栏介绍】⌈c语言日寄⌋&#xff1a;这是一个专注于C语言刷题的专栏&#xff0c;精选题目&#xff0c;搭配详细题解、拓展算法。从基础语法到复杂算法&#xff0c;题目涉及的知识点全面覆盖&#xff0c;助力你系统提升。无论你是初学者&#xff0c;还是…