【Freertos基础入门】深入浅出信号量

简介: 【Freertos基础入门】深入浅出信号量

前言


本系列基于stm32系列单片机来使用freerots

FreeRTOS是一个流行的实时操作系统,提供了许多功能强大的特性,其中包括信号量。信号量是一种在并发编程中常用的同步机制,用于实现资源的共享和互斥访问。本文将介绍FreeRTOS中的信号量,包括其基本概念、用法和示例。


一、Freertos的信号量是什么?


当我们在编写多任务并发程序时,经常会遇到需要多个任务共享资源的情况,比如共享一个打印机、共享一个数据缓冲区等。在这种情况下,为了保证数据的正确性和避免冲突,我们需要一种机制来控制任务对资源的访问。FreeRTOS提供了信号量来解决这个问题。


可以将信号量看作是一种特殊的计数器。它用来记录资源的可用数量。当某个任务想要使用资源时,它需要先获取(acquire)信号量。如果信号量的计数器大于0,那么该任务就可以继续执行,并且计数器会递减。这样其他任务在同一时间就无法获取相同的资源。当任务使用完资源后,它需要释放(release)信号量,这时计数器会递增,其他任务就有机会获取资源了。


实际上,信号量可以用于两种情况:互斥访问和任务同步。在互斥访问中,信号量起到了一种锁的作用,确保同一时间只有一个任务能够访问共享资源,这样可以保证数据的一致性。而在任务同步中,信号量可以用来控制多个任务的执行顺序,一个任务在等待某个事件发生时,可以等待一个信号量,当其他任务触发了这个事件并释放了信号量,等待的任务就能继续执行了。


在FreeRTOS中,我们可以使用一些函数来创建和操作信号量。通过合理地使用信号量,我们可以确保多个任务能够安全地共享资源,并且按照设计的顺序进行执行,从而提高系统的并发性能。


二、二进制信号量和计数型信号量是什么?


当我们使用FreeRTOS的信号量时,有两种常见的类型:二进制信号量和计数型信号量。


二进制信号量(Binary Semaphore):

二进制信号量就像一把开关,只有两个状态:开和关。它的计数器只能是0或1。当一个任务获取二进制信号量时,如果它是开的(计数器为1),那么它可以继续执行;如果它是关的(计数器为0),那么获取操作会被阻塞,直到有其他任务释放了信号量让它变为开。这种信号量常用于实现互斥访问,确保同一时间只有一个任务可以访问共享资源。


计数型信号量(Counting Semaphore):

计数型信号量的计数器不仅仅只是0和1,它可以是任意非负整数。当一个任务获取计数型信号量时,如果计数器大于0,那么计数器会减1,并且获取操作会立即返回;如果计数器为0,那么获取操作会被阻塞,直到有其他任务释放了信号量让计数器变为非零。这种信号量常用于表示资源的可用数量,比如空闲的内存块或可用的任务槽位。


区别:

二进制信号量只有两个状态(开和关),而计数型信号量的计数器可以是任意非负整数。

二进制信号量常用于互斥访问,确保同一时间只有一个任务可以访问共享资源。计数型信号量通常用于表示可用资源的数量。

二进制信号量的计数器只能通过获取和释放操作切换状态。计数型信号量的计数器可以通过获取和释放操作增加或减少。

二进制信号量只能实现互斥访问,而计数型信号量不仅可以实现互斥访问,还可以用于任务同步、事件触发等应用场景。

要根据具体的需求选择信号量的类型,以及合适的获取和释放操作,以确保任务间的资源共享和同步能够正常进行。


三、信号量初步了解


1.二进制信号量的使用

1、动态创建函数xSemaphoreCreateBinary()
函数原型如下:

/* 创建一个二进制信号量,返回它的句柄。
* 此函数内部会分配信号量结构体
* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateBinary( void );


2、静态创建函数xSemaphoreCreateBinaryStatic()
函数原型如下:

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


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

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


4、give/take函数
在任务中使用:


1.xSemaphoreGive
函数原型如下:

/*参数为要give的信号量,
返回值:pdTRUE表示成功,
如果二进制信号量的计数值已经是1,再次调用此函数则返回失败;
如果计数型信号量的计数值已经是最大值,再次调用此函数则返回失败*/
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );


2.xSemaphoreTake

函数原型如下:

/*参数为要take的信号量,等待的时间
返回值:pdTRUE表示成功
*/
BaseType_t xSemaphoreTake(
SemaphoreHandle_t xSemaphore,
TickType_t xTicksToWait
);


在中断中使用:


1.xSemaphoreGiveFromISR

函数原型如下:

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


2.xSemaphoreTakeFromISR

函数原型如下:

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


2.give和take是什么?

046454935f0347e3bc3c185c81a093d6.png


通过上图可以看到任务1先take,然后二进制信号量变成1,当任务2需要take时,发现二进制信号量是1,则任务等待任务1give后,二进制信号量变成0后任务2才take到信号量,然后信号量又变成1,从此往复。


3.为什么需要动态和静态创建的方式?

