4.freertos入门之信号量与互斥量


前言

本篇文章目的是对FREERTOS的信号量和互斥量进行讲解
本系列参考:
韦东山:
https://www.bilibili.com/video/BV1Jw411i7Fz
尚硅谷:
https://www.bilibili.com/video/BV1vUpje9Ej2
野火书籍:
《FreeRTOS 内核实现与应用开发实战指南》
ARM 官网(官网是英文的):
《ARM Cortex-M3 权威指南》(参考的是中文版本的)
本篇参考:
简介:
https://www.freertos.org/


在这里插入图片描述

一、信号量

1.1 什么是信号量

FreeRTOS中的信号量是一种用于任务间同步和资源管理的机制。信号量可以是二进制的(只能取0或1)也可以是计数型的(可以是任意正整数)。信号量的基本操作包括“获取”和“释放”。二进制信号量和计数型的唯一差别,就是计数值的最大值被限定为1。
比如:演唱会一共有6张门票,卖完及空,退票则加。
在这里插入图片描述

1.2 信号量与队列

信号量与队列的区别如下:

信号量队列
主要用于管理对共享资源的访问,确保在同一时刻只有一个任务可以访问共享资源用于任务之间的数据通信,通过在任务之间传递消息,实现信息的传递和同步。
可以是二进制信号量(Binary Semaphore)或计数信号量(Counting Semaphore)存储和传递消息的数据结构,任务可以发送消息到队列,也可以从队列接收消息。
只有计数值,无法容纳其他数据。创建信号量时,只需要分配信号量结构体可以容纳多个数据,创建队列时有 2 部分内存: 队列结构体、存储数据的空间。
适用于对资源的互斥访问,控制任务的执行顺序,或者限制同时访问某一资源的任务数量。适用于在任务之间传递数据,实现解耦和通信。

1.3 二进制信号量与计数信号量运行机制

1.3.1 二进制信号量

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
但有信号量,则获取信号量,没有信号量则挂起等待。

1.3.2 计数型信号量

在这里插入图片描述
当有任务获取信号量时,信号量减1,再有任务要获取则挂起等待。

1.4 信号量函数

1.4.1 创建

创建二进制信号量的函数原型如下:

/* 创建一个二进制信号量,返回它的句柄。
* 此函数内部会分配信号量结构体
* 返回值: 返回句柄,非 NULL 表示成功
*/
SemaphoreHandle_t xSemaphoreCreateBinary( void );
/* 创建一个二进制信号量,返回它的句柄。
* 此函数无需动态分配内存,所以需要先有一个 StaticSemaphore_t 结构体,并传入它的
指针
* 返回值: 返回句柄,非 NULL 表示成功
*/
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer );

创建计数型信号量的函数原型如下:

/* 创建一个计数型信号量,返回它的句柄。
* 此函数内部会分配信号量结构体
* uxMaxCount: 最大计数值
* uxInitialCount: 初始计数值
* 返回值: 返回句柄,非 NULL 表示成功
*/
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);
/* 创建一个计数型信号量,返回它的句柄。
* 此函数无需动态分配内存,所以需要先有一个 StaticSemaphore_t 结构体,并传入它的
指针
* uxMaxCount: 最大计数值
* uxInitialCount: 初始计数值
* pxSemaphoreBuffer: StaticSemaphore_t 结构体指针
* 返回值: 返回句柄,非 NULL 表示成功
*/
SemaphoreHandle_t xSemaphoreCreateCountingStatic( 
	UBaseType_t uxMaxCount,
	UBaseType_t uxInitialCount,
	StaticSemaphore_t *pxSemaphoreBuffer 
);

1.4.2 删除

对于动态创建的信号量,不再需要它们时,可以删除它们以回收内存。
vSemaphoreDelete可以用来删除二进制信号量、计数型信号量,函数原型如下:

/*
* xSemaphore: 信号量句柄,你要删除哪个信号量
*/
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );

1.4.2 获取(take)和释放(give)

二进制信号量、计数型信号量的 give、take 操作函数是一样的。这些函数也分为 2 个版
本:给任务使用,给 ISR 使用。列表如下:

