FreeRTOS--信号量

发布时间 2023-12-02 18:46:08作者: 流翎

示例源码基于FreeRTOS V9.0.0

信号量

1. 概述

消息队列用于传输多个数据,但是有时候只需要传递状态,这个状态值需要用一个数值表示,在这种情况下我们只需要维护一个数值,使用信号量效率更高、更节省内存。

信号量用来表示资源的个数,它是特殊的队列实现,队列数据项为空。

对于二值信号量,队列深度为1,对于计数型信号量,队列深度非0。队列的大小uxMessagesWaiting即表示了信号量值,也就是资源个数。

生成或释放资源时,采用give操作,资源数加1,申请资源时,采用take操作,资源数减1。

2. 接口API

2.1 创建二值信号量

// 定义在semphr.h 中
#define semSEMAPHORE_QUEUE_ITEM_LENGTH		( ( uint8_t ) 0U )

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
	#define vSemaphoreCreateBinary( xSemaphore )																							\
		{																																	\
			( xSemaphore ) = xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE );	\
			if( ( xSemaphore ) != NULL )																									\
			{																																\
				( void ) xSemaphoreGive( ( xSemaphore ) );																					\
			}																																\
		}
#endif

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
	#define xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )
#endif

#if( configSUPPORT_STATIC_ALLOCATION == 1 )
	#define xSemaphoreCreateBinaryStatic( pxStaticSemaphore ) xQueueGenericCreateStatic( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, NULL, pxStaticSemaphore, queueQUEUE_TYPE_BINARY_SEMAPHORE )
#endif
  • vSemaphoreCreateBinary是旧版的创建信号量的接口,创建信号量后计数值初始为1;
  • xSemaphoreCreateBinary是新版的创建信号量的接口,创建信号量后计数值初始为0;
  • xSemaphoreCreateBinaryStatic是静态创建信号量的接口,创建信号量后计数值初始为0;

创建信号量的本质就是创建队列,二值信号量队列的深度是1,数据项大小为0。

队列的创建见 队列

2.2 创建计数型信号量

// 定义在semphr.h 中
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
	#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount ) xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) )
#endif

#if( configSUPPORT_STATIC_ALLOCATION == 1 )
	#define xSemaphoreCreateCountingStatic( uxMaxCount, uxInitialCount, pxSemaphoreBuffer ) xQueueCreateCountingSemaphoreStatic( ( uxMaxCount ), ( uxInitialCount ), ( pxSemaphoreBuffer ) )
#endif /* configSUPPORT_STATIC_ALLOCATION */
  • xSemaphoreCreateCounting用于动态创建计数型信号量,资源总数为uxMaxCount,初始计数值为uxInitialCount;
  • xSemaphoreCreateCountingStatic用于静态创建计数型信号量,资源总数为uxMaxCount,初始计数值为uxInitialCount;

xSemaphoreCreateCounting实际调用的是xQueueCreateCountingSemaphore,实现如下:

