当前位置:首页 > 公众号精选 > 技术让梦想更伟大
[导读]FreeRTOS的信号量包括二进制信号量、计数信号量、互斥信号量(以后简称互斥量)和递归互斥信号量(以后简称递归互斥量)。

ID:技术让梦想更伟大
整理:李肖遥
FreeRTOS的信号量包括二进制信号量、计数信号量、互斥信号量(以后简称互斥量)和递归互斥信号量(以后简称递归互斥量)。
关于它们的区别可以参考《 FreeRTOS系列第19篇---FreeRTOS信号量》一文。
信号量API函数实际上都是宏,它使用现有的队列机制。这些宏定义在semphr.h文件中。如果使用信号量或者互斥量,需要包含semphr.h头文件。
二进制信号量、计数信号量和互斥量信号量的创建API函数是独立的,但是获取和释放API函数都是相同的;
递归互斥信号量的创建、获取和释放API函数都是独立的。

1.信号量创建

在《FreeRTOS高级篇5---FreeRTOS队列分析》中,我们分析了队列的实现过程,包括队列创建、入队和出队操作。
在那篇文章中我们说过,创建队列API函数实际是调用通用队列创建函数xQueueGenericCreate()来实现的。
其实,不但创建队列实际调用通用队列创建函数,二进制信号量、计数信号量、互斥量和递归互斥量也都直接或间接使用这个函数,如表1-1所示。
表1-1中红色字体表示是间接调用xQueueGenericCreate()函数。
表1-1:队列、信号量和互斥量创建宏与直接(间接)执行函数

1.1.创建二进制信号量

二进制信号量创建实际上是直接使用通用队列创建函数xQueueGenericCreate()。创建二进制信号量API接口实际上是一个宏,定义如下:
#define xSemaphoreCreateBinary()         \
xQueueGenericCreate(              \
( UBaseType_t ) 1,       \
semSEMAPHORE_QUEUE_ITEM_LENGTH,  \
NULL,              \
NULL,              \
queueQUEUE_TYPE_BINARY_SEMAPHORE\
)
通过这个宏定义我们知道创建二进制信号量实际上是创建了一个队列,队列项有1个,但是队列项的大小为0(宏semSEMAPHORE_QUEUE_ITEM_LENGTH定义为0)。
有了队列创建的知识,我们可以很容易的画出初始化后的二进制信号量内存,如图1-1所示。
图1-1:初始化后的二进制信号量对象内存或许不止一人像我一样奇怪,创建一个没有队列项存储空间的队列,「信号量用什么表示?」
其实二进制信号量的释放和获取都是通过操作队列结构体成员uxMessageWaiting来实现的(图1-1红色部分,uxMessageWaiting表示队列中当前队列项的个数)。
经过初始化后,变量uxMessageWaiting为0,这说明队列为空,也就是信号量处于无效状态。
在使用API函数xSemaphoreTake()获取信号之前,需要先释放一个信号量。后面讲到二进制信号量释放和获取时还会详细介绍。

1.2.创建计数信号量

创建计数信号量间接使用通用队列创建函数xQueueGenericCreate()。创建计数信号量API接口同样是个宏定义:
#define xSemaphoreCreateCounting(uxMaxCount, uxInitialCount )             \
xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ), (NULL ) )
创建计数信号量API接口有两个参数,含义如下:
  • 「uxMaxCount」:最大计数值,当信号到达这个值后,就不再增长了。
  • 「uxInitialCount」:创建信号量时的初始值。