在任务中使用在 ISR 中使用
givexSemaphoreGivexSemaphoreGiveFromISR
takexSemaphoreTakexSemaphoreTakeFromISR

xSemaphoreGive的函数原型如下:

BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
参数说明
xSemaphore信号量句柄,释放哪个信号量
返回值pdTRUE 表示成功, 如果二进制信号量的计数值已经是 1,再次调用此函数则返回失败;如>果计数型信号量的计数值已经是最大值,再次调用此函数则返回失败

xSemaphoreGiveFromISR的函数原型如下:

BaseType_t xSemaphoreGiveFromISR(
	 SemaphoreHandle_t xSemaphore,
	 BaseType_t *pxHigherPriorityTaskWoken
);
参数说明
xSemaphore信号量句柄,释放哪个信号量
pxHigherPriorityTaskWoken如果释放信号量导致更高优先级的任务变为了
就绪态,则*pxHigherPriorityTaskWoken = pdTRUE
返回值pdTRUE 表示成功,如果二进制信号量的计数值已经是 1,再次调用此函数则返回失败;如果计数型信号量的计数值已经是最大值,再次调用此函数则返回失败

xSemaphoreTake的函数原型如下:

BaseType_t xSemaphoreTake(
	SemaphoreHandle_t xSemaphore,
	TickType_t xTicksToWait
);
参数说明
xSemaphore信号量句柄,获取哪个信号量
xTicksToWait如果无法马上获得信号量,阻塞一会:0:不阻塞,马上返回portMAX_DELAY: 一直阻塞直到成功其他值: 阻塞的 Tick 个数,可以使用 pdMS_TO_TICKS()来指定阻塞时间为若干 ms
返回值pdTRUE 表示成功

xSemaphoreTakeFromISR的函数原型如下:

BaseType_t xSemaphoreTakeFromISR(
	SemaphoreHandle_t xSemaphore,
	BaseType_t *pxHigherPriorityTaskWoken
);
参数说明
xSemaphore信号量句柄,获取哪个信号量
pxHigherPriorityTaskWoken如果获取信号量导致更高优先级的任务变为了就绪态,则*pxHigherPriorityTaskWoken = pdTRUE
返回值pdTRUE 表示成功

1.5 信号量实例

1.5.1 二进制信号量


注意:创建二进制信号量时,默认值为0,需要释放一下
/* 创建二进制信号量 */
xBinarySemaphore = xSemaphoreCreateBinary();
xSemaphoreGive(xBinarySemaphore);


#include "stm32f10x.h"              // Device header
#include "usart.h"
#include "stdio.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
 #include "queue.h"

SemaphoreHandle_t xBinarySemaphore = NULL;

void vTask1(void *param)
{
	
	int i = 0;
	printf("task1\r\n");
	while(1)
	{
		i++;
		
		printf("t2:%d\r\n",i);

		if(xSemaphoreTake(xBinarySemaphore,0) == pdTRUE)
		{
			printf("task1 ok\r\n");
			
		}else{
			printf("task1 no\r\n");
		}
		if(i%10 == 0)
		{
			printf("i");
			xSemaphoreGive(xBinarySemaphore);
			vTaskDelay(10);
		}
		
		
	}
}

void vTask2(void *param)
{
	int i = 0;
	printf("task2\r\n");
	while(1)
	{
		i++;
		
		printf("t2:%d\r\n",i);
		if(xSemaphoreTake(xBinarySemaphore,0) == pdTRUE)
		{
			printf("task2 ok\r\n");
			xSemaphoreGive(xBinarySemaphore);
		}else{
			printf("task2 no\r\n");
		}
		if(i%10 == 0)
		{
			xSemaphoreGive(xBinarySemaphore);
		}

		vTaskDelay(10);

	}
}

int main()
{
	USART_Config();
	/* 创建二进制信号量 */
	xBinarySemaphore = xSemaphoreCreateBinary();
	xSemaphoreGive(xBinarySemaphore);

	xTaskCreate(vTask1,"t1", 128, NULL, 2, NULL );
	
	xTaskCreate(vTask2,"t2", 128, NULL, 2, NULL );
	vTaskStartScheduler();
	
	while(1){
		
	}
	
}


1.5.1 计数值信号量