// 定义在queue.c
#if( ( configUSE_COUNTING_SEMAPHORES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )

    QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount, const UBaseType_t uxInitialCount )
    {
    QueueHandle_t xHandle;

        configASSERT( uxMaxCount != 0 );
        configASSERT( uxInitialCount <= uxMaxCount );

        xHandle = xQueueGenericCreate( uxMaxCount, queueSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_COUNTING_SEMAPHORE );

        if( xHandle != NULL )
        {
            ( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount;

            traceCREATE_COUNTING_SEMAPHORE();
        }
        else
        {
            traceCREATE_COUNTING_SEMAPHORE_FAILED();
        }

        return xHandle;
    }

#endif /* ( ( configUSE_COUNTING_SEMAPHORES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) ) */

xQueueCreateCountingSemaphore也是调用的xQueueGenericCreate用于创建队列,队列深度uxMaxCount即为资源总数,数据项大小为0,队列大小uxMessagesWaiting即为资源计数,初始计数为uxInitialCount;

xSemaphoreCreateCountingStatic实际调用的是xQueueCreateCountingSemaphoreStatic,实现如下:

// 定义在queue.c
#if( ( configUSE_COUNTING_SEMAPHORES == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 1 ) )

    QueueHandle_t xQueueCreateCountingSemaphoreStatic( const UBaseType_t uxMaxCount, const UBaseType_t uxInitialCount, StaticQueue_t *pxStaticQueue )
    {
    QueueHandle_t xHandle;

        configASSERT( uxMaxCount != 0 );
        configASSERT( uxInitialCount <= uxMaxCount );

        xHandle = xQueueGenericCreateStatic( uxMaxCount, queueSEMAPHORE_QUEUE_ITEM_LENGTH, NULL, pxStaticQueue, queueQUEUE_TYPE_COUNTING_SEMAPHORE );

        if( xHandle != NULL )
        {
            ( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount;

            traceCREATE_COUNTING_SEMAPHORE();
        }
        else
        {
            traceCREATE_COUNTING_SEMAPHORE_FAILED();
        }

        return xHandle;
    }

#endif /* ( ( configUSE_COUNTING_SEMAPHORES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) ) */

xSemaphoreCreateCountingStatic也是调用的xQueueGenericCreateStatic用于创建队列,队列深度uxMaxCount即为资源总数,数据项大小为0,队列大小uxMessagesWaiting即为资源计数,初始计数为uxInitialCount;

2.3 删除信号量

对于动态创建的信号量,二值信号量和计数型信号量均使用同一个接口vSemaphoreDelete来删除信号量。

// 定义在semphr.h 中
#define vSemaphoreDelete( xSemaphore ) vQueueDelete( ( QueueHandle_t ) ( xSemaphore ) )

实质也是删除队列。

2.4 获取信号量

获取信号量,即take操作,二值信号量和计数型信号量均使用同一个接口xSemaphoreTake来获取信号量。

// 定义在semphr.h 中
#define xSemaphoreTake( xSemaphore, xBlockTime )		xQueueGenericReceive( ( QueueHandle_t ) ( xSemaphore ), NULL, ( xBlockTime ), pdFALSE )
#define xSemaphoreTakeFromISR( xSemaphore, pxHigherPriorityTaskWoken )	xQueueReceiveFromISR( ( QueueHandle_t ) ( xSemaphore ), NULL, ( pxHigherPriorityTaskWoken ) )

xSemaphoreTake实质是调用队列的接收接口xQueueGenericReceive,传入的pvBuffer为NULL。

xSemaphoreTakeFromISR是xSemaphoreTake的中断版本。

2.5 释放信号量

释放信号量,即give操作,二值信号量和计数型信号量均使用同一个接口xSemaphoreGive来获取信号量。

// 定义在semphr.h 中
#define xSemaphoreGive( xSemaphore )		xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )
#define xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken )	xQueueGiveFromISR( ( QueueHandle_t ) ( xSemaphore ), ( pxHigherPriorityTaskWoken ) )

xSemaphoreGive实质是调用队列的发送接口xQueueGenericSend,传入的数据项指针pvItemToQueue为NULL。

xSemaphoreGiveFromISR是xSemaphoreGive的中断版本,调用xQueueGiveFromISR实现,其实现基本雷同xQueueGenericSendFromISR。

BaseType_t xQueueGiveFromISR( QueueHandle_t xQueue, BaseType_t * const pxHigherPriorityTaskWoken )
{
BaseType_t xReturn;
UBaseType_t uxSavedInterruptStatus;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;

    /* Similar to xQueueGenericSendFromISR() but used with semaphores where the
    item size is 0.  Don't directly wake a task that was blocked on a queue
    read, instead return a flag to say whether a context switch is required or
    not (i.e. has a task with a higher priority than us been woken by this
    post). */

    configASSERT( pxQueue );

    /* xQueueGenericSendFromISR() should be used instead of xQueueGiveFromISR()
    if the item size is not 0. */
    configASSERT( pxQueue->uxItemSize == 0 );

    /* Normally a mutex would not be given from an interrupt, especially if
    there is a mutex holder, as priority inheritance makes no sense for an
    interrupts, only tasks. */
    configASSERT( !( ( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ) && ( pxQueue->pxMutexHolder != NULL ) ) );

    /* RTOS ports that support interrupt nesting have the concept of a maximum
    system call (or maximum API call) interrupt priority.  Interrupts that are
    above the maximum system call priority are kept permanently enabled, even
    when the RTOS kernel is in a critical section, but cannot make any calls to
    FreeRTOS API functions.  If configASSERT() is defined in FreeRTOSConfig.h
    then portASSERT_IF_INTERRUPT_PRIORITY_INVALID() will result in an assertion
    failure if a FreeRTOS API function is called from an interrupt that has been
    assigned a priority above the configured maximum system call priority.
    Only FreeRTOS functions that end in FromISR can be called from interrupts
    that have been assigned a priority at or (logically) below the maximum
    system call	interrupt priority.  FreeRTOS maintains a separate interrupt
    safe API to ensure interrupt entry is as fast and as simple as possible.
    More information (albeit Cortex-M specific) is provided on the following
    link: http://www.freertos.org/RTOS-Cortex-M3-M4.html */
    portASSERT_IF_INTERRUPT_PRIORITY_INVALID();

    uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
    {
        const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;

        /* When the queue is used to implement a semaphore no data is ever
        moved through the queue but it is still valid to see if the queue 'has
        space'. */
        if( uxMessagesWaiting < pxQueue->uxLength )
        {
            const int8_t cTxLock = pxQueue->cTxLock;

            traceQUEUE_SEND_FROM_ISR( pxQueue );

            /* A task can only have an inherited priority if it is a mutex
            holder - and if there is a mutex holder then the mutex cannot be
            given from an ISR.  As this is the ISR version of the function it
            can be assumed there is no mutex holder and no need to determine if
            priority disinheritance is needed.  Simply increase the count of
            messages (semaphores) available. */
            pxQueue->uxMessagesWaiting = uxMessagesWaiting + 1;

            /* The event list is not altered if the queue is locked.  This will
            be done when the queue is unlocked later. */
            if( cTxLock == queueUNLOCKED )
            {
                #if ( configUSE_QUEUE_SETS == 1 )
                {
                    if( pxQueue->pxQueueSetContainer != NULL )
                    {
                        if( prvNotifyQueueSetContainer( pxQueue, queueSEND_TO_BACK ) != pdFALSE )
                        {
                            /* The semaphore is a member of a queue set, and
                            posting	to the queue set caused a higher priority
                            task to	unblock.  A context switch is required. */
                            if( pxHigherPriorityTaskWoken != NULL )
                            {
                                *pxHigherPriorityTaskWoken = pdTRUE;
                            }
                            else
                            {
                                mtCOVERAGE_TEST_MARKER();
                            }
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }
                    }
                    else
                    {
                        if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
                        {
                            if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
                            {
                                /* The task waiting has a higher priority so
                                record that a context switch is required. */
                                if( pxHigherPriorityTaskWoken != NULL )
                                {
                                    *pxHigherPriorityTaskWoken = pdTRUE;
                                }
                                else
                                {
                                    mtCOVERAGE_TEST_MARKER();
                                }
                            }
                            else
                            {
                                mtCOVERAGE_TEST_MARKER();
                            }
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }
                    }
                }
                #else /* configUSE_QUEUE_SETS */
                {
                    if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
                    {
                        if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
                        {
                            /* The task waiting has a higher priority so record that a
                            context	switch is required. */
                            if( pxHigherPriorityTaskWoken != NULL )
                            {
                                *pxHigherPriorityTaskWoken = pdTRUE;
                            }
                            else
                            {
                                mtCOVERAGE_TEST_MARKER();
                            }
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }
                #endif /* configUSE_QUEUE_SETS */
            }
            else
            {
                /* Increment the lock count so the task that unlocks the queue
                knows that data was posted while it was locked. */
                pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 );
            }

            xReturn = pdPASS;
        }
        else
        {
            traceQUEUE_SEND_FROM_ISR_FAILED( pxQueue );
            xReturn = errQUEUE_FULL;
        }
    }
    portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );

    return xReturn;
}

2.6 关于prvCopyDataToQueue和prvCopyDataFromQueue的说明

申请和释放信号量,调用的是队列的Send和Receive系列函数,涉及到入队和出队的操作,即prvCopyDataToQueue和prvCopyDataFromQueue。

由于信号量的uxItemSize=0,所以,

  • 对于入队prvCopyDataToQueue,仅将uxMessagesWaiting加1;
  • 对于出队prvCopyDataFromQueue,不处理;

3. 总结

二值信号量 计数型信号量
动态创建 xSemaphoreCreateBinary 计数值初始值为0 xSemaphoreCreateCounting
vSemaphoreCreateBinary(过时了) 计数值初始值为1
静态创建 xSemaphoreCreateBinaryStatic xSemaphoreCreateCountingStatic
在任务中使用 在ISR中使用
give xSemaphoreGive xSemaphoreGiveFromISR
take xSemaphoreTake xSemaphoreTakeFromISR

参考链接

百问网 FreeRTOS入门与工程实践-基于STM32F103 第12章信号量(semaphore)