我们来看一下函数xQueueCreateCountingSemaphore()如何实现的:
QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_tuxMaxCount, const UBaseType_t uxInitialCount, StaticQueue_t *pxStaticQueue )
{
QueueHandle_t xHandle;
configASSERT( uxMaxCount != 0 );
configASSERT( uxInitialCount <= uxMaxCount );
/*调用通用队列创建函数*/
xHandle =xQueueGenericCreate(
uxMaxCount,
queueSEMAPHORE_QUEUE_ITEM_LENGTH,
NULL,
pxStaticQueue,
queueQUEUE_TYPE_COUNTING_SEMAPHORE );
if( xHandle != NULL )
{
( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount;
}
configASSERT( xHandle );
return xHandle;
}
从代码可以看出,创建计数信号量仍然调用通用队列创建函数xQueueGenericCreate()来创建一个队列,队列项的数目由参数uxMaxCount指定,每个队列项的大小由宏queueSEMAPHORE_QUEUE_ITEM_LENGTH指出,我们找到这个宏定义发现,这个宏被定义为0,也就是说创建的队列只有队列数据结构存储空间而没有队列项存储空间。
如果队列创建成功,则将队列结构体成员uxMessageWaiting设置为初始计数信号量值。初始化后的计数信号量内存如图1-2所示。
图1-2:初始化后的计数信号量对象内存

1.3创建互斥量

创建互斥量间接使用通用队列创建函数xQueueGenericCreate()。创建互斥量API接口同样是个宏,定义如下:
#define xSemaphoreCreateMutex()             \
xQueueCreateMutex( queueQUEUE_TYPE_MUTEX, NULL )
其中,宏queueQUEUE_TYPE_MUTEX用于通用队列创建函数,表示创建队列的类型是互斥量,在文章《FreeRTOS高级篇5---FreeRTOS队列分析》关于通用队列创建函数参数说明中提到了这个宏。
我们来看一下函数xQueueCreateMutex()是如何实现的:
#if ( configUSE_MUTEXES == 1 )
QueueHandle_t xQueueCreateMutex( const uint8_tucQueueType, StaticQueue_t *pxStaticQueue )
{
Queue_t *pxNewQueue;
const UBaseType_tuxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0;
/* 防止编译器产生警告信息 */
( void ) ucQueueType;
/*调用通用队列创建函数*/
pxNewQueue = ( Queue_t * )xQueueGenericCreate( uxMutexLength, uxMutexSize, NULL, pxStaticQueue, ucQueueType );
/* 成功分配新的队列结构体? */
if( pxNewQueue != NULL )
{
/*xQueueGenericCreate()函数会按照通用队列的方式设置所有队列结构体成员,但是我们是要创建互斥量.因此需要对一些结构体成员重新赋值. */
pxNewQueue->pxMutexHolder = NULL;
pxNewQueue->uxQueueType =queueQUEUE_IS_MUTEX;  //NULL
/* 用于递归互斥量创建 */
pxNewQueue->u.uxRecursiveCallCount = 0;
/* 使用一个预期状态启动信号量 */
( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK);
}
return pxNewQueue;
}
#endif /* configUSE_MUTEXES */
这个函数是带条件编译的,只有将宏configUSE_MUTEXES定义为1才会编译这个函数。
函数首先调用通用队列创建函数xQueueGenericCreate()来创建一个队列,队列项数目为1,队列项大小为0,说明创建的队列只有队列数据结构存储空间而没有队列项存储空间。
如果队列创建成功,通用队列创建函数还会按照通用队列的方式 初始化所有队列结构体成员。
但是这里要创建的是互斥量,所以有一些结构体成员必须重新赋值。
在这段代码中,可能你会疑惑,队列结构体成员中,并没有pxMutexHolder和uxQueueType!
其实这两个标识符只是宏定义,是专门为互斥量而定义的,如下所示:
#define pxMutexHolder              pcTail
#define uxQueueType                pcHead
#define queueQUEUE_IS_MUTEX        NULL
当队列结构体用于互斥量时,成员pcHead和pcTail指针就不再需要,并且将pcHead指针设置为NULL,表示pcTail指针实际指向互斥量持有者任务TCB(如果有的话)。
最后调用函数xQueueGenericSend()释放一个互斥量,相当于互斥量创建后是有效的,可以直接使用获取信号量API函数来获取这个互斥量。
如果某资源同时只准一个任务访问,可以用互斥量保护这个资源。
这个资源一定是存在的,所以创建互斥量时会先释放一个互斥量,表示这个资源可以使用。
任务想访问资源时,先获取互斥量,等使用完资源后,再释放它。
也就是说互斥量一旦创建好后,要先获取,后释放,要在同一个任务中获取和释放。
这也是互斥量和二进制信号量的一个重要区别,二进制信号量可以在随便一个任务中获取或释放,然后也可以在任意一个任务中释放或获取。
「互斥量不同于二进制信号量的还有」:互斥量具有优先级继承机制,二进制信号量没有,互斥量不可以用于中断服务程序,二进制信号量可以。
初始化后的互斥量内存如图1-3所示。
图1-3:初始化后的互斥量对象内存

1.4创建递归互斥量

创建递归互斥量间接使用通用队列创建函数xQueueGenericCreate()。创建递归互斥量API接口同样是个宏,定义如下:
#definexSemaphoreCreateRecursiveMutex()                 \
xQueueCreateMutex(queueQUEUE_TYPE_RECURSIVE_MUTEX, NULL )
其中,宏queueQUEUE_TYPE_RECURSIVE_MUTEX用于通用队列创建函数,表示创建队列的类型是递归互斥量,在文章《FreeRTOS高级篇5---FreeRTOS队列分析》关于通用队列创建函数参数说明中提到了这个宏。
创建互斥量和创建递归互斥量是调用的同一个函数xQueueCreateMutex();
至于参数queueQUEUE_TYPE_RECURSIVE_MUTEX,我们在FreeRTOS一文中已经知道,它只是用于可视化调试;
因此创建互斥量和创建递归互斥量可以看作是一样的,初始化后的递归互斥量对象内存也和互斥量一样,如图1-3所示。

2.释放信号量

无论二进制信号量、计数信号量还是互斥量,它们都使用相同的获取和释放API函数。释放信号量用于使信号量有效,分为不带中断保护和带中断保护两个版本。

2.1 xSemaphoreGive()

用于释放一个信号量,不带中断保护。被释放的信号量可以是二进制信号量、计数信号量和互斥量。
注意递归互斥量并不能使用这个API函数释放。其实信号量释放是一个宏,真正调用的函数是xQueueGenericSend(),宏定义如下:
#definexSemaphoreGive( xSemaphore )                    \
xQueueGenericSend(                       \
( QueueHandle_t ) ( xSemaphore ), \
NULL,                \
semGIVE_BLOCK_TIME,  \
queueSEND_TO_BACK )
可以看出释放信号量实际上是一次入队操作,并且阻塞时间为0(由宏semGIVE_BLOCK_TIME定义)。
对于二进制信号量和计数信号量,根据上一章的内容可以总结出,释放一个信号量的过程实际上可以简化为两种情况:
「第一」,如果队列未满,队列结构体成员uxMessageWaiting加1,判断是否有阻塞的任务,有的话解除阻塞,然后返回成功信息(pdPASS);
「第二」,如果队列满,返回错误代码(err_QUEUE_FULL),表示队列满。
对于互斥量要复杂些,因为互斥量具有优先级继承机制。
「优先级继承是个什么过程呢?」
我们举个例子。某个资源X同时只能有一个任务访问,现在有任务A和任务C都要访问这个资源,任务A的优先级为1,任务C的优先级为10,所以任务C的优先级大于任务A的优先级。
我们用互斥量保护资源X,并且当前任务A正在访问资源X。
在任务A访问资源X的过程中,来了一个中断,中断事件使得任务C执行。
任务C执行的过程中,也想访问资源X,但是因为资源X还被任务A独占着,所以任务C无法获取互斥量,会进入阻塞状态。
此时,低优先级任务A会继承高优先级任务C的优先级,任务A的优先级临时的被提升,优先级变成10。这个机制能够将已经发生的优先级反转影响降低到最小。
「那么什么是优先级反转呢?」
还是看上面的例子,任务C的优先级高于任务A,但是任务C因为没有获得互斥量而进入阻塞,只能等待低优先级的任务A释放互斥量后才能运行,这种情况就是优先级反转。
「那为什么优先级继承可以降低优先级反转的影响呢?」
还是看上面的例子,不过我们再增加一个优先级为5的任务B,这三个任务都处于就绪状态。
如果没有优先级继承机制,三个任务的优先级顺序为任务C>任务B>任务A。
当任务C因为得不到互斥量而阻塞后,任务B会获取CPU权限,等到任务B主动或被动让出CPU后,任务A才会执行,任务A释放互斥量后,任务C才能得到运行。
再看一下有优先级继承的情况,当任务C因为得不到互斥量而阻塞后,任务A继承任务C的优先级,现在三个任务的优先级顺序为任务C=任务A>任务B。
当任务C因为得不到互斥量而阻塞后,任务A会获得CPU权限,等到任务A释放互斥量后,任务C就会得到运行。看,任务C等待的时间变短了。
有了上面的基础理论,我们就很好理解为什么释放互斥量会比较复杂了。
「还是可以简化为两种情况:」
「第一」,如果队列未满,除了队列结构体成员uxMessageWaiting加1外,还要判断获取互斥量的任务是否有优先级继承,如果有的话,还要将任务的优先级恢复到原始值。当然,恢复到原来值也是有条件的,就是该任务必须在没有使用其它互斥量的情况下,才能将继承的优先级恢复到原始值。然后判断是否有阻塞的任务,有的话解除阻塞,最后返回成功信息(pdPASS);
「第二」,如果如果队列满,返回错误代码(err_QUEUE_FULL),表示队列满。

2.2xSemaphoreGiveFromISR()

用于释放一个信号量,带中断保护。被释放的信号量可以是二进制信号量和计数信号量。
和普通版本的释放信号量API函数不同,它不能释放互斥量,这是因为互斥量不可以在中断中使用!
互斥量的优先级继承机制只能在任务中起作用,在中断中毫无意义。带中断保护的信号量释放其实也是一个宏,真正调用的函数是xQueueGiveFromISR (),宏定义如下:
#definexSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken )     \
xQueueGiveFromISR(                     \
( QueueHandle_t ) ( xSemaphore),  \
( pxHigherPriorityTaskWoken ) )
我们看真正被调用的函数源码(经过整理后的):
BaseType_t xQueueGiveFromISR(
QueueHandle_t xQueue,
BaseType_t * constpxHigherPriorityTaskWoken )
{
BaseType_t xReturn;
UBaseType_t uxSavedInterruptStatus;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
uxSavedInterruptStatus =portSET_INTERRUPT_MASK_FROM_ISR();
{
/*当队列用于实现信号量时,永远不会有数据出入队列,但是任然要检查队列是否为空 */
if( pxQueue->uxMessagesWaiting < pxQueue->uxLength )
{
/* 一个任务可以获取多个互斥量,但是只能有一个继承优先级,如果任务是互斥量的持有者,则互斥量不允许在中断服务程序中释放.因此这里不需要判断是否要恢复任务的原始优先级值,只是简单更新队列项计数器. */
( pxQueue->uxMessagesWaiting );
/* 如果列表上锁,不能改变队列的事件列表. */
if( pxQueue->xTxLock == queueUNLOCKED )
{
if( listLIST_IS_EMPTY(
本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

业内消息,近日高通公司宣布推出针对桌面平台的全新骁龙 X Plus 处理器。

关键字: 高通 骁龙 X Plus 处理器

近日,台积电在圣克拉拉年度技术研讨会上宣布首个“埃级”制程技术:A16。A16 是台积电首次引入背面电源输送网络技术,计划于 2026 年下半年开始量产。同时,台积电也在重新命名工艺节点,标志着「埃级」时代的开始。

关键字: 台积电 A16

4 月 25 日消息,4 月 25 日,国际数据公司(IDC)发布 2024 年第一季度中国手机市场跟踪报告,荣耀以 17.1% 的市场份额拿下第一,华为占 17.0% 位列第二,OPPO、苹果和 vivo 分别位列第三...

关键字: 荣耀 华为

业内消息, 近日华为全新Pura 70系列手机正式开售引发广大 数码爱好者追捧,但是有网友注意到这款手机的“AI修图”功能,竟然可以将照片中的人物衣服消除,并拍成视频发布网络。

关键字: 华为Pura70 华为

据韩媒报道,近日韩国多位军方人士透露,韩国军方正在考虑全面禁止在军事建筑内使用苹果手机,军方担心敏感信息通过录音泄露。

关键字: iPhone 苹果

据韩媒《朝鲜日报》消息,三星集团已确认已决定将适用于三星电子等部分关联公司的“高管每周工作 6 天”扩大到整个集团。三星子公司的人力资源团队直接通过口头、群聊和电子邮件向高管传达了这一新政,而非正式信函的形式。

关键字: 三星

4月23日,深圳传音控股股份有限公司发表了2023年年度报告。数据显示,2023年,该公司手机整体出货量约1.94亿部。

关键字: 传音 智能手机

最新消息,美国参议院以 79 票赞成、18 票反对的压倒性多数,通过了一项可能导致 TikTok 在美国被禁的法案,该法案要求字节跳动公司出售 TikTok,否则将面临禁令。TikTok 最多有 12 个月的时间从母公司...

关键字: 美国 TikTok 字节跳动

业内消息,近日数码博主@手机晶片达人在社交媒体发文表示,苹果公司正在研发自家的 AI 服务器芯片,采用台积电的 3nm 工艺,预估将于 2025 年下半年量产。台积电是苹果最重要的合作伙伴,目前苹果的大部分 3nm 产能...

关键字: 苹果 AI服务器芯片 台积电 3nm

业内消息,近日苹果公司公布了2023财年供应链名单。其中,中国大陆地区新进8家企业,有4家企业被剔除;中国台湾地区供应商新进2家企业,同样有4家企业被剔除。

关键字: 苹果 供应链
关闭
关闭