转载自 https://blog.csdn.net/zhoutaopower/article/details/107359095

在裸机编程中这样使用过一个变量:用于标记某个事件是否发生,或者标志一下某个东西是否正在被使用,如果是被占用了,或者没有发生,我们就不对它进行操作。

信号量  Semaphore

FreeRTOS 中使用信号量来做同步,信号量可以在任务中使用作为任务与任务间的同步,也可以在中断中使用(带 FromISR 的版本)中断与任务间的同步;

针对不同的应用场景,信号量分为两种:

1、二值信号量;

2、计数信号量;

二值信号量 (Binary Semaphores)

为什么叫二值信号量?

因为信号量资源被获取了,信号量值就是0;信号量资源被释放,信号量值就是1,把这种只有0和1两种情况的信号量称之为二值信号量。

在多任务系统中,经常使用到二值信号量。某个任务需要等待一个标记,在任务中轮询这个标记有没有被置位,很消耗CPU,更好的做法是让任务大部分时间处于阻塞状态,让其他任务执行,等到某些事件发生后,该任务才被唤醒去执行,可以使用二值信号量来实现这种同步。任务执行完毕不用归还信号量。

二值信号量在任务与中断同步的应用场景:

比如:在串口接收中,我们不知道啥时候有数据发过来,有一个任务是专门做接收这些数据并处理的。总不能在任务中每时每刻都在查询数据有没有发过来吧(也不是不能,只是这样太傻,浪费CPU资源)。这种情况下使用二值信号量是一个很好的办法,当没有数据到来的时候,任务就进入阻塞态,不参与任务的调度,等到数据到来了,释放一个二值信号量,任务就立即从阻塞态解除,进入就绪态,然后运行的时候处理数据。这样资源利用率更高。

二值信号量的运作机制

创建信号量时: 系统会为创建的信号量对象分配内存,并把可用的信号量初始化为用户自定义的个数,显然二值信号量的最大可用信号量个数是1.

获取二值信号量:任何任务都可以从创建的二值信号量资源中获取一个二值信号量,若获取成功则返回正确,否则就根据设置的阻塞时间来等待其他的任务和中断释放信号量,等待的时间,任务处于阻塞态,任务将被挂到该信号量的阻塞等待列表中。

          

常用信号量接口函数

直接到任务通知代替二值信号量,效果更好,更快,更省内存

上面的情况,可以将二值信号量理解为长度为 1 的 Queue,实际上,它的实现,也是用 Queue;

操作二值信号量,分为两个行为:Give 和 Take:

1、Give:往二值信号量写 1;

2、Take:获取二值信号量;

上面的例子可以理解为下面的顺序:

首先初始化一个二值信号量,任务尝试获取二值信号量,但是获取失败,使得任务进入 Blocked 状态:

中断来了,执行 ISR,往这个二值信号量写 1;

此刻,等待在这个二值信号量上的任务会被解除阻塞,投入运行:

任务获取到二值信号量,开始执行任务:

执行完毕后,再次去 Take 失败,再次进入 Blocked:

1.2、APIs

  1.2.1 定义和产生一个二值信号量

  osSemaphoreDef(myBinarySem01); /*definition of myBinarySem01 */
myBinarySem01Handle = osSemaphoreCreate(osSemaphore(myBinarySem01), 1);/*creation of myBinarySem01*/

1.2.1、xSemaphoreCreateBinary

应用代码使用 xSemaphoreCreateBinary 来创建二值信号量:

SemaphoreHandle_t xSemaphoreCreateBinary( void );

有一个返回值:

Return:创建成功返回一个信号量的句柄,失败返回 NULL;

1.2.2、xSemaphoreTake / xSemaphoreTakeFromISR

应用代码使用 xSemaphoreTake() 来获取一个二值信号量:

注意:不要在 ISR 中使用 xSemaphoreTake 应该使用对应的 xSemaphoreTakeFromISR;

BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );

有两个入参,一个返回值:

xSemaphore:创建信号量的句柄;

xTicksToWait:如果获取不到信号量,最大阻塞的时间;如果设置为 0,那么立即返回(不阻塞),如果配置为 portMAX_DELAY,则无限制等待;

Return:返回 pdPASS 代表正常获取到信号量,返回 pdFALSE 代表获取失败;

