今天学习信号量mutex和semaphore的区别,找到了正点原子的博客介绍,讲的挺详细的。建议大家阅读
转载自:https://blog.csdn.net/nippon1218/article/details/79039197
1 二值信号量
1.1 二值信号量简介
二值信号量通常用于互斥访问或同步,二值信号量和互斥信号量非常类似,但是还是有一些细微的差别,互斥信号量拥有优先级继承机制,二值信号量没有优先级继承。因此二值信号另更适合用于同步(任务与任务或任务与中断的同步),而互斥信号量适合用于简单的互斥访问。
和队列一样,信号量 API 函数允许设置一个阻塞时间,阻塞时间是当任务获取信号量的时候由于信号量无效从而导致任务进入阻塞态的最大时钟节拍数。如果多个任务同时阻塞在同一个信号量上的话那么优先级最高的哪个任务优先获得信号量,这样当信号量有效的时候高优先级的任务就会解除阻塞状态。
二值信号量其实就是一个只有一个队列项的队列,这个特殊的队列要么是满的,要么是空的,这不正好就是二值的吗? 任务和中断使用这个特殊队列不用在乎队列中存的是什么消息,只需要知道这个队列是满的还是空的。可以利用这个机制来完成任务与中断之间的同步。
使用二值信号量来完成中断与任务同步的这个机制中,任务优先级确保了外设能够得到及时的处理,这样做相当于推迟了中断处理过程。也可以使用队列来替代二值信号量,在外设事件的中断服务函数中获取相关数据,并将相关的数据通过队列发送给任务。如果队列无效的话任务就进入阻塞态,直至队列中有数据,任务接收到数据以后就开始相关的处理过程。下面几个步骤演示了二值信号量的工作过程。
1 、二值信号量无效
在图 14.2.1.1 中任务 Task 通过函数 xSemaphoreTake()获取信号量,但是此时二值信号量无
效,所以任务 Task 进入阻塞态。
2 、中断 释放 信号量
此时中断发生了,在中断服务函数中通过函数 xSemaphoreGiveFromISR()释放信号量,因此信号量变为有效。
3、任务获取信号量成功
由于信号量已经有效了,所以任务 Task 获取信号量成功,任务从阻塞态解除,开始执行相关的处理过程
4、任务再次进入阻塞态
由于任务函数一般都是一个大循环,所以在任务做完相关的处理以后就会再次调用函数xSemaphoreTake()获取信号量。在执行完第三步以后二值信号量就已经变为无效的了,所以任务将再次进入阻塞态,和第一步一样,直至中断再次发生并且调用函数 xSemaphoreGiveFromISR()释放信号量。
1.2 创建二值信号量
同队列一样,要想使用二值信号量就必须先创建二值信号量,二值信号量创建函数 如表 14.2.2所示:
1 、函数 vSemaphoreCreateBinary ()
此函数是老版本 FreeRTOS 中的创建二值信号量函数,新版本已经不再使用了,新版本的FreeRTOS 使用 xSemaphoreCreateBinary()来替代此函数,这里还保留这个函数是为了兼容那些基于老版本 FreeRTOS 而做的应用层代码。此函数是个宏,具体创建过程是由函数xQueueGenericCreate()来完成的,在文件 semphr.h 中有如下定义:
void vSemaphoreCreateBinary( SemaphoreHandle_t xSemaphore )
参数:
xSemaphore:保存创建成功的二值信号量句柄。
返回值:
NULL: 二值信号量创建失败。
其他值: 二值信号量创建成功。
2 、函数 xSemaphoreCreateBinary()
此函数是 vSemaphoreCreateBinary()的新版本,新版本的 FreeRTOS 中统一用此函数来创建二值信号量。使用此函数创建二值信号量的话信号量所需要的 RAM 是由 FreeRTOS 的内存管理部分来动态分配的。此函数创建好的二值信号量默认是空的,也就是说刚创建好的二值信号量使用函数 xSemaphoreTake()是获取不到的,此函数也是个宏,具体创建过程是由函数xQueueGenericCreate()来完成的,函数原型如下:
SemaphoreHandle_t xSemaphoreCreateBinary( void )
参数:
无。
返回值:
NULL: 二值信号量创建失败。
其他值: 创建成功的二值信号量的句柄。
3 、函数 xSemaphoreCreateBinaryStatic()
此函数也是创建二值信号量的,只不过使用此函数创建二值信号量的话信号量所需要的RAM 需要由用户来分配,此函数是个宏,具体创建过程是通过函数 xQueueGenericCreateStatic()来完成的,函数原型如下:
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer )
参数:
pxSemaphoreBuffer: :此参数指向一个 StaticSemaphore_t 类型的变量,用来保存信号量结构体。
返回值:
NULL: 二值信号量创建失败。
其他值: 创建成功的二值信号量句柄。
1.3 二值信号量创建过程分析
上一小节讲了三个用于二值信号量创建的函数,两个动态的创建函数和一个静态的创建函数。本节就来分析一下这两个动态的创建函数,静态创建函数和动态的类似,就不做分析了。首先来看一下老版本的二值信号量动态创建函数 vSemaphoreCreateBinary(),函数代码如下:
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define vSemaphoreCreateBinary( xSemaphore ) \
{ \
( xSemaphore ) = xQueueGenericCreate( ( UBaseType_t ) 1, \ (1)
semSEMAPHORE_QUEUE_ITEM_LENGTH, \
queueQUEUE_TYPE_BINARY_SEMAPHORE ); \
if( ( xSemaphore ) != NULL ) \
{ \
( void ) xSemaphoreGive( ( xSemaphore ) ); \ (2)
} \
}
#endif
(1)、上面说了二值信号量是在队列的基础上实现的,所以创建二值信号量就是创建队列的过程。这里使用函数 xQueueGenericCreate()创建了一个队列,队列长度为 1,队列项长度为 0,队列类型为 queueQUEUE_TYPE_BINARY_SEMAPHORE,也就是二值信号量。
(2)、当二值信号量创建成功以后立即调用函数 xSemaphoreGive()释放二值信号量,此时新
创建的二值信号量有效。在来看一下新版本的二值信号量创建函数 xSemaphoreCreateBinary(),函数代码如下:
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateBinary() \
xQueueGenericCreate( ( UBaseType_t ) 1, \
semSEMAPHORE_QUEUE_ITEM_LENGTH, \
queueQUEUE_TYPE_BINARY_SEMAPHORE ) \
#endif
可以看出新版本的二值信号量创建函数也是使用函数 xQueueGenericCreate()来创建一个类型为queueQUEUE_TYPE_BINARY_SEMAPHORE、长度为 1、队列项长度为 0 的队列。这一步和老版本的二值信号量创建函数一样,唯一不同的就是新版本的函数在成功创建二值信号量以后不会立即调用函数 xSemaphoreGive()释放二值信号量。也就是说新版函数创建的二值信号量默认是无效的,而老版本是有效的。
大家注意看,创建的队列是个没有存储区的队列,前面说了使用队列是否为空来表示二值信号量,而队列是否为空可以通过队列结构体的成员变量 uxMessagesWaiting 来判断。
1.4 释放信号量
释放信号量的函数有两个,如表 14.2.4.1 所示:
同队列一样,释放信号量也分为任务级和中断级。还有!不管是二值信号量、计数型信号量还是互斥信号量,它们都使用表 14.2.4.1 中的函数释放信号量,递归互斥信号量有专用的释放函数。
1、数 函数 xSemaphoreGive()
此函数用于释放二值信号量、计数型信号量或互斥信号量,此函数是一个宏,真正释放信号量的过程是由函数 xQueueGenericSend()来完成的,函数原型如下:
BaseType_t xSemaphoreGive( xSemaphore )
参数:
xSemaphore :要释放的信号量句柄。
返回值:
pdPASS: 释放信号量成功。
errQUEUE_FULL: 释放信号量失败。
我们再来看一下函数 xSemaphoreGive()的具体内容,此函数在文件 semphr.h 中有如下定义:
#define xSemaphoreGive( xSemaphore ) \
xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), \
NULL, \
semGIVE_BLOCK_TIME, \
queueSEND_TO_BACK ) \
可以看出任务级释放信号量就是向队列发送消息的过程,只是这里并没有发送具体的消息,阻塞时间为 0(宏 semGIVE_BLOCK_TIME 为 0),入队方式采用的后向入队。具体入队过程第十三章已经做了详细的讲解,入队的时候队列结构体成员变量 uxMessagesWaiting 会加一,对于二值信号量通过判断 uxMessagesWaiting 就可以知道信号量是否有效了,当 uxMessagesWaiting 为1 的话说明二值信号量有效,为 0 就无效。如果队列满的话就返回错误值 errQUEUE_FULL,提示队列满,入队失败。
2 、函数 xSemaphoreGiveFromISR()
此函数用于在中断中释放信号量,此函数只能用来释放二值信号量和计数型信号量,绝对不能用来在中断服务函数中释放互斥信号量!此函数是一个宏,真正执行的是函数
xQueueGiveFromISR(),此函数原型如下:
BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore,
BaseType_t * pxHigherPriorityTaskWoken)
参数:
xSemaphore: 要释放的信号量句柄。
pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值由这
三个函数来设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。
返回值:
pdPASS: 释放信号量成功。
errQUEUE_FULL: 释放信号量失败。
在中断中释放信号量真正使用的是函数 xQueueGiveFromISR(),此函数和中断级通用入队函数 xQueueGenericSendFromISR()极其类似!只是针对信号量做了微小的改动。函数xSemaphoreGiveFromISR()不能用于在中断中释放互斥信号量,因为互斥信号量涉及到优先级继承的问题,而中断不属于任务,没法处理中断优先级继承。大家可以参考第十三章分析函数
xQueueGenericSendFromISR()的过程来分析 xQueueGiveFromISR()。
1.5 获取信号量
获取信号量也有两个函数,如表 14.2.5.1 所示:
同释放信号量的 API 函数一样,不管是二值信号量、计数型信号量还是互斥信号量,它们都使用表 14.2.5.1 中的函数获取信号量。
1 、函数 xSemaphoreTake()
此函数用于获取二值信号量、计数型信号量或互斥信号量,此函数是一个宏,真正获取信号量的过程是由函数 xQueueGenericReceive ()来完成的,函数原型如下:
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,
TickType_t xBlockTime)
参数:
xSemaphore :要获取的信号量句柄。
xBlockTime: 阻塞时间。
返回值:
pdTRUE: 获取信号量成功。
pdFALSE: 超时,获取信号量失败。
再来看一下函数 xSemaphoreTake ()的具体内容,此函数在文件 semphr.h 中有如下定义:
#define xSemaphoreTake( xSemaphore, xBlockTime ) \
xQueueGenericReceive( ( QueueHandle_t ) ( xSemaphore ), \
NULL, \
( xBlockTime ), \
pdFALSE ) \
获取信号量的过程其实就是读取队列的过程,只是这里并不是为了读取队列中的消息。在第十三章讲解函数 xQueueGenericReceive()的时候说过如果队列为空并且阻塞时间为 0 的话就立即返回 errQUEUE_EMPTY,表示队列满。如果队列为空并且阻塞时间不为 0 的话就将任务添加到延时列表中。如果队列不为空的话就从队列中读取数据(获取信号量不执行这一步),数据读取完成以后还需要将队列结构体成员变量 uxMessagesWaiting 减一,然后解除某些因为入队而阻塞的任务,最后返回 pdPASS 表示出对成功。互斥信号量涉及到优先级继承,处理方式不同,后面讲解互斥信号量的时候在详细的讲解。
2 、函数 xSemaphoreTakeFromISR ()
此函数用于在中断服务函数中获取信号量,此函数用于获取二值信号量和计数型信号量,绝 对 不 能 使 用 此 函 数 来 获 取 互 斥 信 号 量 ! 此 函 数 是 一 个 宏 , 真 正 执 行 的 是 函 数
xQueueReceiveFromISR (),此函数原型如下:
BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore,
BaseType_t * pxHigherPriorityTaskWoken)
参数:
xSemaphore: 要获取的信号量句柄。
pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值由这
三个函数来设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退
出中断服务函数之前一定要进行一次任务切换。
返回值:
pdPASS: 获取信号量成功。
pdFALSE: 获取信号量失败。
在中断中获取信号量真正使用的是函数 xQueueReceiveFromISR (),这个函数就是中断级出队函数!当队列不为空的时候就拷贝队列中的数据(用于信号量的时候不需要这一步),然后将队列结构体中的成员变量 uxMessagesWaiting 减一,如果有任务因为入队而阻塞的话就解除阻塞态,当解除阻塞的任务拥有更高优先级的话就将参数pxHigherPriorityTaskWoken 设置为pdTRUE,最后返回 pdPASS 表示出队成功。如果队列为空的话就直接返回 pdFAIL 表示出队失败!这个函数还是很简单的。
2 计数型信号量
2.1 计数型信号量简介
有些资料中也将计数型信号量叫做数值信号量,二值信号量相当于长度为 1 的队列,那么计数型信号量就是长度大于 1 的队列。同二值信号量一样,用户不需要关心队列中存储了什么数据,只需要关心队列是否为空即可。计数型信号量通常 用于如下两个场合:
1 、事件计数
在这个场合中,每次事件发生的时候就在事件处理函数中释放信号量(增加信号量的计数值),其他任务会获取信号量(信号量计数值减一,信号量值就是队列结构体成员变量uxMessagesWaiting)来处理事件。在这种场合中创建的计数型信号量初始计数值为 0。
2 、资源管理
在这个场合中,信号量值代表当前资源的可用数量,比如停车场当前剩余的停车位数量。一个任务要想获得资源的使用权,首先必须获取信号量,信号量获取成功以后信号量值就会减一。当信号量值为 0 的时候说明没有资源了。当一个任务使用完资源以后一定要释放信号量,释放信号量以后信号量值会加一。在这个场合中创建的计数型信号量初始值应该是资源的数量,比如停车场一共有 100 个停车位,那么创建信号量的时候信号量值就应该初始化为 100。
2.2 创建计数型信号量
FreeRTOS 提供了两个计数型信号量创建函数,如表 14.4.2.1 所示:
1 、函数 xSemaphoreCreateCounting()
此函数用于创建一个计数型信号量,所需要的内存通过动态内存管理方法分配。此函数本质是一个宏,真正完成信号量创建的是函数 xQueueCreateCountingSemaphore(),此函数原型如下:
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount )
参数:
uxMaxCount: 计数信号量最大计数值,当信号量值等于此值的时候释放信号量就会失败。
uxInitialCount: 计数信号量初始值。
返回值:
NULL: 计数型信号量创建失败。
其他值: 计数型信号量创建成功,返回计数型信号量句柄。
2 、函数 xSemaphoreCreateCountingStatic()
此函数也是用来创建计数型信号量的,使用此函数创建计数型信号量的时候所需要的内存需要由用户分配。此函数也是一个宏,真正执行的是函数xQueueCreateCountingSemaphoreStatic(),函数原型如下:
SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount,
StaticSemaphore_t * pxSemaphoreBuffer )
参数:
uxMaxCount: 计数信号量最大计数值,当信号量值等于此值的时候释放信号量就会失败。
uxInitialCount: 计数信号量初始值。
pxSemaphoreBuffer :指向一个 StaticSemaphore_t 类型的变量,用来保存信号量结构体。
返回值:
NULL: 计数型信号量创建失败。
其他值: 计数型号量创建成功,返回计数型信号量句柄。
2.3 计数型信号量创建过程分析
这里只分析动态创建计数型信号量函数 xSemaphoreCreateCounting(),此函数是个宏,定义如下:
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount ) \
xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) ) \
#endif
可以看出,真正干事的是函数 xQueueCreateCountingSemaphore(),此函数在文件 queue.c 中
有如下定义:
QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount,
const UBaseType_t uxInitialCount )
{
QueueHandle_t xHandle;
configASSERT( uxMaxCount != 0 );
configASSERT( uxInitialCount <= uxMaxCount );
xHandle = xQueueGenericCreate( uxMaxCount,\ (1)
queueSEMAPHORE_QUEUE_ITEM_LENGTH, \
queueQUEUE_TYPE_COUNTING_SEMAPHORE );
if( xHandle != NULL )
{
( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount; (2)
traceCREATE_COUNTING_SEMAPHORE();
}
else
{
traceCREATE_COUNTING_SEMAPHORE_FAILED();
}
return xHandle;
}
(1)、计数型信号量也是在队列的基础上实现的,所以需要调用函数 xQueueGenericCreate()创 建 一 个 队 列 , 队 列 长 度 为 uxMaxCount , 对 列 项 长 度 为queueSEMAPHORE_QUEUE_ITEM_LENGTH( 此 宏 为 0) , 队 列 的 类 型 为queueQUEUE_TYPE_COUNTING_SEMAPHORE,表示是个计数型信号量。
(2)、队列结构体的成员变量 uxMessagesWaiting 用于计数型信号量的计数,根据计数型信号量的初始值来设置 uxMessagesWaiting。
3 互斥信号量
3.1 互斥信号量简介
互斥信号量其实就是一个拥有优先级继承的二值信号量,在同步的应用中(任务与任务或中断与任务之间的同步)二值信号量最适合。互斥信号量适合用于那些需要互斥访问的应用中。在互斥访问中互斥信号量相当于一个钥匙,当任务想要使用资源的时候就必须先获得这个钥匙,当使用完资源以后就必须归还这个钥匙,这样其他的任务就可以拿着这个钥匙去使用资源。互斥信号量使用和二值信号量相同的 API 操作函数,所以互斥信号量也可以设置阻塞时间,不同于二值信号量的是互斥信号量具有优先级继承的特性。当一个互斥信号量正在被一个低优先级的任务使用,而此时有个高优先级的任务也尝试获取这个互斥信号量的话就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级,这个过程就是优先级继承。优先级继承尽可能的降低了高优先级任务处于阻塞态的时间,并且将已经出现的“优先级翻转”的影响降到最低。优先级继承并不能完全的消除优先级翻转,它只是尽可能的降低优先级翻转带来的影响。硬实时应用应该在设计之初就要避免优先级翻转的发生。互斥信号量不能用于中断服务函数中,原因如下:
● 互斥信号量有优先级继承的机制,所以只能用在任务中,不能用于中断服务函数。
● 中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态。
3.2 创建互斥信号量
FreeRTOS 提供了两个互斥信号量创建函数,如表 14.8.2.1 所示:
1 、函数 xSemaphoreCreateMutex()
此函数用于创建一个互斥信号量,所需要的内存通过动态内存管理方法分配。此函数本质是一个宏,真正完成信号量创建的是函数 xQueueCreateMutex(),此函数原型如下:
SemaphoreHandle_t xSemaphoreCreateMutex( void )
参数:
无。
返回值:
NULL: 互斥信号量创建失败。
其他值: 创建成功的互斥信号量的句柄。
2 、函数 xSemaphoreCreateMutexStatic()
此函数也是创建互斥信号量的,只不过使用此函数创建互斥信号量的话信号量所需要的RAM 需要由用户来分配,此函数是个宏,具体创建过程是通过函数 xQueueCreateMutexStatic ()来完成的,函数原型如下:
SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer )
参数:
pxMutexBuffer :此参数指向一个 StaticSemaphore_t 类型的变量,用来保存信号量结构体。
返回值:
NULL: 互斥信号量创建失败。
其他值: 创建成功的互斥信号量的句柄。
3.3 互斥信号量创建过程分析
这里只分析动态创建互斥信号量函数 xSemaphoreCreateMutex (),此函数是个宏,定义如下:
#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
可以看出,真正干事的是函数 xQueueCreateMutex(),此函数在文件 queue.c 中有如下定义,
QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )
{
Queue_t *pxNewQueue;
const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0;
pxNewQueue = ( Queue_t * ) xQueueGenericCreate( uxMutexLength, uxMutexSize,\ (1)
ucQueueType );
prvInitialiseMutex( pxNewQueue ); (2)
return pxNewQueue;
}
(1)、调用函数 xQueueGenericCreate()创建一个队列,队列长度为 1,队列项长度为 0,队列类型为参数 ucQueueType。由于本函数是创建互斥信号量的,所以参数 ucQueueType 为queueQUEUE_TYPE_MUTEX。
(2)、调用函数 prvInitialiseMutex()初始化互斥信号量。
函数 prvInitialiseMutex()代码如下:
static void prvInitialiseMutex( Queue_t *pxNewQueue )
{
if( pxNewQueue != NULL )
{
//虽然创建队列的时候会初始化队列结构体的成员变量,但是此时创建的是互斥
//信号量,因此有些成员变量需要重新赋值,尤其是那些用于优先级继承的。
pxNewQueue->pxMutexHolder = NULL; (1)
pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX; (2)
//如果是递归互斥信号量的话。
pxNewQueue->u.uxRecursiveCallCount = 0; (3)
traceCREATE_MUTEX( pxNewQueue );
//释放互斥信号量
( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U,\
queueSEND_TO_BACK );
}
else
{
traceCREATE_MUTEX_FAILED();
}
}
(1)和(2)、这里大家可能会疑惑,队列结构体 Queue_t 中没有 pxMutexHolder 和 uxQueueType这两个成员变量吖?这两个东西是哪里来的妖孽?这两个其实是宏,专门为互斥信号量准备的,在文件 queue.c 中有如下定义:
#define pxMutexHolder pcTail
#define uxQueueType pcHead
#define queueQUEUE_IS_MUTEX NULL
当 Queue_t 用于表示队列的时候 pcHead 和 pcTail 指向队列的存储区域,当 Queue_t 用于表示互斥信号量的时候就不需要 pcHead 和 pcTail 了。当用于互斥信号量的时候将 pcHead 指向NULL 来表示 pcTail 保存着互斥队列的所有者,pxMutexHolder 指向拥有互斥信号量的那个任务的任务控制块。重命名 pcTail 和 pcHead 就是为了增强代码的可读性。
(3)、如果创建的互斥信号量是互斥信号量的话,还需要初始化队列结构体中的成员变量u.uxRecursiveCallCount。互斥信号量创建成功以后会调用函数xQueueGenericSend()释放一次信号量,说明互斥信号量默认就是有效的!互斥信号量创建完成以后如图 14.8.3.1 所示:
3.4 释放互斥信号量
释 放 互 斥 信 号 量 的 时 候 和 二 值 信 号 量 、 计 数 型 信 号 量 一 样 , 都 是 用 的 函 数xSemaphoreGive()(实际上完成信号量释放的是函数 xQueueGenericSend())。不过由于互斥信号量涉及到优先级继承的问题,所以具体处理过程会有点区别。使用函数 xSemaphoreGive()释放信号 量 最 重 要 的 一 步 就 是 将 uxMessagesWaiting 加 一 , 而 这 一 步 就 是 通 过 函 数prvCopyDataToQueue() 来 完 成 的 , 释 放 信 号 量 的 函 数 xQueueGenericSend() 会 调 用
prvCopyDataToQueue()。互斥信号量的优先级继承也是在函数 prvCopyDataToQueue()中完成的,此函数中有如下一段代码:
static BaseType_t prvCopyDataToQueue( Queue_t * const pxQueue,
const void * pvItemToQueue,
const BaseType_t xPosition )
{
BaseType_t xReturn = pdFALSE;
UBaseType_t uxMessagesWaiting;
uxMessagesWaiting = pxQueue->uxMessagesWaiting;
if( pxQueue->uxItemSize == ( UBaseType_t ) 0 )
{
#if ( configUSE_MUTEXES == 1 ) //互斥信号量
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ) (1)
{
xReturn = xTaskPriorityDisinherit( ( void * ) pxQueue->pxMutexHolder );(2)
pxQueue->pxMutexHolder = NULL; (3)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_MUTEXES */
}
/*********************************************************************/
/*************************省略掉其他处理代码**************************/
/*********************************************************************/
pxQueue->uxMessagesWaiting = uxMessagesWaiting + 1;
return xReturn;
}
(1)、当前操作的是互斥信号量。
(2)、调用函数 xTaskPriorityDisinherit()处理互斥信号量的优先级继承问题。
(3)、互斥信号量释放以后,互斥信号量就不属于任何任务了,所以 pxMutexHolder 要指向NULL。
在来看一下函数 xTaskPriorityDisinherit()是怎么具体的处理优先级继承的,函数xTaskPriorityDisinherit()代码如下:
BaseType_t xTaskPriorityDisinherit( TaskHandle_t const pxMutexHolder )
{
TCB_t * const pxTCB = ( TCB_t * ) pxMutexHolder;
BaseType_t xReturn = pdFALSE;
if( pxMutexHolder != NULL ) (1)
{
//当一个任务获取到互斥信号量以后就会涉及到优先级继承的问题,正在释放互斥
//信号量的任务肯定是当前正在运行的任务 pxCurrentTCB。
configASSERT( pxTCB == pxCurrentTCB );
configASSERT( pxTCB->uxMutexesHeld );
( pxTCB->uxMutexesHeld )--; (2)
//是否存在优先级继承?如果存在的话任务当前优先级肯定和任务基优先级不同。
if( pxTCB->uxPriority != pxTCB->uxBasePriority ) (3)
{
//当前任务只获取到了一个互斥信号量
if( pxTCB->uxMutexesHeld == ( UBaseType_t ) 0 ) (4)
{
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ) (5)
{
taskRESET_READY_PRIORITY( pxTCB->uxPriority ); (6)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
//使用新的优先级将任务重新添加到就绪列表中
traceTASK_PRIORITY_DISINHERIT( pxTCB, pxTCB->uxBasePriority );
pxTCB->uxPriority = pxTCB->uxBasePriority; (7)
/* Reset the event list item value. It cannot be in use for any other purpose if this task is running, and it must be running to give back the mutex. */
listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), \ (8)
( TickType_t ) configMAX_PRIORITIES - \
( TickType_t ) pxTCB->uxPriority );
prvAddTaskToReadyList( pxTCB ); (9)
xReturn = pdTRUE; (10)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
return xReturn;
}
(1)、函数的参数 pxMutexHolder 表示拥有此互斥信号量任务控制块,所以要先判断此互斥信号量是否已经被其他任务获取。
(2)、有的任务可能会获取多个互斥信号量,所以就需要标记任务当前获取到的互斥信号量个数,任务控制块结构体的成员变量 uxMutexesHeld 用来保存当前任务获取到的互斥信号量个数。任务每释放一次互斥信号量,变量 uxMutexesHeld 肯定就要减一。
(3)、判断是否存在优先级继承,如果存在的话任务的当前优先级肯定不等于任务的基优先级。
(4)、判断当前释放的是不是任务所获取到的最后一个互斥信号量,因为如果任务还获取了其他互斥信号量的话就不能处理优先级继承。优先级继承的处理必须是在释放最后一个互斥信号量的时候。
(5)、优先级继承的处理说白了就是将任务的当前优先级降低到任务的基优先级,所以要把当前任务先从任务就绪表中移除。当任务优先级恢复为原来的优先级以后再重新加入到就绪表中。
(6)、如果任务继承来的这个优先级对应的就绪表中没有其他任务的话就将取消这个优先级的就绪态。
(7)、重新设置任务的优先级为任务的基优先级 uxBasePriority。
(8)、复位任务的事件列表项。
(9)、将优先级恢复后的任务重新添加到任务就绪表中。
(10)、返回 pdTRUE,表示需要进行任务调度。
3.5 获取互斥信号量
获取互斥信号量的函数同获取二值信号量和计数型信号量的函数相同,都是xSemaphoreTake()(实际执行信号量获取的函数是 xQueueGenericReceive()),获取互斥信号量的过程也需要处理优先级继承的问题,函数 xQueueGenericReceive()在文件 queue.c 中有定义,在第十三章讲解队列的时候我们没有分析这个函数,本节就来简单的分析一下这个函数,缩减后的函数代码如下:
BaseType_t xQueueGenericReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t
xTicksToWait, const BaseType_t xJustPeeking )
{
BaseType_t xEntryTimeSet = pdFALSE;
TimeOut_t xTimeOut;
int8_t *pcOriginalReadPosition;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
for( ;; )
{
taskENTER_CRITICAL();
{
const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
//判断队列是否有消息
if( uxMessagesWaiting > ( UBaseType_t ) 0 ) (1)
{
pcOriginalReadPosition = pxQueue->u.pcReadFrom;
prvCopyDataFromQueue( pxQueue, pvBuffer ); (2)
if( xJustPeeking == pdFALSE ) (3)
{
traceQUEUE_RECEIVE( pxQueue );
//移除消息
pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1; (4)
#if ( configUSE_MUTEXES == 1 ) (5)
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
pxQueue->pxMutexHolder = (6)
( int8_t * ) pvTaskIncrementMutexHeldCount();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_MUTEXES */
//查看是否有任务因为入队而阻塞,如果有的话就需要解除阻塞态。
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == (7)
pdFALSE )
{
if( xTaskRemoveFromEventList( &
( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
{
//如果解除阻塞的任务优先级比当前任务优先级高的话就需要
//进行一次任务切换
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else (8)
{
traceQUEUE_PEEK( pxQueue );
//读取队列中的消息以后需要删除消息
pxQueue->u.pcReadFrom = pcOriginalReadPosition;
//如果有任务因为出队而阻塞的话就解除任务的阻塞态。
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == (9)
pdFALSE )
{
if( xTaskRemoveFromEventList( &
( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
{
//如果解除阻塞的任务优先级比当前任务优先级高的话就需要
//进行一次任务切换
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
return pdPASS;
}
else //队列为空 (10)
{
if( xTicksToWait == ( TickType_t ) 0 )
{
//队列为空,如果阻塞时间为 0 的话就直接返回 errQUEUE_EMPTY
taskEXIT_CRITICAL();
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else if( xEntryTimeSet == pdFALSE )
{
//队列为空并且设置了阻塞时间,需要初始化时间状态结构体。
vTaskSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
taskEXIT_CRITICAL();
vTaskSuspendAll();
prvLockQueue( pxQueue );
//更新时间状态结构体,并且检查超时是否发生
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ) (11)
{
if( prvIsQueueEmpty( pxQueue ) != pdFALSE ) (12)
{
traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ) (13)
{
taskENTER_CRITICAL();
{
vTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );(14)
}
taskEXIT_CRITICAL();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), (15)
xTicksToWait );
prvUnlockQueue( pxQueue );
if( xTaskResumeAll() == pdFALSE )
{
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
//重试一次
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
}
}
else
{
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
{
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
}
(1)、队列不为空,可以从队列中提取数据。
(2)、调用函数 prvCopyDataFromQueue()使用数据拷贝的方式从队列中提取数据。
(3)、数据读取以后需要将数据删除掉。
(4)、队列的消息数量计数器 uxMessagesWaiting 减一,通过这一步就将数据删除掉了。
(5)、表示此函数是用于获取互斥信号量的。
(6)、获取互斥信号量成功,需要标记互斥信号量的所有者,也就是给 pxMutexHolder 赋值,pxMutexHolder 应 该 是 当 前 任 务 的 任 务 控 制 块 。 但 是 这 里 是 通 过 函 数pvTaskIncrementMutexHeldCount()来赋值的,此函数很简单,只是将任务控制块中的成员变量uxMutexesHeld 加一,表示任务获取到了一个互斥信号量,最后此函数返回当前任务的任务控制块。
(7)、出队成功以后判断是否有任务因为入队而阻塞的,如果有的话就需要解除任务的阻塞态,如果解除阻塞的任务优先级比当前任务的优先级高还需要进行一次任务切换。
(8)、出队的时候不需要删除消息。
(9)、如果出队的时候不需要删除消息的话那就相当于刚刚出队的那条消息接着有效!既然还有有效的消息存在队列中,那么就判断是否有任务因为出队而阻塞,如果有的话就解除任务的阻塞态。同样的,如果解除阻塞的任务优先级比当前任务的优先级高的话还需要进行一次任务切换。
(10)、上面分析的都是队列不为空的时候,那当队列为空的时候该如何处理呢?处理过程和队列的任务级通用入队函数 xQueueGenericSend()类似。如果阻塞时间为 0 的话就就直接返回errQUEUE_EMPTY,表示队列空,如果设置了阻塞时间的话就进行相关的处理。
(11)、检查超时是否发生,如果没有的话就需要将任务添加到队列的 xTasksWaitingToReceive列表中。
(12)、检查队列是否继续为空?如果不为空的话就会在重试一次出队。
(13)、表示此函数是用于获取互斥信号量的。
(14)、调用函数 vTaskPriorityInherit()处理互斥信号量中的优先级继承问题,如果函数xQueueGenericReceive()用于获取互斥信号量的话,此函数执行到这里说明互斥信号量正在被其他的任务占用。此函数和 14.8.4 小节中的函数 xTaskPriorityDisinherit()过程相反。此函数会判断当前任务的任务优先级是否比正在拥有互斥信号量的那个任务的任务优先级高,如果是的话就会把拥有互斥信号量的那个低优先级任务的优先级调整为与当前任务相同的优先级!
(15)、经过(12)步判断,队列依旧为空,那么就将任务添加到列表 xTasksWaitingToReceive中。在上面的分析中,红色部分就是当函数 xQueueGenericReceive()用于互斥信号量的时候的处理过程,其中(13)和(14)条详细的分析了互斥信号量优先级继承的过程。我们举个例子来简单的演示一下这个过程,假设现在有两个任务 HighTask 和 LowTask,HighTask 的任务优先级为 4,LowTask 的任务优先级为 2。这两个任务都会操同一个互斥信号量 Mutex,LowTask 先获取到互斥信号量 Mutex。此时任务 HighTask 也要获取互斥信号量 Mutex,任务 HighTask 调用函数xSemaphoreTake()尝试获取互斥信号量 Mutex,发现此互斥信号量正在被任务 LowTask 使用,并且 LowTask 的任务优先级为 2,比自己的任务优先级小,因为任务 HighTask 就会将 LowTask的任务优先级调整为与自己相同的优先级,即 4,然后任务 HighTask 进入阻塞态等待互斥信号量有效。
3.6 递归互斥信号量
3.6.1 递归互斥信号量简介
递归互斥信号量可以看作是一个特殊的互斥信号量,已经获取了互斥信号量的任务就不能再次获取这个互斥信号量,但是递归互斥信号量不同,已经获取了递归互斥信号量的任务可以再次获取这个递归互斥信号量,而且次数不限!一个任务使用函数 xSemaphoreTakeRecursive()成功的获取了多少次递归互斥信号量就得使用函数 xSemaphoreGiveRecursive()释放多少次!比如某个任务成功的获取了 5 次递归信号量,那么这个任务也得同样的释放 5 次递归信号量。递归互斥信号量也有优先级继承的机制,所以当任务使用完递归互斥信号量以后一定要记得释放。同互斥信号量一样,递归互斥信号量不能用在中断服务函数中。
● 由于优先级继承的存在,就限定了递归互斥信号量只能用在任务中,不能用在中断服务函数中!
● 中断服务函数不能设置阻塞时间。要使用递归互斥信号量的话宏 configUSE_RECURSIVE_MUTEXES 必须为 1!
3.6.2 创建互斥信号量
FreeRTOS 提供了两个互斥信号量创建函数,如表 14.10.2.1 所示:
1 、函数 xSemaphoreCreateRecursiveMutex()
此函数用于创建一个递归互斥信号量,所需要的内存通过动态内存管理方法分配。此函数本质是一个宏,真正完成信号量创建的是函数 xQueueCreateMutex (),此函数原型如下:
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void )
参数:
无。
返回值:
NULL: 互斥信号量创建失败。
其他值: 创建成功的互斥信号量的句柄。
2 、函数 xSemaphoreCreateRecursiveMutexStatic()
此函数也是创建递归互斥信号量的,只不过使用此函数创建递归互斥信号量的话信号量所需 要 的 RAM 需 要 由 用 户 来 分 配 , 此 函 数 是 个 宏 , 具 体 创 建 过 程 是 通 过 函 数
xQueueCreateMutexStatic ()来完成的,函数原型如下:
SemaphoreHandle_t xSemaphoreCreateRecursiveMutexStatic( StaticSemaphore_t *pxMutexBuffer )
参数:
pxMutexBuffer :此参数指向一个 StaticSemaphore_t 类型的变量,用来保存信号量结构体。
返回值:
NULL: 互斥信号量创建失败。
其他值: 创建成功的互斥信号量的句柄。
3.6.3 递归信号量创建过程分析
这里只分析动态创建互斥信号量函数 xSemaphoreCreateRecursiveMutex (),此函数是个宏,
定义如下:
#define xSemaphoreCreateRecursiveMutex()
xQueueCreateMutex( queueQUEUE_TYPE_RECURSIVE_MUTEX )
可以看出,真正干事的是函数 xQueueCreateMutex(),互斥信号量的创建也是用的这个函数,
只是在创建递归互斥信号量的时候类型选择为 queueQUEUE_TYPE_RECURSIVE_MUTEX。具
体的创建过程参考 14.8.3 小节。
3.6.4 释放递归互斥信号量
递归互斥信号量有专用的释放函数:xSemaphoreGiveRecursive(),此函数为宏,如下:
#define xSemaphoreGiveRecursive( xMutex ) xQueueGiveMutexRecursive( ( xMutex ) )
函 数 的 参 数 就 是 就 是 要 释 放 的 递 归 互 斥 信 号 量 , 真 正 的 释 放 是 由 函 数
xQueueGiveMutexRecursive()来完成的,此函数代码如下:
BaseType_t xQueueGiveMutexRecursive( QueueHandle_t xMutex )
{
BaseType_t xReturn;
Queue_t * const pxMutex = ( Queue_t * ) xMutex;
configASSERT( pxMutex );
//检查递归互斥信号量是不是被当前任务获取的,要释放递归互斥信号量的任务肯定是当
//前正在运行的任务。 因为同互斥信号量一样,递归互斥信号量的获取和释放要在同一个
//任务中完成!如果当前正在运行的任务不是递归互斥信号量的拥有者就不能释放!
if( pxMutex->pxMutexHolder == ( void * ) xTaskGetCurrentTaskHandle() ) (1)
{
traceGIVE_MUTEX_RECURSIVE( pxMutex );
( pxMutex->u.uxRecursiveCallCount )--; (2)
if( pxMutex->u.uxRecursiveCallCount == ( UBaseType_t ) 0 ) (3)
{
( void ) xQueueGenericSend( pxMutex, NULL, \ (4)
queueMUTEX_GIVE_BLOCK_TIME, queueSEND_TO_BACK );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
xReturn = pdPASS; (5)
}
else
{
xReturn = pdFAIL; (6)
traceGIVE_MUTEX_RECURSIVE_FAILED( pxMutex );
}
return xReturn;
}
(1)、哪个任务获取到的递归互斥信号量,哪个任务就释放!要释放递归互斥信号量的任务肯定是当前正在运行的任务。检查这个任务是不是递归互斥信号量的拥有者,如果不是的话就不能完成释放。
(2)、uxRecursiveCallCount 减一,uxRecursiveCallCount 用来记录递归信号量被获取的次数。由于递归互斥信号量可以被一个任务多次获取,因此在释放的时候也要多次释放,但是只有在最后一次释放的时候才会调用函数xQueueGenericSend()完成释放过程,其他的时候只是简单的将 uxRecursiveCallCount 减一即可。
(3)、当 uxRecursiveCallCount 为 0 的时候说明是最后一次释放了。
(4)、如果是最后一次释放的话就调用函数 xQueueGenericSend()完成真正的释放过程。阻塞时间是queueMUTEX_GIVE_BLOCK_TIME,宏 queueMUTEX_GIVE_BLOCK_TIME 为 0。
(5)、递归互斥信号量释放成功,返回 pdPASS。
(6)、递归互斥信号量释放未成功,返回 pdFAIL。
由于递归互斥信号量可以被一个任务重复的获取,因此在释放的时候也要释放多次,但是
只有在最后一次释放的时候才会调用函数 xQueueGenericSend()完成真正的释放。其他释放的话
只是简单的将 uxRecursiveCallCount 减一。
3.6.5 获取递归互斥信号量
递归互斥信号量的获取使用函数 xSemaphoreTakeRecursive(),此函数是个宏,定义如下:
#define xSemaphoreTakeRecursive( xMutex, xBlockTime )
xQueueTakeMutexRecursive( ( xMutex ), ( xBlockTime ) )
函数第一个参数是要获取的递归互斥信号量句柄,第二个参数是阻塞时间。真正的获取过程是由函数 xQueueTakeMutexRecursive()来完成的,此函数如下:
BaseType_t xQueueTakeMutexRecursive( QueueHandle_t xMutex, //要获取的信号量
TickType_t xTicksToWait )//阻塞时间
{
BaseType_t xReturn;
Queue_t * const pxMutex = ( Queue_t * ) xMutex;
configASSERT( pxMutex );
traceTAKE_MUTEX_RECURSIVE( pxMutex );
if( pxMutex->pxMutexHolder == ( void * ) xTaskGetCurrentTaskHandle() ) (1)
{
( pxMutex->u.uxRecursiveCallCount )++; (2)
xReturn = pdPASS;
}
else
{
xReturn = xQueueGenericReceive( pxMutex, NULL, xTicksToWait, pdFALSE ); (3)
if( xReturn != pdFAIL )
{
( pxMutex->u.uxRecursiveCallCount )++; (4)
}
else
{
raceTAKE_MUTEX_RECURSIVE_FAILED( pxMutex );
}
}
return xReturn;
}
(1)、判断当前要获取递归互斥信号量的任务是不是已经是递归互斥信号量的拥有者。通过这一步就可以判断出当前任务是第一次获取递归互斥信号量还是重复获取。
(2)、如果当前任务已经是递归互斥信号量的拥有者,那就说明任务已经获取了递归互斥信号量,本次是重复获取递归互斥信号量,那么就简单的将 uxRecursiveCallCount 加一,然后返回pdPASS 表示获取成功。
(3)、如果任务是第一次获取递归互斥信号量的话就需要调用函数 xQueueGenericReceive()完成真正的获取过程。
(4)、第一次获取递归互斥信号量成功以后将 uxRecursiveCallCount 加一。
————————————————
版权声明:本文为CSDN博主「nippon1218」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/nippon1218/article/details/79039197