FreeRTOS --(15)信号量之概述
阅读原文时间:2023年07月10日阅读:2

转载自 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.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

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

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

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

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

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

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

2.2.1、xSemaphoreCreateCounting

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

  1. SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,

  2. UBaseType_t uxInitialCount );

两个入参,一个返回值:

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

uxInitialCount:被初始化的个数;

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

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

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章