1.2.3、xSemaphoreGive / xSemaphoreGiveFromISR

应用代码使用 xSemaphoreGive() 来设置一个二值信号量:

注意:不要在 ISR 中使用 xSemaphoreGive 应该使用对应的 xSemaphoreGiveFromISR;

  1.  
    BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore,
  2.  
    BaseType_t *pxHigherPriorityTaskWoken );

两个入参,一个返回值:

xSemaphore:信号量的句柄;

pxHigherPriorityTaskWoken :如果该信号量会导致一个比当前任务优先级更高的任务解除阻塞,那么返回 pdTRUE;

Return:返回是否设置信号量成功,成功返回 pdTRUE,否则返回 pdFALSE;

Example:

有一个周期性的任务,每隔 500ms 定时产生一个软件中断:

/* The number of the software interrupt used in this example. The code shown is from
the Windows project, where numbers 0 to 2 are used by the FreeRTOS Windows port
itself, so 3 is the first number available to the application. */ #define mainINTERRUPT_NUMBER 3
static void vPeriodicTask( void *pvParameters )
{
const TickType_t xDelay500ms = pdMS_TO_TICKS( 500UL );
/* As per most tasks, this task is implemented within an infinite loop. */
for( ;; )
{
/* Block until it is time to generate the software interrupt again. */
vTaskDelay( xDelay500ms );
/* Generate the interrupt, printing a message both before and after
the interrupt has been generated, so the sequence of execution is evident
from the output.
The syntax used to generate a software interrupt is dependent on the
FreeRTOS port being used. The syntax used below can only be used with
the FreeRTOS Windows port, in which such interrupts are only simulated. */
vPrintString( "Periodic task - About to generate an interrupt.\r\n" );
vPortGenerateSimulatedInterrupt( mainINTERRUPT_NUMBER );
vPrintString( "Periodic task - Interrupt generated.\r\n\r\n\r\n" );
}
}

下面是一个期望获取二值信号量的任务:

static void vHandlerTask( void *pvParameters )
{
/* As per most tasks, this task is implemented within an infinite loop. */
for( ;; )
{
/* Use the semaphore to wait for the event. The semaphore was created
before the scheduler was started, so before this task ran for the first
time. The task blocks indefinitely, meaning this function call will only
return once the semaphore has been successfully obtained - so there is
no need to check the value returned by xSemaphoreTake(). */
xSemaphoreTake( xBinarySemaphore, portMAX_DELAY );
/* To get here the event must have occurred. Process the event (in this
Case, just print out a message). */
vPrintString( "Handler task - Processing event.\r\n" );
}
}

接下来是产生中断的那个 ISR,根据传入的 pxHigherPriorityTaskWoken 来判断是否要进行上下文切换:

