FreeRTOS互斥信号量
互斥信号量
1. 优先级翻转
优先级翻转是使用二值信号量时常遇见的问题,在可剥夺内核中非常常见,但是在实时系统中不允许出现这种现象,因为会破坏任务的预期顺序,可能会导致严重后果。
如下图所示的优先级翻转的例子:
- 低优先级任务 L 要访问共享资源,在获取到信号量使用 CPU 的过程中,如果此时高优先级任务 H 到达,会剥夺 L 的 CPU 使用权从而运行任务 H
- 当 H 想要访问共享资源时,由于此时该资源的信号量仍被 L 占用着,H 只能挂起等待 L 释放该信号量
- L 继续运行,此时中优先级任务 M 到达,再次剥夺 L 的 CPU 使用权从而运行任务 M
- M 执行完后,将 CPU 使用权归还给任务 L,L 继续运行
- L 运行完毕并释放出了信号量,至此高优先级的任务 H 才能获取到该信号量访问共享资源并运行
由上图可见,任务 H 的优先级实际上降到了任务 L 的优先级水平,因为要一直等待 L 释放其占用的共享资源。过程中不需要使用共享资源的中优先级任务 M 抢占 CPU 使用权后顺利运行完成,这样就相当于 M 的优先级高于 J,导致优先级翻转
2. 互斥信号量
互斥信号量其实就是一个拥有优先级继承的二值信号量。二值信号适合于同步应用中,互斥信号适用于需要互斥访问的应用中。互斥访问中互斥信号量相当于一个钥匙,当任务想要使用资源的时候就必须先获得这个钥匙,使用完资源后必须归还这个钥匙
当一个互斥信号量正被一个低优先级任务使用时,若有高优先级的任务也尝试获取该互斥信号量的话就会被阻塞。不过此时高优先级任务会将低优先级任务的优先级提升到与与自已相同的等级(即优先级继承)。优先级继承只是尽可能降低优先级翻转带来的影响,并不能完全消除优先级翻转
3. 互斥信号量的 API 函数
3.1 创建互斥信号量
1
2
3
4
5
6
7
/********************动态创建互斥信号量**********************************************/
SemaphoreHandle_t xSemaphoreCreateMutex(void)
/********************静态创建互斥信号量**********************************************/
SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t * pxSemaphoreBuffer)
//参数:pxSemaphoreBuffer 指向一个StaticSemaphore_t类型的变量,用来保存信号量结构体
/***********************************************************************************/
返回值:创建成功返回互斥信号量句柄;失败返回NULL
动态互斥信号量创建函数是一个宏,最终是通过 xQueueCreateMutex()
函数来完成,其源码如下:
1
2
3
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateMutex() xQueueCreateMutex(queueQUEUE_TYPE_MUTEX)
#endif
1
2
3
4
5
6
7
8
9
10
/**************xQueueCreateMutex源码分析*********************/
QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType ){
Queue_t *pxNewQueue;
const UBaseType_t uxMutexLength = (UBaseType_t) 1,uxMutexSize = (UBaseType_t) 0;
/* 创建一个队列长度为1,队列项长度为0,队列类型为queueQUEUE_TYPE_MUTEX的队列 */
pxNewQueue = (Queue_t *)xQueueGenericCreate(uxMutexLength,uxMutexSize,ucQueueType);
/* 初始化互斥信号量,其实就是初始化消息队列的控制块 */
prvInitialiseMutex( pxNewQueue );
return pxNewQueue;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**************prvInitialiseMutex源码分析*********************/
static void prvInitialiseMutex( Queue_t *pxNewQueue ){
if( pxNewQueue != NULL ){
/* 对创建好的队列结构体的某些互斥信号量特有的成员变量重新赋值 */
pxNewQueue->pxMutexHolder = NULL;//互斥信号量专有的宏
pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;//互斥信号量专有的宏
/* 如果是递归互斥信号量,将以下成员变量赋值为0 */
pxNewQueue->u.uxRecursiveCallCount = 0;
traceCREATE_MUTEX( pxNewQueue );
/* 释放互斥信号量 */
(void) xQueueGenericSend(pxNewQueue,NULL,(TickType_t) 0U,queueSEND_TO_BACK);
}
else{
traceCREATE_MUTEX_FAILED();
}
}
3.2 释放互斥信号量
释放互斥信号量函数与二值信号量、计数信号量释放函数是一样的。但是由于互斥信号涉及到优先级继承的问题,所以处理过程会有一些区别。
信号量释放函数 xSemaphoreGive()
实际上调用 xQueueGenericSend()
函数,分析该函数源码(消息队列中有介绍)可见函数会调用 prvCopyDataToQueue()
函数。互斥信号量的优先级继承就是在 prvCopyDataToQueue()
函数中完成的,其源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**************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 ){
/* 调用以下函数处理优先级继承问题 */
xReturn = xTaskPriorityDisinherit((void *) pxQueue->pxMutexHolder);
pxQueue->pxMutexHolder = NULL;//互斥信号量释放后,就不属于任何任务了
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_MUTEXES */
}
/*********************************************************/
/********************省略其他处理代码*********************/
/*********************************************************/
pxQueue->uxMessagesWaiting = uxMessagesWaiting + ( UBaseType_t ) 1;
return xReturn
}
优先级处理函数 xTaskPriorityDisinherit()
的源码分析如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
BaseType_t xTaskPriorityDisinherit(TaskHandle_t const pxMutexHolder){
TCB_t * const pxTCB = ( TCB_t * ) pxMutexHolder;
BaseType_t xReturn = pdFALSE;
if( pxMutexHolder != NULL ){
/* 任务获取到互斥信号量后就会涉及到优先级继承 */
configASSERT( pxTCB == pxCurrentTCB );
configASSERT( pxTCB->uxMutexesHeld );
( pxTCB->uxMutexesHeld )--;//用于标记任务当前获取到的互斥信号量个数
/* 如果任务当前优先级和任务基优先级不同,则存在优先级继承 */
if(pxTCB->uxPriority != pxTCB->uxBasePriority){
/* 当前任务只获取到一个互斥信号量 */
if( pxTCB->uxMutexesHeld == ( UBaseType_t ) 0 ){
/* 把任务的当前优先级降低到基优先级 */
if(uxListRemove(&(pxTCB->xStateListItem)) == (UBaseType_t ) 0){
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
else{
mtCOVERAGE_TEST_MARKER();
}
/* 使用新的优先级将任务添加到就绪列表中 */
traceTASK_PRIORITY_DISINHERIT( pxTCB, pxTCB->uxBasePriority );
pxTCB->uxPriority = pxTCB->uxBasePriority;
/* 复位任务的事件列表项 */
listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), \
(TickType_t)configMAX_PRIORITIES - (TickType_t)pxTCB->uxPriority);
prvAddTaskToReadyList( pxTCB );//将恢复优先级后的任务重新添加到就绪表
xReturn = pdTRUE;//返回pdTRUE,表示需要进行任务调度
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
else{
mtCOVERAGE_TEST_MARKER();
}
return xReturn;
}
3.3 获取互斥信号量
互斥信号量获取函数与二值信号量、计数信号量获取函数是一样的,都是 xSemaphoreTake()
函数,最终调用 xQueueGenericReceive()
;获取互斥信号量的过程也需要处理优先级继承的问题,源码分析如下示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
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){
/* 如有数据,调用以下函数使用数据拷贝的方式从队列中提取数据 */
pcOriginalReadPosition = pxQueue->u.pcReadFrom;
prvCopyDataFromQueue( pxQueue, pvBuffer );
if( xJustPeeking == pdFALSE ){//数据读取后需要将数据删除
traceQUEUE_RECEIVE( pxQueue );
/* 移除消息 */
pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1;
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ){
/* 若获取信号量成功,则标记互斥信号量的所有者 */
pxQueue->pxMutexHolder = (int8_t *) pvTaskIncrementMutexHeldCount();
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_MUTEXES */
/* 查看是否有任务因为入队而阻塞,若有就解除阻塞态 */
if(listLIST_IS_EMPTY(&( pxQueue->xTasksWaitingToSend)) == pdFALSE){
if(xTaskRemoveFromEventList(&(pxQueue->xTasksWaitingToSend)) != pdFALSE){
/* 若解除阻塞的任务优先级比当前任务高,就需要进行一次任务切换 */
queueYIELD_IF_USING_PREEMPTION();
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
else{//数据读取后不需要将数据删除
traceQUEUE_PEEK( pxQueue );
pxQueue->u.pcReadFrom = pcOriginalReadPosition;
/* 查看是否有任务因为出队而阻塞,若有就解除阻塞态 */
if(listLIST_IS_EMPTY(&(pxQueue->xTasksWaitingToReceive)) == pdFALSE){
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ){
/* 若解除阻塞的任务优先级比当前任务高,就需要进行一次任务切换 */
queueYIELD_IF_USING_PREEMPTION();
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
return pdPASS;
}
else{ //若队列为空
if( xTicksToWait == ( TickType_t ) 0 ){
/* 如阻塞时间为0,则直接返回errQUEUE_EMPTY */
taskEXIT_CRITICAL();
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else if( xEntryTimeSet == pdFALSE ){
/* 若阻塞时间不为0,则初始化时间状态结构体 */
vTaskSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;
}
else{
/* Entry time was already set. */
mtCOVERAGE_TEST_MARKER();
}
}
}
taskEXIT_CRITICAL();
vTaskSuspendAll();
prvLockQueue( pxQueue );
/* 更新时间状态结构体,检查是否超时 */
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ){
/* 检查队列是否为空 */
if( prvIsQueueEmpty( pxQueue ) != pdFALSE){//若队列为空
traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ){
taskENTER_CRITICAL();
{
/* 处理互斥信号量的优先级继承 */
vTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );
}
taskEXIT_CRITICAL();
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
/* 由于队列为空,将任务添加到xTasksWaitingToReceive列表中 */
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), 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();
}
}
}
}
4. 互斥信号量的应用实例
本实例介绍并模拟如何使用互斥信号量来避免章节 1 中的优先级翻转现象
使用 STM32CubeMX 将 FreeRTOS 移植到工程中,创建优先级为高中低的三个任务、一个互斥信号量
High_Task:高优先级任务,会获取互斥信号量,获取成功后进行相应的处理,处理完后释放互斥信号量Middle_Task:中优先级任务,简单的应用任务Low_Task:低优先级任务,会获取互斥信号量,获取成功后进行相应的处理,处理完后释放互斥信号量。但是任务占用互斥信号量的时间比高优先级任务占用的时间要长
4.1 STM32CubeMX 设置
- RCC 设置外接 HSE,时钟设置为 72M
- USART1 选择为异步通讯方式,波特率设置为 115200Bits/s,传输数据长度为 8Bit,无奇偶校验,1 位停止位;
- 激活 FreeRTOS,添加任务,设置任务名称、优先级、堆栈大小、函数名称等参数
- 动态创建互斥信号量
- 使用 FreeRTOS 操作系统,一定要将 HAL 库的 Timebase Source 从 SysTick 改为其他定时器,选好定时器后,系统会自动配置 TIM
- 输入工程名,选择路径(不要有中文),选择 MDK-ARM V5;勾选 Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击 GENERATE CODE,生成工程代码
4.2 MDK-ARM 软件编程
- 添加 High_Task、Middle_Task、Low_Task 任务函数代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/******************HighTask**************************/
void HighTask(void const * argument){
for(;;){
vTaskDelay(500);
printf("High task Pend Semaphore\r\n");
xSemaphoreTake(MutexSemHandle,portMAX_DELAY);
printf("High task running!\r\n");
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_1);
xSemaphoreGive(MutexSemHandle);
vTaskDelay(500);
}
}
/******************MiddleTask***********************/
void MiddleTask(void const * argument){
for(;;){
printf("Middle task running!\r\n");
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0);
vTaskDelay(1000);
}
}
/******************LowTask**************************/
void LowTask(void const * argument){
for(;;){
xSemaphoreTake(MutexSemHandle,portMAX_DELAY);
printf("Low task running!\r\n");
for(int i=0;i<20000000;i++){
taskYIELD();
}
xSemaphoreGive(MutexSemHandle);
vTaskDelay(1000);
}
}
4.3 下载验证
编译无误下载到开发板后,打开串口调试助手,串口输出如下图示的调试信息:
由于 High_Task 任务延时 500ms,因此 Middle_Task 最先开始运行;之后 Low_Task 运行,获取互斥信号;High_Task 开始运行,阻塞在请求互斥信号量这里,等待 Low_Task 释放互斥信号量此时由于 Low_Task 正在使用互斥信号量,因此 Low_Task 的优先级暂时提升到了与 High_Task 相同的优先级,所以 Middle_Task 无法打断 Low_Task 的运行。Low_Task 运行完后,释放互斥信号量,High_Task 获取互斥信号量并运行
由此可见,互斥信号量有效的解决了优先级翻转的问题
以上转载自博主“安迪西嵌入式”的FreeRTOS专栏,仅作学习记录,如有侵权,请联系删除。