注意配置:
#define configUSE_COUNTING_SEMAPHORES 1


#include "stm32f10x.h"              // Device header
#include "usart.h"
#include "stdio.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
 #include "queue.h"

SemaphoreHandle_t xBinarySemaphore = NULL;

void vTask1(void *param)
{
	
	int i = 0;
	printf("task1\r\n");
	while(1)
	{
		i++;

		if(xSemaphoreTake(xBinarySemaphore,0) == pdTRUE)
		{
			printf("task1 g\r\n");
			
		}
		if(i%10 == 0)
		{
			xSemaphoreGive(xBinarySemaphore);
			i = 0;
			printf("task1 r\r\n");
		}
		vTaskDelay(5);
		
	}
}

void vTask2(void *param)
{
	int i = 0;
	printf("task2\r\n");
	while(1)
	{
		i++;
		if(xSemaphoreTake(xBinarySemaphore,0) == pdTRUE)
		{
			printf("task2 g\r\n");
		}
		if(i%10 == 0)
		{
			xSemaphoreGive(xBinarySemaphore);
			i = 0;
			printf("task2 r\r\n");
		}
		vTaskDelay(5);
	}
}

void vTask3(void *param)
{
	int i = 0;
	printf("task3\r\n");
	while(1)
	{
		i++;

		if(xSemaphoreTake(xBinarySemaphore,0) == pdTRUE)
		{
			printf("task3 g\r\n");
		}
		if(i%10 == 0)
		{
			xSemaphoreGive(xBinarySemaphore);
			i = 0;
			printf("task3 r\r\n");
		}
	vTaskDelay(5);
		

	}
}

int main()
{
	USART_Config();
//	/* 创建二进制信号量 */
//	xBinarySemaphore = xSemaphoreCreateBinary();
//	xSemaphoreGive(xBinarySemaphore);
	
	xBinarySemaphore = xSemaphoreCreateCounting(2,0);
	
	xTaskCreate(vTask1,"t1", 128, NULL, 2, NULL );
	xTaskCreate(vTask2,"t2", 128, NULL, 2, NULL );
	xTaskCreate(vTask3,"t3", 128, NULL, 2, NULL );
	vTaskStartScheduler();
	
	while(1){
		
	}
	
}




二、互斥量

2.1 什么是互斥量

互斥信号量是包含优先级继承机制的二进制信号量。二进制信号量能更好实现实现同步(任务间或任务与中断之间), 而互斥信号量有助于更好实现简单互斥(即相互排斥)。
优先级继承是一种解决实时系统中任务调度引起的优先级翻转问题的机制。在具体的任务调度中,当一个高优先级任务等待一个低优先级任务所持有的资源时,系统会提升低优先级任务的优先级,以避免高优先级任务长时间等待的情况。用于临界资源的保护一般建议使用互斥量。

2.2 优先级继承机制

首先考虑为什么需要优先级继承?什么是优先级反转问题?

优先级翻转是一个在实时系统中可能出现的问题,特别是在多任务环境中。该问题指的是一个较低优先级的任务阻塞了一个较高优先级任务的执行,从而导致高优先级任务无法及时完成。
典型的优先级翻转场景如下:
任务A(高优先级):拥有高优先级,需要访问共享资源,比如一个关键数据结构。
任务B(低优先级):拥有低优先级,目前正在访问该共享资源。
任务C(中优先级):位于任务A和任务B之间,具有介于两者之间的优先级。
具体流程如下:
(1)任务A开始执行,但由于任务B正在访问共享资源,任务A被阻塞等待。
(2)任务C获得执行权,由于优先级高于任务B,它可以抢占任务B。
(3)任务C执行完成后,任务B被解除阻塞,开始执行,完成后释放了共享资源。
(4)任务A重新获取执行权,继续执行。
这个过程中,任务A因为资源被占用而被阻塞,而任务B却被中优先级的任务C抢占,导致任务B无法及时完成。这种情况称为优先级翻转,因为任务C的介入翻转了高优先级任务A的执行顺序。
在这里插入图片描述