static uint32_t ulExampleInterruptHandler( void )
{
BaseType_t xHigherPriorityTaskWoken; /* The xHigherPriorityTaskWoken parameter must be initialized to pdFALSE as
it will get set to pdTRUE inside the interrupt safe API function if a
context switch is required. */
xHigherPriorityTaskWoken = pdFALSE; /* 'Give' the semaphore to unblock the task, passing in the address of
xHigherPriorityTaskWoken as the interrupt safe API function's
pxHigherPriorityTaskWoken parameter. */
xSemaphoreGiveFromISR( xBinarySemaphore, &xHigherPriorityTaskWoken ); /* Pass the xHigherPriorityTaskWoken value into portYIELD_FROM_ISR(). If
xHigherPriorityTaskWoken was set to pdTRUE inside xSemaphoreGiveFromISR()
then calling portYIELD_FROM_ISR() will request a context switch. If
xHigherPriorityTaskWoken is still pdFALSE then calling
portYIELD_FROM_ISR() will have no effect. Unlike most FreeRTOS ports, the
Windows port requires the ISR to return a value - the return statement
is inside the Windows version of portYIELD_FROM_ISR(). */ portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

主函数为:

int main( void )
{
/* Before a semaphore is used it must be explicitly created. In this example
a binary semaphore is created. */
xBinarySemaphore = xSemaphoreCreateBinary();
/* Check the semaphore was created successfully. */
if( xBinarySemaphore != NULL )
{
/* Create the 'handler' task, which is the task to which interrupt
processing is deferred. This is the task that will be synchronized with
the interrupt. The handler task is created with a high priority to ensure
it runs immediately after the interrupt exits. In this case a priority of
3 is chosen. */
xTaskCreate( vHandlerTask, "Handler", 1000, NULL, 3, NULL );
/* Create the task that will periodically generate a software interrupt.
This is created with a priority below the handler task to ensure it will
get preempted each time the handler task exits the Blocked state. */
xTaskCreate( vPeriodicTask, "Periodic", 1000, NULL, 1, NULL );
/* Install the handler for the software interrupt. The syntax necessary
to do this is dependent on the FreeRTOS port being used. The syntax
shown here can only be used with the FreeRTOS windows port, where such
interrupts are only simulated. */
vPortSetInterruptHandler( mainINTERRUPT_NUMBER, ulExampleInterruptHandler );
/* Start the scheduler so the created tasks start executing. */
vTaskStartScheduler();
}
/* As normal, the following line should never be reached. */
for( ;; );
}

由于 vHandlerTask 优先级为最高,所以他会先运行,并阻塞在二值信号量的获取上;

vHandlerTask 进入阻塞后,vPeriodicTask 会周期性的去拉一个中断,导致进入 ISR;

在 ISR 中设置了信号量,导致 vHandlerTask 被解除阻塞,进入运行,抢占 vPeriodicTask;

vHandlerTask 运行完后,再次进入阻塞;

上述的场景中,二值信号量是可以胜任的,但是试想,真实的系统中,IRQ 是随时都可能来的,如果一种情况下,IRQ 来的比较频繁,当 Task 正在获得二值信号量处理的时候,又连续来了 2 个 IRQ,由于二值信号量只能存储一次事件,那么必然导致事件的丢失,如下所示:

此刻二值信号量显得有点力不从心,接下来就看计数信号量的了

2、Counting Semaphores

2.1、Usage

二值信号量可以看成是只有一个长度的 Queue,计数信号量就是多个长度的 Queue(只关心长度,不关心 Queue 内容);

要使用计数信号量,需要配置 configUSE_COUNTING_SEMAPHORES 为 1;

计数信号量主要可以用作如下两个方面:

1、事件计数:这种场景下,事件通过 Give 来往计数信号量中记录事件发生的次数,另一端的 Task 通过 Take 来进行每一次事件的处理;一般的,信号量的计数被初始化为 0;

2、资源管理:这种场景下,信号量代表可用资源的数目,一般的,这种情况将信号量初始化为一个资源的数目,任务每次获取资源,都将资源减一;如果信号量为 0 说明没有可用的资源了;一旦任务完成,便通过 Give 来释放资源,增加信号量的计数;

同样是之前的例子,当 IRQ 来的过快,任务来不及处理完的情况下,多余的 Event 会在计数信号量中保存,直到任务完成,再次进入阻塞:

2.2、APIs

2.2.1、xSemaphoreCreateCounting

创建一个计数信号量使用 xSemaphoreCreateCounting 接口:

  1.  
    SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,
  2.  
    UBaseType_t uxInitialCount );

两个入参,一个返回值:

uxMaxCount:指的是信号量的最大计数个数;

uxInitialCount:被初始化的个数;

Return:如果成功,返回信号量的句柄,否则返回 NULL;

其余的 Get 和 Take 和二值信号量一样,不在赘述;