在前面的博客我们看到了很多函数,在queue中,他有动态创建和静态创建,那freertos的编写代码的人难道没事找事吗?肯定不然,

下面就是freertos代码编写者为什么需要这样做的原因:

FreeRTOS提供了两种方式来创建任务、队列、信号量等内核对象:动态创建和静态创建。


动态创建:


动态创建是在运行时动态分配内存来创建内核对象。通过调用API函数,FreeRTOS会在堆上为内核对象动态分配内存空间。

动态创建灵活,可以根据具体的需求动态调整内核对象的数量和大小。

动态创建可以在运行时创建和删除内核对象,可以根据系统的动态需求进行动态管理。

动态创建适用于需要动态管理内存的场景,比如对象的数量和大小在运行时发生变化,或者内存资源受限的情况。

静态创建:


静态创建是在编译时静态分配内存来创建内核对象。用户需要提前为内核对象预留内存空间,并在创建时使用静态分配的内存空间。

静态创建可以避免在运行时进行内存分配和释放的开销,节省了系统的运行时间和资源消耗。

静态创建需要提前估计和分配内存空间,并在编译时固定了内核对象的数量和大小。

静态创建适用于内存资源相对稳定,内核对象数量和大小较为确定的场景,可以提高系统的可靠性和性能。

动态创建和静态创建各有其适用场景。动态创建适合需要在运行时动态管理内核对象的情况,而静态创建适合内存资源相对稳定,对象数量和大小可以在编译时确定的情况。选择合适的创建方式可以根据系统的需求、资源约束和代码设计来做出权衡。


四、二进制信号量示例代码

uint32_t count = 0;
SemaphoreHandle_t s;
void Task(void *p)
{
  while(1)
  {
    xSemaphoreTake(s,pdMS_TO_TICKS(1000));//信号量1
    printf("Count:%d\r\n",count);
    xSemaphoreGive(s);//信号量变成0
    vTaskDelay(pdMS_TO_TICKS(500));
  }
  vTaskDelete(NULL);
  vvSemaphoreDelete(s);//在任务的外面删除,如果删除了,可能是出错了!!!
}
void Task2(void *p)
{
  while(1)
  {
    xSemaphoreTake(s,pdMS_TO_TICKS(1000));//信号量1
    count++;
    xSemaphoreGive(s);//信号量变成0
    vTaskDelay(1);
  }
  vTaskDelete(NULL);
}
void TaskTest(void)
{
  s = xSemaphoreCreateBinary( );
  xTaskCreate(Task,"MyTask",50,&count,1,NULL);
  xTaskCreate(Task2,"MyTask2",50,&count,3,NULL);
  vTaskStartScheduler();
}


4a5b64dbea3b434b9ead59479031974b.png

注意:如果没有信号量,可以就会冲突


总结


信号量是FreeRTOS中用于实现资源的共享和互斥访问的重要机制。本文介绍了信号量的基本概念、创建和初始化、获取和释放操作以及用途。通过合理地使用信号量,可以提高系统并发性能,并确保共享资源的正确访问。在实际应用中,根据具体需求选择适当的信号量类型和操作方式,可以更好地利用FreeRTOS的功能来设计健壮的并发应用。

相关文章
|
8月前
|
传感器 调度 开发者
【Freertos基础入门】freertos任务的优先级
【Freertos基础入门】freertos任务的优先级
181 0
|
8月前
|
调度 C语言 芯片
FreeRTOS学习笔记—基础知识
本文简要介绍了什么是RTOS,介绍了前后台系统和RTOS的工作流程。此外,简单介绍了FreeRTOS的特点,相关概念和优点。最后,介绍了下载FreeRTOS的方法。
143 0
FreeRTOS学习笔记—基础知识
|
8月前
|
存储 消息中间件 API
FreeRTOS入门教程(堆和栈)
FreeRTOS入门教程(堆和栈)
228 0
|
18天前
FreeRTOS入门教程(信号量的具体使用)
FreeRTOS入门教程(信号量的具体使用)
56 0
|
18天前
|
存储
FreeRTOS深入教程(信号量源码分析)
FreeRTOS深入教程(信号量源码分析)
80 0
|
18天前
|
算法 调度
FreeRTOS入门教程(互斥锁的概念和函数使用)
FreeRTOS入门教程(互斥锁的概念和函数使用)
119 0
|
18天前
|
消息中间件 算法 调度
FreeRTOS入门教程(同步与互斥)
FreeRTOS入门教程(同步与互斥)
111 0
|
8月前
|
存储 消息中间件 程序员
FreeRTOS学习笔记—任务基础知识
本文学习了FreeRTOS的任务特性,任务状态,任务优先级,任务实现,任务控制块,任务堆栈的知识。对于一些重点内容,做出了特殊标记。
34 0
FreeRTOS学习笔记—任务基础知识
|
8月前
【Freertos基础入门】深入浅出freertos互斥量
【Freertos基础入门】深入浅出freertos互斥量
|
8月前
|
存储 缓存 安全
[笔记]C++并发编程实战 《二》线程管理(一)
[笔记]C++并发编程实战 《二》线程管理

热门文章

最新文章