在 FreeRTOS 操作系统中为了降低优先级翻转问题利用了优先级继承算法。优先级继承算法是指,暂时提高某个占有某种资源的低优先级任务的优先级,使之与在所有等待该资源的任务中优先级最高那个任务的优先级相等,而当这个低优先级任务执行完毕释放该资源时,优先级重新回到初始设定值。因此,继承优先级的任务避免了系统资源被任何中间优先级的任务抢占。
互斥量与二值信号量最大的不同是:互斥量具有优先级继承机制,而信号量没有。也就是说,某个临界资源受到一个互斥量保护,如果这个资源正在被一个低优先级任务使用,那么此时的互斥量是闭锁状态,也代表了没有任务能申请到这个互斥量,如果此时一个高优先级任务想要对这个资源进行访问,去申请这个互斥量,那么高优先级任务会因为申请不到互斥量而进入阻塞态,那么系统会将现在持有该互斥量的任务的优先级临时提升到与高优先级任务的优先级相同,这个优先级提升的过程叫做优先级继承。这个优先级继承机制确保高优先级任务进入阻塞状态的时间尽可能短,以及将已经出现的“优先级翻转”危害降低到最小。

在这里插入图片描述
任务的优先级在创建的时候就已经是设置好的,高优先级的任务可以打断低优先级的任务,抢占 CPU 的使用权。但是在很多场合中,某些资源只有一个,当低优先级任务正在占用该资源的时候,即便高优先级任务也只能乖乖的等待低优先级任务使用完该资源后释放资源。这里高优先级任务无法运行而低优先级任务可以运行的现象称为“优先级翻转”。为什么说优先级翻转在操作系统中是危害很大?因为在我们一开始创造这个系统的时候,我们就已经设置好了任务的优先级了,越重要的任务优先级越高。但是发生优先级翻转,对我们操作系统是致命的危害,会导致系统的高优先级任务阻塞时间过长。
举个例子,现在有 3 个任务分别为 H 任务(High)、M 任务(Middle)、L 任务(Low),3 个任务的优先级顺序为 H 任务>M 任务>L 任务。正常运行的时候 H 任务可以打断 M 任务与 L 任务,M 任务可以打断 L 任务,假设系统中有一个资源被保护了,此时该资源被 L 任务正在使用中,某一刻,H 任务需要使用该资源,但是 L 任务还没使用完,H任务则因为申请不到资源而进入阻塞态,L 任务继续使用该资源,此时已经出现了“优先级翻转”现象,高优先级任务在等着低优先级的任务执行,如果在 L 任务执行的时候刚好M 任务被唤醒了,由于 M 任务优先级比 L 任务优先级高,那么会打断 L 任务,抢占了CPU 的使用权,直到 M 任务执行完,再把 CUP 使用权归还给 L 任务,L 任务继续执行,等到执行完毕之后释放该资源,H 任务此时才从阻塞态解除,使用该资源。这个过程,本来是最高优先级的 H 任务,在等待了更低优先级的 L 任务与 M 任务,其阻塞的时间是 M任务运行时间+L 任务运行时间,这只是只有 3 个任务的系统,假如很多个这样子的任务打断最低优先级的任务,那这个系统最高优先级任务岂不是崩溃了,这个现象是绝对不允许出现的,高优先级的任务必须能及时响应。所以,没有优先级继承的情况下,使用资源保护,其危害极大。
优先级继承后流程:
在这里插入图片描述总结来说就是,高优先级因为等待低优先级释放互斥量的时候,中优先级打断低优先级任务进行。导致高优先级一直在等待。优先级继承就是让中优先级不能打断低优先级任务进行。

2.3 互斥量应用场景

互斥量的使用比较单一,因为它是信号量的一种,并且它是以锁的形式存在。在初始化的时候,互斥量处于开锁的状态,而被任务持有的时候则立刻转为闭锁的状态。
互斥量更适合于:

  • 可能会引起优先级翻转的情况。

递归互斥量更适用于:

  • 任务可能会多次获取互斥量的情况下。这样可以避免同一任务多次递归持有而造
    成死锁的问题。

