在学习FreeRTOS过程中,结合韦东山-FreeRTOS手册和视频、野火-FreeRTOS内核实现与应用开发、及网上查找的其他资源,整理了该篇文章。如有内容理解不正确之处,欢迎大家指出,共同进步。
队列用于传输数据:在任务之间、任务和中断之间。
消息队列用于传输多个数据,但是有时候我们只需要传递状态,这个状态值需要用一个数值表示。
信号量就是特殊的队列。队列里使用环形缓冲区存放数据,信号量里只记录计数值。
信号量是一种实现任务间通信的机制,可以实现任务之间同步或临界资源的互斥访问,常用于协助一组相互竞争的任务来访问临界资源。
1. 信号量的特性
1.1 信号量的常规操作:
信号量这个名字很恰当:
- 信号:起通知作用
- 量:还可以用来表示资源的数量
- 当"量"没有限制时,它就是"计数型信号量"(Counting Semaphores)
- 当"量"只有0、1两个取值时,它就是"二进制信号量"(Binary Semaphores)
- 支持的动作:"give"给出资源,计数值加1;"take"获得资源,计数值减1
计数型信号量的典型场景是:
- 计数:事件产生时"give"信号量,让计数值加1;处理事件时要先"take"信号量,就是获得信号量,让计数值减1。
- 资源管理:要想访问资源需要先"take"信号量,让计数值减1;用完资源后"give"信号量,让计数值加1。 信号量的"give"、"take"双方并不需要相同,可以用于生产者-消费者场合:
- 生产者为任务A、B,消费者为任务C、D
- 一开始信号量的计数值为0,如果任务C、D想获得信号量,会有两种结果:
- 阻塞:买不到东西咱就等等吧,可以定个闹钟(超时时间)
- 即刻返回失败:不等
- 任务A、B可以生产资源,就是让信号量的计数值增加1,并且把等待这个资源的顾客唤醒
- 唤醒谁?谁优先级高就唤醒谁,如果大家优先级一样就唤醒等待时间最长的人
- 二进制信号量跟计数型的唯一差别,就是计数值的最大值被限定为1。
1.2 信号量和队列的对比
1.3 信号量
1.3.1 二值信号量
二值信号量既可以用于临界资源访问也可以用于同步功能。
二值信号量和互斥信号量(以下使用互斥量表示互斥信号量)非常相似,但是有一些细 微差别:
- 互斥量有优先级继承机制,二值信号量则没有这个机制。
- 这使得二值信号量更偏 向应用于同步功能(任务与任务间的同步或任务和中断间同步),而互斥量更偏向应用于 临界资源的访问。
二值信号量可以被认为是长度为 1 的队列,因此这个队列只能为空或满(因此称为二 值),我们在运用的时候只需要知道队列中是否有消息即可,而无需关注消息是什么。
1.3.2 如何使用二进制信号量来同步任务之间的操作?
用作同步时,信号量在创建后应被置为0,任务 1 获取信号量而进入阻塞,任务 2 在某种条件发生后,释放信号量,于是任务 1 获得信号量得以进入就绪态,如果任务 1 的优先级是最高的,那么就会立即切换任务,从而达到了两个任务间的同步。同样的,在中断服务函数中释放信号量,任务 1 也会得到信号量,从而达到任务与中断间的同步。
1.3.3 计数型信号量
二值信号量可以被认为是长度为 1 的队列,而计数信号量则可以被认为长度大于 1 的队列,信号量使用者依然不必关心存储在队列中的消息,只需关心队列是否有消息即可。
计数型信号量的典型场景是:
- 计数:
- 事件产生时释放"give"一个信号量,让计数值加1;处理事件时要先获取"take"一个信号量,就是获得信号量,让计数值减1。
- 信号量的计数值表示还有多少个事件没被处理。
- 资源管理:
- 要想访问资源需要先"take"信号量,让计数值减1;用完资源后"give"信号量,让计数值加1。 信号量的"give"、"take"双方并不需要相同,可以用于生产者-消费者场合。
- 信号量的计数值表示系统中可用的资源数目。
- 任务必须先获取到信号量才能获取资源访问权,当信号量的计数值为零时表示系统 没有可用的资源,但是要注意,在使用完资源的时候必须归还信号量,否则当计数值为 0 的时候任务就无法访问该资源了。
1.3.4 互斥信号量
互斥信号量其实是特殊的二值信号量,由于其特有的优先级继承机制从而使它更适用 于简单互锁,也就是保护临界资源 。
用作互斥时,信号量创建后可用信号量个数应该是满的,任务在需要使用临界资源时, (临界资源是指任何时刻只能被一个任务访问的资源),先获取互斥信号量,使其变空, 这样其他任务需要使用临界资源时就会因为无法获取信号量而进入阻塞,从而保证了临界 资源的安全。
1.3.5 递归信号量
对于已经获取递归互斥量的 任务可以重复获取该递归互斥量,该任务拥有递归信号量的所有权。任务成功获取几次递 归互斥量,就要返还几次,在此之前递归互斥量都处于无效状态,其他任务无法获取,只 有持有递归信号量的任务才能获取与释放。
1.4 计数型和二进制信号量
1.4.1 计数型和二进制信号量的区别
信号量的计数值都有限制:限定了最大值。如果最大值被限定为1,那么它就是二进制信号量;如果最大值不是1,它就是计数型信号量。
1.4.2 二值信号量运作机制
1.4.3 计数信号量运作机制
1.5 释放信号量和获取信号量流程
释放信号量:give(要么成功,要么失败,不可能休眠)
- 关中断;
- count++;
xTasksWaitingToReceive
中是否有任务在等待信号量,如果有,将其唤醒- 把
xTasksWaitingToReceive;
中的第 1 个任务移除 - 将其从 DelayList——> ReadList 中
- 把
获取信号量:Take
- 关中断;
- 判断
count > 0 ?
- 否:
- 返回 ERR(不等待)
- 休眠
- 放入
xTasksWaitingToReceive
, - ReadList ——> DelayList中
- 放入
- 是
- 否:
- 是(count > 0):本来count > 0;休眠被唤醒(有任务释放了)
- count --;
- 有任务在等待释放信号量,唤醒它:
- 从
xTasksWaitingToSend
移除 - DelayList ——>ReadList
- 从
- return ok;
2. 信号量函数
2.1 信号量控制块
信号量控制块结构体与消息队列结构体是一模一样的,只不过结构体中某些成员变量代表的含义不 一样而已
- uxMessagesWaiting:
- 用于消息队列: 用来记录当前消息队列的消息个数;
- 用于信号量:这个值就表示有效信号量个数,
- 二值信号量、互斥信号量:这个值是 1 则表示有可用信号量,如果 是 0 则表示没有可用信号量。
- 计数信号量:这个值表示可用的信号量个数,在创建计数信号量的时候会被初始化一个可用信号量个数 uxInitialCount,最大不允许超过创建信号量的初始 值 uxMaxCount。
- uxLength :
- 用于消息队列: 表示队列的长度, 也就是能存放多少消息 ;
- 用于信号量: 表示最大的信号量可用个数
- 二值信号量、互斥信号量: uxLength 最大为 1,因为信号量要么是 有效的,要么是无效的。
- 计数信号量: 这个值表示最大的信号量个数,在创建计数信号量的时候将 由用户指定这个值 uxMaxCount。
- uxItemSize :
- 用于消息队列:表示单个消息的大小;
- 用于信号量:无需存储空间,为 0 即可 ;
2.2 创建二值信号量 xSemaphoreCreateBinary()
// 二进制型信号量 :初始值为0
g_xSemTicks = xSemaphoreCreateBinary();
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateBinary() \
xQueueGenericCreate( ( UBaseType_t ) 1, \
semSEMAPHORE_QUEUE_ITEM_LENGTH, \
queueQUEUE_TYPE_BINARY_SEMAPHORE )
#endif
创建函数xQueueGenericCreate也就是消息队列创建使用的函数,但参数不一样。
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength,
const UBaseType_t uxItemSize,
const uint8_t ucQueueType )
- 创建一个没有消息存储空间的队列,信号量用什么表示?
- 其实二值信号量的释放和获取都是通过操作队列结控制块构体成员
uxMessageWaiting
来实现的, 它表示信号量中当前可用的信号量个数。在信号量创建之后,变量uxMessageWaiting
的值 为 0,这说明当前信号量处于无效状态,此时的信号量是无法被获取的,在获取信号之前, 应先释放一个信号量。
2.3 创建计数信号量 xSemaphoreCreateCounting()
// 计数型信号量 :最大值为3,初始值为2
g_xSemTicks = xSemaphoreCreateCounting(3, 2);
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount )\
xQueueCreateCountingSemaphore( ( uxMaxCount ), \
( uxInitialCount ) )
#endif
- uxMaxCount: 计数信号量的最大值,当达到这个值的时候,信号量不能再被释放。
- uxInitialCount: 创建计数信号量的初始值。
#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 )