文章目录
- 1.队列
- 1.1 队列特性
- 1.2 队列创建
- 1.2.1 接口函数
- 1.2.2 内存占用
- 1.2.3 创建过程分析
- 1.3 入队与出队
-
- 2.信号量
- 2.1 二值信号量
- 2.2 计数型信号量
- 2.3 互斥信号量
- 3.总结
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;
volatile BaseType_t xTxLock;
#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 );
configASSERT( ( uxItemSize == 0 ) || ( uxQueueLength == ( xQueueSizeInBytes / uxItemSize ) ) );
configASSERT( ( sizeof( Queue_t ) + xQueueSizeInBytes ) > xQueueSizeInBytes );
pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );
if( pxNewQueue != NULL )
{
pucQueueStorage = ( uint8_t * ) pxNewQueue;
pucQueueStorage += sizeof( Queue_t );
#if ( configSUPPORT_STATIC_ALLOCATION == 1 )
{
pxNewQueue->ucStaticallyAllocated = pdFALSE;
}
#endif
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,
queueQUEUE_TYPE_BINARY_SEMAPHORE )
#endif
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,
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 | 申请的一方必须自己进行释放 | 用于互斥访问机制 |
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)