多任务环境下往往存在多个任务竞争同一临界资源的应用场景,互斥量可被用于对临界资源的保护从而实现独占式访问。另外,互斥量可以降低信号量存在的优先级翻转问题带来的影响。比如有两个任务需要对串口进行发送数据,其硬件资源只有一个,那么两个任务肯定不能同时发送啦,不然导致数据错误,那么,就可以用互斥量对串口资源进行保护,当一个任务正在使用串口的时候,另一个任务则无法使用串口,等到任务使用串口完毕之后,另外一个任务才能获得串口的使用权。另外需要注意的是互斥量不能在中断服务函数中使用,因为其特有的优先级继承机制只在任务起作用,在中断的上下文环境毫无意义。
运行机制:
在这里插入图片描述

2.4 互斥量函数

2.4.1 创建

互斥量是一种特殊的二进制信号量。
使用互斥量时,先创建、然后去获得、释放它。使用句柄来表示一个互斥量。
创建互斥量的函数有2种:动态分配内存,静态分配内存,函数原型如下:

/* 创建一个互斥量,返回它的句柄。
* 此函数内部会分配互斥量结构体
* 返回值: 返回句柄,非 NULL 表示成功
*/
SemaphoreHandle_t xSemaphoreCreateMutex( void );
/* 创建一个互斥量,返回它的句柄。
* 此函数无需动态分配内存,所以需要先有一个 StaticSemaphore_t 结构体,并传入它的
指针
* 返回值: 返回句柄,非 NULL 表示成功
*/
SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer );

2.4.2 其他函数

互斥量不能在 ISR 中使用。
各类操作函数,比如删除、give/take,跟一般是信号量是一样的。

/*
* xSemaphore: 信号量句柄,你要删除哪个信号量, 互斥量也是一种信号量
*/
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
/* 释放 */
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
/* 释放(ISR 版本) */
BaseType_t xSemaphoreGiveFromISR(
 	SemaphoreHandle_t xSemaphore,
 	BaseType_t *pxHigherPriorityTaskWoken
 );
/* 获得 */
BaseType_t xSemaphoreTake(
 	SemaphoreHandle_t xSemaphore,
 	TickType_t xTicksToWait
 );
/* 获得(ISR 版本) */
xSemaphoreGiveFromISR(
 	SemaphoreHandle_t xSemaphore,
 	BaseType_t *pxHigherPriorityTaskWoken
 );

2.5 互斥量实例


注意配置:
#define configUSE_MUTEXES 1


#include "stm32f10x.h"              // Device header
#include "usart.h"
#include "stdio.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
 #include "queue.h"

SemaphoreHandle_t xBinarySemaphore = NULL;
SemaphoreHandle_t xMutex;

void vTask1(void *param)
{
	
	int i = 0;
	printf("task1\r\n");
	while(1)
	{
		i++;

		if(xSemaphoreTake(xMutex,0) == pdTRUE)
		{
			printf("task1 g\r\n");
			
		}
		if(i%10 == 0)
		{
			xSemaphoreGive(xMutex);
			i = 0;
			printf("task1 r\r\n");
		}
		vTaskDelay(5);
		
	}
}

void vTask2(void *param)
{
	int i = 0;
	printf("task2\r\n");
	while(1)
	{
		i++;
		if(xSemaphoreTake(xMutex,0) == pdTRUE)
		{
			printf("task2 g\r\n");
		}
		if(i%10 == 0)
		{
			xSemaphoreGive(xMutex);
			i = 0;
			printf("task2 r\r\n");
		}
		vTaskDelay(5);
	}
}


int main()
{
	USART_Config();
//	/* 创建二进制信号量 */
//	xBinarySemaphore = xSemaphoreCreateBinary();
//	xSemaphoreGive(xBinarySemaphore);
//	/*创建计数值信号量*/
//	xBinarySemaphore = xSemaphoreCreateCounting(2,0);
	/*创建互斥*/
	/* 创建互斥量 */
	xMutex = xSemaphoreCreateMutex();
	xTaskCreate(vTask1,"t1", 128, NULL, 2, NULL );
	xTaskCreate(vTask2,"t2", 128, NULL, 2, NULL );
	
	vTaskStartScheduler();
	
	while(1){
		
	}
	
}
 


结尾

本篇进行了信号量和互斥量的讲解,原理还是利用队列来实现,可以理解为阉割版队列,好好想想他们的应用场景

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值