FreeRTOS学习(五)队列与信号量

1.队列

  FreeRTOS支持多任务操作,那么任务之间以及任务与中断之间肯定需要通讯与同步,因此,继任务相关内容学习之后,下一个重要的概念就是队列。

1.1 队列特性

  队列能够存储一定数目、大小固定的数据项目。因此创建队列时,需要指明数据的长度length和数据元素的大小 size。这里将队列类比成火车,长度length对应火车的节数,元素大小size对应每节车厢容纳人数。
  队列特性主要有:

  • FIFO,向队列中发送数据叫入队,从队列中读取数据叫出队
  • 支持值传递和引用传递,值传递时是将原数据拷贝到队列中,原数据可以删除或覆写
  • 多任务访问,不属于某个特定任务,属于公共资源
  • 入队阻塞/出队阻塞:三种阻塞机制
1.2 队列创建
1.2.1 接口函数

  队列也有动态创建xQueueCreate()和静态创建xQueueCreateStatic()两种方式。

API功能
xQueueCreate()动态创建,内存由编辑器负责分配
xQueueCreateStatic()静态创建,需要程序员提供结构体和实际储存区的地址

  以动态创建为例,其归根结底还是调用了一个通用 API xQueueGenericCreate(),这个API不仅可以创建队列,也是创建信号量的底层函数。

QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, //队列长度
                                       const UBaseType_t uxItemSize,//队列中每个消息的长度,单位为字节
                                       const uint8_t ucQueueType )	//队列类型

  重点在于ucQueueType队列类型这个参数,它一共有6种类型:

queueQUEUE_TYPE_BASE:表示队列
queueQUEUE_TYPE_SET:表示队列集合 
queueQUEUE_TYPE_MUTEX:表示互斥量
queueQUEUE_TYPE_COUNTING_SEMAPHORE:表示计数信号量
queueQUEUE_TYPE_BINARY_SEMAPHORE:表示二进制信号量
queueQUEUE_TYPE_RECURSIVE_MUTEX :表示递归互斥量

  实际上,信号量就是在队列的基础上延伸出来的,是对队列的约束。关于信号量,后面内容会讲。

1.2.2 内存占用

  与任务创建类似,队列的创建也需要两块内存,分别是队列结构体队列项存储区,这两块内存是连续的。
在这里插入图片描述

  • 队列结构体,是对队列的描述,类似于任务的TCB
  • 列表项存储区,消息真正存放的区域
    在这里插入图片描述

  队列结构体成员参数解释如下:

typedef struct QueueDefinition
{
    int8_t *pcHead;             /* 指向队列存储区起始位置,即第一个队列项 */
    int8_t *pcTail;             /* 指向队列存储区结束后的下一个字节 */
    int8_t *pcWriteTo;          /* 指向下队列存储区的下一个空闲位置 */
 
 
    union                       /* 使用联合体用来确保两个互斥的结构体成员不会同时出现 */
    {
        int8_t *pcReadFrom;     /* 当结构体用于队列时,这个字段指向出队项目中的最后一个. */
        UBaseType_t uxRecursiveCallCount;/* 当结构体用于互斥量时,用作计数器,保存递归互斥量被"获取"的次数. */
    } u;
 
 
    List_t xTasksWaitingToSend;      /* 因为等待入队而阻塞的任务列表,按照优先级顺序存储 */
    List_t xTasksWaitingToReceive;   /* 因为等待队列项而阻塞的任务列表,按照优先级顺序存储 */
 
 
    volatile UBaseType_t uxMessagesWaiting;/*< 当前队列的队列项数目 */
    UBaseType_t uxLength;            /* 队列项的数目 */
    UBaseType_t uxItemSize;          /* 每个队列项的大小 */
 
 
    volatile BaseType_t xRxLock;   /* 队列上锁后,存储从队列收到的列表项数目,如果队列没有上锁,设置为queueUNLOCKED */
    volatile BaseType_t xTxLock;   /* 队列上锁后,存储发送到队列的列表项数目,如果队列没有上锁,设置为queueUNLOCKED */
 
 
    #if ( configUSE_QUEUE_SETS == 1 )
        struct QueueDefinition *pxQueueSetContainer;
    #endif
 
 
    #if ( configUSE_TRACE_FACILITY == 1 )
        UBaseType_t uxQueueNumber;
        uint8_t ucQueueType;
    #endif
 
 
    #if ( configSUPPORT_STATIC_ALLOCATION == 1 )
        uint8_t ucStaticAllocationFlags;
    #endif
 
 
} xQUEUE;
 
 
typedef xQUEUE Queue_t;
1.2.3 创建过程分析
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength,
                                       const UBaseType_t uxItemSize,
                                       const uint8_t ucQueueType )
    {
        Queue_t * pxNewQueue;
        size_t xQueueSizeInBytes;
        uint8_t * pucQueueStorage;

        configASSERT( uxQueueLength > ( UBaseType_t ) 0 );

        // 计算队列存储区大小
        xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); 

        /* Check for multiplication overflow. */
        configASSERT( ( uxItemSize == 0 ) || ( uxQueueLength == ( xQueueSizeInBytes / uxItemSize ) ) );

        /* Check for addition overflow. */
        configASSERT( ( sizeof( Queue_t ) + xQueueSizeInBytes ) >  xQueueSizeInBytes );

        // 为队列结构体和队列项存储区申请内存
        pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes ); /*lint !e9087 !e9079 see comment above. */
		// 内存申请成功
        if( pxNewQueue != NULL )
        {
            // 计算队列项存储区的首地址
            pucQueueStorage = ( uint8_t * ) pxNewQueue;
            pucQueueStorage += sizeof( Queue_t ); 

            #if ( configSUPPORT_STATIC_ALLOCATION == 1 )
                {
                    // 动态申请内存的话,那么这个成员参数设置为 pdFALSE
                    pxNewQueue->ucStaticallyAllocated = pdFALSE;
                }
            #endif /* configSUPPORT_STATIC_ALLOCATION */
			// 初始化队列结构体成员参数
            prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );
        }
        else
        {
            traceQUEUE_CREATE_FAILED( ucQueueType );
            mtCOVERAGE_TEST_MARKER();
        }

        return pxNewQueue;
    }

  简要的流程图如下:主要就是分配内存,然后对队列结构体进行初始化。
在这里插入图片描述

1.3 入队与出队
1.3.1 队列项入队

  入队函数分为任务级与中断级,但这些 API 都是宏定义。
在这里插入图片描述
  任务级入队函数都是调用的同一个函数xQueueGenericSend,其参数如下

BaseType_t xQueueGenericSend( QueueHandle_t xQueue,				//队列句柄
                              const void * const pvItemToQueue, //指向要发送的消息
                              TickType_t xTicksToWait,			//阻塞时间
                              const BaseType_t xCopyPosition )	//入队方式

//入队方式主要有以下三种,分别对应后向入队、前向入队和覆写入队
#define queueSEND_TO_BACK                     ( ( BaseType_t ) 0 )
#define queueSEND_TO_FRONT                    ( ( BaseType_t ) 1 )
#define queueOVERWRITE                        ( ( BaseType_t ) 2 )

  中断级入队函数都是调用的同一个函数xQueueGenericSendFromISR,其参数如下

BaseType_t xQueueGenericSendFromISR( QueueHandle_t xQueue,
                                     const void * const pvItemToQueue,
                                     BaseType_t * const pxHigherPriorityTaskWoken, //标记退出此函数后是否进行任务切换
                                     const BaseType_t xCopyPosition ) //入队方式,同上

  关于队列项入队的详细过程先挖个坑,后续再填。

1.3.1 队列项出队

在这里插入图片描述

2.信号量

  信号量一般用来进行资源管理任务间、任务与中断间的同步,FreeRTOS 中的信号量分为二值信号量、计数型信号量、互斥信号量和递归互斥信号量。

  • 信号量创建调用的底层API跟队列创建是同一个,只是传入参数不同
  • 这四种信号量的句柄类型是相同的SemaphoreHandle_t,本质上是队列句柄QueueHandle_t