FreeRTOS --(15)信号量之概述的更多相关文章

  1. FreeRTOS 计数信号量

    以下转载自安富莱电子: http://forum.armfly.com/forum.php 本章节开始讲解 FreeRTOS 任务间的同步和资源共享机制,计数信号量. FreeRTOS 中计数信号量的 ...

  2. 嵌入式系统FreeRTOS — 互斥信号量

    互斥信号量可以在资源保护的时候很有帮助.用于控制在两个或多个任务间访问共享资源.任务1里面用互斥,那么任务2只能等任务1访问完再访问同一个变量. 比如全局变量double gADC_value[CH_ ...

  3. stm32中使用cubemx配置freertos的信号量大小

    在配置freertos的情况下,cubemx会自动计算每个任务.信号,队列和软件定时器的使用堆栈大小,因此要合理规划 信号量默认是88byte 任务根据设定来计算,我默认配置是128,则最终是624b ...

  4. FreeRTOS的信号量和互斥量

    1. 理解如下,言简意赅的说,信号量解决同步,互斥量解决竞争. 信号量用于同步,主要任务间和中断间同步:互斥量用于互锁,用于保护同时只能有一个任务访问的资源,为资源上一把锁. 互斥量具有优先级继承,信 ...

  5. FreeRTOS互斥信号量

    API函数 #if( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) #define xSemaphoreCreateMutex() xQueueCreateMutex ...

  6. FreeRTOS 任务计数信号量,任务二值信号量,任务事件标志组,任务消息邮箱

    以下基础内容转载自安富莱电子: http://forum.armfly.com/forum.php 本章节为大家讲解 FreeRTOS 计数信号量的另一种实现方式----基于任务通知(Task Not ...

  7. FreeRTOS 二值信号量,互斥信号量,递归互斥信号量

    以下转载自安富莱电子: http://forum.armfly.com/forum.php 本章节讲解 FreeRTOS 任务间的同步和资源共享机制,二值信号量. 二值信号量是计数信号量的一种特殊形式 ...

  8. FreeRTOS系列第20篇---FreeRTOS信号量API函数

    FreeRTOS的信号量包括二进制信号量.计数信号量.相互排斥信号量(以后简称相互排斥量)和递归相互排斥信号量(以后简称递归相互排斥量).我们能够把相互排斥量和递归相互排斥量看成特殊的信号量. 信号量 ...

  9. FreeRTOS相关转载-(朱工的专栏)

    FreeRTOS系列第1篇---为什么选择FreeRTOS? 1.为什么学习RTOS? 作为基于ARM7.Cortex-M3硬件开发的嵌入式工程师,我一直反对使用RTOS.不仅因为不恰当的使用RTOS ...

随机推荐

  1. 深入理解Java虚拟机-内存分配与回收策略

    一.内存分配策略 新生代中98%的对象都是"朝生夕死"的,所以并不需要按照1:1的比例来划分内存空间,而是将内存(新生代内存)分为一块较大的Eden(伊甸园)空间和两块较小的Sur ...

  2. ruoyi首次使用常见问题的解决方案

    1.导入项目之后,下载依赖包之后,模块的依赖项飘红(我这里无法复现,当参考图吧) 解决方法: 2.ruoyi框架代码生成之后,需要自己进行替换到指定位置.相应的官方文档位置,否则,可能会出现404,访 ...

  3. Oracle入门基础(十一)一一PL/SQL基本语法

    1.打印Hello World declare --说明部分 begin --程序 dbms_output.put_line('Hello World'); end; 2.引用型变量 查询并打印783 ...

  4. 什么情况下使用break关键字?什么情况下使用Continue关键字

    return用于返回一个值给函数,或者直接使用,结束函数:break用于结束循环,即从循环中退出:continue用于结束当次循环,直接进行下次循环.

  5. 学习Git(二)

    常用命令 git add 添加 git status 查看状态 git status -s 状态概览 git diff 对比 git diff --staged 对比暂存区 git commit 提交 ...

  6. 单例模式应用 | Shared_ptr引用计数管理器

    在我们模拟设计 shared_ptr 智能指针时发现,不同类型的 Shared_ptr 不能使用同一个引用计数管理器,这显然会造成内存上的浪费.因此我们考虑将其设计为单例模式使其所有的 Shared_ ...

  7. .NET Best Practices: Architecture & Design Patterns (5 Days Training)

    .NET Best Practices: Architecture & Design Patterns (5 Days Training) .NET最佳实践:架构及设计模式 5天培训课程 课程 ...

  8. 行内元素的padding和margin是否有效

    行内元素的纵向padding和margin都是不考虑的,这是css规范定义的.inline元素确实可以设置垂直方向的padding和margin值,但是inline元素的margin和padding的 ...

  9. 记一个ios滚动穿透问题

    直接上代码 <body style="overflow: hidden;-webkit-overflow-scrolling: touch;"> <div id= ...

  10. Codepen 每日精选(2018-4-16)

    按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以打开原始页面. 内容切换的交互效果https://codepen.io/jcoulterde... 报价卡片的交互效果ht ...