typedef QueueHandle_t SemaphoreHandle_t;
  • 这四种信号量的创建函数不同,但释放、获取与删除时相同的
2.1 二值信号量

(1)主要特点

  • 实质上是一个队列长度为 1,队列项长度为 0 的队列
  • 没有队列项存储区,通过队列结构体的成员 uxMessageWaiting来判断队列是否为空

(2)创建函数

API功能
xSemaphoreCreateBinary()动态创建二值信号量
xSemaphoreCreateBinaryStatic()静态创建二值信号量

  创建成功后会返回信号量的句柄,创建函数其实是一个宏定义,如下

#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
    #define xSemaphoreCreateBinary()    xQueueGenericCreate( ( UBaseType_t ) 1, 
    									semSEMAPHORE_QUEUE_ITEM_LENGTH,  // 宏定义 0
   										queueQUEUE_TYPE_BINARY_SEMAPHORE )
#endif

//example
SemaphoreHandle_t 		BinSemaphore;
BinSemaphore = xSemaphoreCreateBinary();
  • 二值信号量创建后是无效的,必须先调用API释放,才能使用。

(3)释放、获取与删除

API功能
xSemaphoreGive()任务级释放二值信号量
xSemaphoreGiveFromISR()中断级释放信号量
xSemaphoreTake()任务级获取信号量
xSemaphoreTakeFromISR()中断级获取信号量
vSemaphoreDelete()删除信号量

  二值信号量的释放就是向队列中发送消息,但消息为NULL,阻塞时间为0,后向入队。关键在于入队的时候,队列结构体成员 uxMessageWaiting 会加一,从而判断队列时满1还是空0

#define xSemaphoreGive( xSemaphore )    xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), 
															NULL, 
															semGIVE_BLOCK_TIME, // 阻塞时间宏,为 0
															queueSEND_TO_BACK ) // 后向入队

  获取二值信号量可以设置阻塞时间,读取成功后 uxMessageWaiting 会减一。

#define xSemaphoreTake( xSemaphore, xBlockTime )    xQueueSemaphoreTake( ( xSemaphore ), ( xBlockTime ) )
2.2 计数型信号量

(1)主要特点

  • 队列长度大于1,队列项长度为0
  • 同样通过队列结构体成员 uxMessageWaiting来判断数量

(2)创建函数

API功能
xSemaphoreCreateCounting()动态创建计数型信号量
xSemaphoreCreateCountingStatic()静态创建计数型信号量
#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount )    xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) )
  • 计数型信号量的释放、获取与删除操作与二值信号量相同
2.3 互斥信号量

(1)主要特点

  • 互斥量具有优先级继承机制,只能用在任务中,不能用于中断服务函数
  • 中断服务函数不能因为要等待互斥量而设置阻塞时间进入阻塞态
  • 队列长度为1,队列项长度为0
  • 任务在使用完互斥量后必须释放,否则其他资源无法使用,即互斥量是一个不能被销毁的特殊信号量

(2)创建函数

API功能
xSemaphoreCreateMutex()动态创建互斥信号量
xSemaphoreCreateMutexStatic()静态创建互斥信号量

  互斥信号量获取和释放的过程与二值信号量有些区别,主要涉及到优先级继承。
(3)优先级翻转
  优先级翻转主要发生在当两个任务H和L共用一个信号量时,

  • 当信号量为二值型时,L优先获得信号量后,会将H的优先级拉低到和自身相同,使得M任务抢先于H执行,出现优先级翻转
  • 当信号量为互斥型时,L优先获得信号量后,H会将L的优先级拉高到和自身相同,尽量避免优先级翻转
    在这里插入图片描述

3.总结

通讯方式长度约束作用
队列-值传递>0阻塞读写用于任务之间传递数据
队列-引用传递>0阻塞读写用于大块数据的高效传输
二值信号量1一方只需要申请,另一方只需要释放用于锁存1个中断代表资源的到达 / 用于任务的异步通知
计数型信号量>1一方只需要申请,另一方只需要释放用于锁存若干个中断以免中断丢失 / 用于同步代表资源的数量
互斥信号量1申请的一方必须自己进行释放用于互斥访问机制
  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

la_fe_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值