0%

freertos 任务通信

概览

FreeRTOS的信号量包括二进制信号量、计数信号量、互斥信号量(以后简称互斥量)和递归互斥信号量(以后简称递归互斥量)
FreeRTOS的信号量是使用队列机制实现的,数据结构也完全是队列的那一套。
除此之外,还有一种简单的任务间的同步方式: 任务通知
它的数据结构嵌在任务TCB中的,并且数据结构十分简单,涉及到任务TCB的两个字段,我们将它单独列出来:

1
2
1.  volatile uint32_t ulNotifiedValue; 	/*任务通知值*/  
2. volatile uint8_t ucNotifyState; /*任务通知状态,标识任务是否在等待通知等*/

本文只讨论信号量的部分

api

0cad59aa-bb2a-41b1-a65a-dc30706f90b8
创建队列与其他几个不同,暂且不表

二进制信号量

创建

vSemaphoreCreateBinary xSemaphoreCreateBinary
类型为 queueQUEUE_TYPE_BINARY_SEMAPHORE

1
2
3
4
5
6
7
8
9
10
#define vSemaphoreCreateBinary( xSemaphore )																							\
{ \
( xSemaphore ) = xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE ); \
if( ( xSemaphore ) != NULL ) \
{ \
( void ) xSemaphoreGive( ( xSemaphore ) ); \
} \
}

#define xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )

vSemaphoreCreateBinary 使用方式是先创建,再take, 再give, 别的任务再take, 再give。
xSemaphoreCreateBinary的使用方式不同,它的处理方式是,创建后,需要先give,才能take到。
其他的计数信号量、互斥量的api与废弃的版本的使用方式都和该api的差不多。

通过这个宏定义我们知道创建二进制信号量实际上是创建了一个队列,队列项有1个,但是队列项的大小为0(宏semSEMAPHORE_QUEUE_ITEM_LENGTH定义为0)

3842242d-c821-41aa-bb29-90428a4eb141

图1-1:初始化后的二进制信号量对象内存

二进制信号量的释放和获取都是通过操作队列结构体成员uxMessageWaiting来实现的(图1-1红色部分,uxMessageWaiting表示队列中当前队列项的个数)。
经过初始化后,变量uxMessageWaiting为0,这说明队列为空,也就是信号量处于无效状态。
在使用API函数xSemaphoreTake()获取信号之前,需要先释放一个信号量。
释放信号量 give 会将 uxMessageWaiting + 1,这样take才能获取到信号。

计数信号量

创建

创建计数信号量间接使用通用队列创建函数xQueueGenericCreate()。创建计数信号量API接口同样是个宏定义:

1
#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount ) xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) )

创建计数信号量API接口有两个参数,含义如下:

  • uxMaxCount:最大计数值,当信号到达这个值后,就不再增长了。
  • uxInitialCount:创建信号量时的初始值。

queueSEMAPHORE_QUEUE_ITEM_LENGTH = 0, 类型为queueQUEUE_TYPE_COUNTING_SEMAPHORE

1
2
3
4
5
6
7
8
9
#if( ( configUSE_COUNTING_SEMAPHORES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount, const UBaseType_t uxInitialCount )
{
xHandle = xQueueGenericCreate( uxMaxCount, queueSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_COUNTING_SEMAPHORE );
( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount;
return xHandle;
}
#endif

![2987021a-e63f-4443-9dc7-790ca0c91530 (1)](attachments/2987021a-e63f-4443-9dc7-790ca0c91530 (1)-171340877101513.png)

图1-2:初始化后的计数信号量对象内存

互斥量

创建

创建互斥量间接使用通用队列创建函数xQueueGenericCreate()。创建互斥量API接口同样是个宏,定义如下:

1
#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )

类型为 queueQUEUE_TYPE_MUTEX, 同样使用xQueueGenericCreate 间接创建。 队列项 item = 0,可以看到信号量的队列项大小全是0

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
QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )
{
uxMutexLength = ( UBaseType_t ) 1
uxMutexSize = ( UBaseType_t ) 0;
// 首先调用通用队列创建函数xQueueGenericCreate()来创建一个队列,队列项数目为1,队列项大小为0,说明创建的队列只有队列数据结构存储空间而没有队列项存储空间。
pxNewQueue = ( Queue_t * ) xQueueGenericCreate( uxMutexLength, uxMutexSize, ucQueueType );
// 按mutex 来初始化通用队列
prvInitialiseMutex( pxNewQueue );
return pxNewQueue;
}
static void prvInitialiseMutex( Queue_t *pxNewQueue )
{
/* The queue create function will set all the queue structure members
correctly for a generic queue, but this function is creating a
mutex. Overwrite those members that need to be set differently -
in particular the information required for priority inheritance. */
// 1. #define pxMutexHolder pcTail
// 2. #define uxQueueType pcHead
pxNewQueue->pxMutexHolder = NULL;
pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;
/* In case this is a recursive mutex. */
pxNewQueue->u.uxRecursiveCallCount = 0;

/* Start with the semaphore in the expected state. */
// 释放一个互斥量,相当于互斥量创建后是有效的
( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK );
}
}

最后调用函数xQueueGenericSend()释放一个互斥量,相当于互斥量创建后是有效的,可以直接使用获取信号量API函数来获取这个互斥量。
如果某资源同时只准一个任务访问,可以用互斥量保护这个资源。这个资源一定是存在的
所以创建互斥量时会先释放一个互斥量,表示这个资源可以使用。

使用方式: 先创建, 然后take, 再give。
sample:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
QueueHandle_t mutexHandle;
mutexHandle = xSemaphoreCreateMutex();
void vTaskFunction2( void *pvParameters )
{
char *pcTaskName;
xQueueSemaphoreTake(mutexHandle, 2000);
info("vTaskFunction2 have take mutex\n");
pcTaskName = (char*) pvParameters;
vTaskDelay(1000);
info("vTaskFunction2 release mutex\n");
xSemaphoreGive(mutexHandle);
vTaskDelay(1000);
while(1);
}

void vTaskFunction3( void *pvParameters )
{
info("vTaskFunction3 taking the mutex\n");
xQueueSemaphoreTake(mutexHandle, 2000);
info("vTaskFunction3 have take the mutex\n");
while(1);
}

0aece3ee-4e69-425c-bcc4-c4f24d9839b1

图1-3:初始化后的互斥量对象内存

互斥量和二进制信号量区别:

  • 二进制信号量可以在随便一个任务中获取或释放,然后也可以在任意一个任务中释放或获取。
  • 互斥量具有优先级继承机制,二进制信号量没有
  • 互斥量不可以用于中断服务程序,二进制信号量可以。

递归互斥量

创建递归互斥量间接使用通用队列创建函数xQueueGenericCreate()。创建递归互斥量API接口同样是个宏,定义如下:

1
#define xSemaphoreCreateRecursiveMutex() xQueueCreateMutex( queueQUEUE_TYPE_RECURSIVE_MUTEX )

类型为 queueQUEUE_TYPE_RECURSIVE_MUTEX
创建互斥量和创建递归互斥量是调用的同一个函数xQueueCreateMutex(),至于参数queueQUEUE_TYPE_RECURSIVE_MUTEX,我们在FreeRTOS一文中已经知道,它只是用于可视化调试
因此创建互斥量和创建递归互斥量可以看作是一样的,初始化后的递归互斥量对象内存也和互斥量一样,如图1-3所示。

释放信号量

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

xSemaphoreGive

不带中断保护的版本

1
2
3
#define xSemaphoreGive( xSemaphore )		\
// 实际上是一次入队操作,并且阻塞时间为0(semGIVE_BLOCK_TIME=0)。
xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )

对于二进制信号量和计数信号量, 释放一个信号量的过程实际上可以简化为两种情况:

  • 第一,如果队列未满,队列结构体成员uxMessageWaiting加1,判断xSemaphore上是否有阻塞的任务,有的话解除阻塞,然后返回成功信息(pdPASS);
  • 第二,如果队列满,返回错误代码(err_QUEUE_FULL),表示队列满。

对于互斥量要复杂些,因为互斥量具有优先级继承机制。
释放互斥量可以简化为两种情况:

  • 第一,如果队列未满
    • 队列结构体成员uxMessageWaiting加1
    • 还要判断获取互斥量的任务是否有优先级继承
      • 如果有的话,还要将任务的优先级恢复到原始值。
        • 恢复到原来值也是有条件的,就是该任务必须在没有使用其它互斥量的情况下,才能将继承的优先级恢复到原始值。
    • 然后判断是否有阻塞的任务,有的话解除阻塞,最后返回成功信息(pdPASS);
  • 第二,如果如果队列满,返回错误代码(err_QUEUE_FULL),表示队列满。

优先级继承机制

简单的说:
前提条件: 三个任务 A B C ,优先级 C > B > A , C 与 A 互斥访问同一资源 M:
假设 A (running), A持有资源M, C等待资源M。 来了一个中断,A退出运行态,此时应该切换到C,但是C在等待资源M,所以C 得不到调度
如果按现有的优先级顺序,需要调度到B,再调度到C, 如果C的take的timeout非常大时,可能会阻塞很长事件。
加入优先级继承后, 这样C在take时,会将A的优先级提升到和C一样, 等不到资源调度出去后,会先切到A,A在做完事情后,释放资源M,A在释放时,需要将优先级回到原来的初始值, 再切换到C, C就不需要阻塞那么长的时间。

xSemaphoreGiveFromISR

带中断保护的版本
被释放的信号量可以是二进制信号量和计数信号量。和普通版本的释放信号量API函数不同,它不能释放互斥量,这是因为互斥量不可以在中断中使用
互斥量只能在任务的上下文中使用, take, give
互斥量的优先级继承机制只能在任务中起作用,在中断中毫无意义。
带中断保护的信号量释放其实也是一个宏,真正调用的函数是xQueueGiveFromISR

1
#define xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken )	xQueueGiveFromISR( ( QueueHandle_t ) ( xSemaphore ), ( pxHigherPriorityTaskWoken ) )
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
BaseType_t xQueueGiveFromISR( QueueHandle_t xQueue, BaseType_t * const pxHigherPriorityTaskWoken )
{
BaseType_t xReturn;
UBaseType_t uxSavedInterruptStatus;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
{
const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
/*当队列用于实现信号量时,永远不会有数据出入队列,但是仍然要检查队列是否为空 */
if( uxMessagesWaiting < pxQueue->uxLength )
{
traceQUEUE_SEND_FROM_ISR( pxQueue );
/*互斥量不允许使用这个函数,只是给二进制信号量或计数信号量的,所以只是简单更新队列项计数器. */
pxQueue->uxMessagesWaiting = uxMessagesWaiting + ( UBaseType_t ) 1;

const int8_t cTxLock = pxQueue->cTxLock;
// 事件列表未上锁才能对事件列表进行操作。 queueUNLOCKED=-1
if( cTxLock == queueUNLOCKED )
{
// xQueue上有阻塞任务
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
// 阻塞任务列表一个出队, 即唤醒一个阻塞的任务
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
// 传入的第二个参数, 不为NULL的话,表示这次被唤醒的任务有更高的优先级,需要记录切换上下文请求
if( pxHigherPriorityTaskWoken != NULL )
*pxHigherPriorityTaskWoken = pdTRUE;
}
else
{
//队列上锁时, 中断服务里释放信号量, cTxLock + 1. 表示队列上锁期间入队(即投递)的个数
pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 );
}
xReturn = pdPASS;
}
}
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
return xReturn;
}
  • 如果队列满,直接返回错误代码(err_QUEUE_FULL);
  • 如果队列未满,则将队列结构体成员uxMessageWaiting加1,然后视队列是否上锁而决定是否解除任务阻塞(如果有得话)

获取信号量

1
#define xSemaphoreTake( xSemaphore, xBlockTime )		xQueueSemaphoreTake( ( xSemaphore ), ( xBlockTime ) )

无论二进制信号量、计数信号量还是互斥量,它们都使用相同的获取和释放API函数。
获取信号量会消耗信号量,如果获取信号量失败,任务可能会阻塞,阻塞时间由函数参数xBlockTime指定,如果为0,则直接返回,不阻塞。
获取信号量分为不带中断保护和带中断保护两个版本。

1
BaseType_t xQueueSemaphoreTake( QueueHandle_t xQueue, TickType_t xTicksToWait )
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
BaseType_t xQueueSemaphoreTake( QueueHandle_t xQueue, TickType_t xTicksToWait )
{
BaseType_t xEntryTimeSet = pdFALSE;
TimeOut_t xTimeOut;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;

#if( configUSE_MUTEXES == 1 )
BaseType_t xInheritanceOccurred = pdFALSE;
#endif

for( ;; )
{
taskENTER_CRITICAL();
{
const UBaseType_t uxSemaphoreCount = pxQueue->uxMessagesWaiting;
1.a ----> // 有信号量的情况, 直接拿到. 队列uxMessagesWaiting表示队列上有多少信号量, uxMessagesWaiting-1
if( uxSemaphoreCount > ( UBaseType_t ) 0 )
{

// 队列项item 是0, 承载信息的是 uxMessagesWaiting
1.a.1 ----> // uxMessagesWaiting -1 ,表示被取走了一个信号量
pxQueue->uxMessagesWaiting = uxSemaphoreCount - ( UBaseType_t ) 1;

#if ( configUSE_MUTEXES == 1 )
{
1.a.2 ----> // Queue的pxMutexHolder更新为当前任务, 同时当前任务的持有mutex数+1
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
//任务 pxCurrentTCB->uxMutexesHeld++ pxMutexHolder指向当前任务TCB
pxQueue->pxMutexHolder = ( int8_t * ) pvTaskIncrementMutexHeldCount();
}
}
#endif /* configUSE_MUTEXES */
1.a.3 ----> // 查看是否有等待 释放 该信号量的任务,如果有
// 将任务的事件列表项从xTasksWaitingToSend上删除,并且将任务移动到就绪列表中, 触发pendsv中断
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
{
queueYIELD_IF_USING_PREEMPTION();
}
}
taskEXIT_CRITICAL();
return pdPASS;
}
2. ----> //没有信号量,需要等别人释放
else
{
2.a ----> //没有信号量,且未设置timeout的情况, 直接返回ERROR_EMPTY
if( xTicksToWait == ( TickType_t ) 0 ) //不阻塞队列
{
taskEXIT_CRITICAL();
traceQUEUE_RECEIVE_FAILED( pxQueue );
//返回失败 EMPTY
return errQUEUE_EMPTY;
}
2.b ----> //没有信号量, 且设置了timeout, 初始化timeout结构, 用于后面的计算耗时
else if( xEntryTimeSet == pdFALSE )
{
// 初始化timeout structure
vTaskInternalSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;
}
}
}
taskEXIT_CRITICAL();

2.b ----> // 来到这里说明当前队列上没有信号量, 且设置了timeout的情况
//挂起调度器
vTaskSuspendAll();
// lock the queue
prvLockQueue( pxQueue );
2.b.1 ----> // 设置了timeout, 队列上没有信号量, timeout未到期
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
{
// pxQueue->uxMessagesWaiting == ( UBaseType_t ) 0 未到timeout,队列上没有信号量。
if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
{
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
taskENTER_CRITICAL();
{
// 优先级继承?
xInheritanceOccurred = xTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );
}
taskEXIT_CRITICAL();
}
}
#endif
// 放入等待队列, 即当前任务的事件列表项挂入队列的xTasksWaitingToReceive, 同时当前任务的状态列表项挂入延时列表, 更新nextUnblocktime, 由tick中断处理.
// 一是当队列上被释放了信号量时会将xTasksWaitingToReceive上的等待的任务唤醒, 如果在到期之前没有信号被释放的话, timeout到期后, 该任务才会被唤醒.
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
// 解锁queue,解锁后才可以操作队列, 这里的操作是专门针对中断服务说的
prvUnlockQueue( pxQueue );
// 恢复调度器, 切换任务
if( xTaskResumeAll() == pdFALSE )
{
portYIELD_WITHIN_API();
}
}
else // 未到timeout 但有信号被塞过来了, 当前任务再次尝试获取信号量, 这个地方是在循环中, 因此会进入下一轮循环
{
// 重试
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
}
}
2.b.2 ----> // 设置了timeout, 队列上没有信号量, timeout到期了
else
{
/* Timed out. */ //timeout耗尽了, 不该阻塞了,该返回了
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll(); //恢复调度器
// 没有信号量
if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
{
#if ( configUSE_MUTEXES == 1 )
{
// 发生过优先级继承, 对应上面timeout未到期时的设置
if( xInheritanceOccurred != pdFALSE )
{
taskENTER_CRITICAL();
{
UBaseType_t uxHighestWaitingPriority;
// 导致持有该mutex的任务发生了优先级继承的情况, 现在该任务已经超时返回,其他任务的优先级反转应该disable掉, 但是这个其他任务的优先级应该继承和它等待相同互斥锁的次优先级任务
uxHighestWaitingPriority = prvGetDisinheritPriorityAfterTimeout( pxQueue );
vTaskPriorityDisinheritAfterTimeout( ( void * ) pxQueue->pxMutexHolder, uxHighestWaitingPriority );
}
taskEXIT_CRITICAL();
}
}
#endif /* configUSE_MUTEXES */
//没有任务释放信号量,返回ERROR EMPTY
return errQUEUE_EMPTY;
}
}
}
}

小结

  1. Queue上有信号量的情况下
    a. 出队(出队/入队, 队是指的Queue)直接拿到了该信号量. 返回true

    • 队列uxMessagesWaiting表示队列上有多少信号量, uxMessagesWaiting-1
    • Queue的pxMutexHolder更新为当前任务, 同时当前任务的持有mutex数+1
    • 查看是否有等待释放该信号量的任务,如果有, 将等待任务的事件列表项从xTasksWaitingToSend上删除,并且将任务移动到就绪列表中, 触发pendsv中断
      • 释放信号量的任务为什么会阻塞? Queue满了且不是覆盖式入队且设置了timeout的情况, 参考freertos 队列xQueue分析, 此时要先将这个释放信号量的任务唤醒. 需要切换上下文.
  2. Queue上没有信号量:

a. 没有信号量,且未设置timeout的情况, 直接返回ERROR_EMPTY
b. 设置了timeout, 队列上没有信号量

  - 设置了timeout, 队列上没有信号量, timeout未到期
     - 如果信号量是mutex, 则需要记录是否发生了或发生过优先级反转, 这个标记在后面timeout到期后会用到, 
        - 如果此时需要发生优先级继承
           - 持有该mutex的任务继承当前任务的优先级
           - 如果持有该mutex的任务处于就绪列表中, 需要将其重入readylists, 复位readylists的最高优先级
     - 放入等待队列, 即当前任务的事件列表项挂入队列的`xTasksWaitingToReceive`, 同时当前任务的状态列表项挂入延时列表, 更新nextUnblocktime, 由tick中断处理. 
        - 一是当队列上被释放了信号量时会将`xTasksWaitingToReceive`上的等待的任务唤醒, 
        - 如果在到期之前没有信号被释放的话, timeout到期后, 该任务才会被唤醒.
     - 

解锁queue (解锁后才可以操作队列, 这里的操作是专门针对中断服务说的), 恢复调度器, 切换任务, 该任务已经由就绪态转到阻塞态, 调度到就绪列表上最高优先级
- 设置了timeout, 队列上没有信号量, timeout到期了(由tick中断处理delaylist上状态列表项到期时唤醒)
- 如果信号量是mutex, 在未到期时发生了或发生过优先级继承, 需要重设持有该mutex的任务的优先级.
- 有其他任务在等待take该mutex时, 持有mutex任务初始优先级大于等于其他take该mutex任务的最高优先级时, 重设为这些任务中的最高优先级
- 没有其他任务在等待take该mutex时, 重设优先级为其初始的优先级( base). 或持有mutex任务初始优先级大于等于其他take该mutex任务的最高优先级时, 回到其初始优先级
- 持有该mutex的任务的优先级与将要变动到的优先级如果相同, 则后面什么也不做, 直接返回. 只有发生了变化时, 才进行后面的步骤
- 优先级需要变更的情况下:

持有该mutex的任务如果在就绪列表中, 需要重入readylists, 同时复位readylists的最高优先级

这个需要结合入队(入Queue) (释放信号量的过程)来看

freertos 队列xQueue分析

优先级继承

这里需要特别看下上述过程发生优先级继承时发生了什么?
信号量是mutex的情况, 才会发生优先级继承. Queue上没有信号量, 该任务要take信号量时, 设置了timeout

  1. timeout未到期, 会调用xTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder )

见下面的分析, xInheritanceOccurred= true, 表明
a. 当前任务优先级优先级大于持有该任务优先级的任务, 此时发生优先级继承
1). 如果持有该mutex的任务处于就绪列表中, 需要更新就绪列表, 包括重设优先级等, 持有该mutex的任务需要重入就绪列表.
b. 有该mutex的任务优先级比当前任务的优先级低, 但是持有mutex的任务已经发生过优先级继承时, 也返回true

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
// pxQueue->pxMutexHolder是当前hold 该mutex的任务
xInheritanceOccurred = xTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder )
BaseType_t xTaskPriorityInherit( TaskHandle_t const pxMutexHolder )
{
TCB_t * const pxMutexHolderTCB = ( TCB_t * ) pxMutexHolder;
BaseType_t xReturn = pdFALSE;
// 当前持有该mutex的任务, 有可能没有任务持有该mutex. 在没有任务持有时,什么都不做
if( pxMutexHolder != NULL )
{
1.a ----> // 当前任务优先级优先级大于持有该任务优先级的任务
if( pxMutexHolderTCB->uxPriority < pxCurrentTCB->uxPriority )
{
// 事件列表项保存的是任务的优先级的补数 (只是为了排序, 升序排列, 优先级高的排在列表的前面, 从头开始取任务)
// 未设置 taskEVENT_LIST_ITEM_VALUE_IN_USE 时才可以更新其事件列表项的value
if( ( listGET_LIST_ITEM_VALUE( &( pxMutexHolderTCB->xEventListItem ) ) & taskEVENT_LIST_ITEM_VALUE_IN_USE ) == 0UL )
{
listSET_LIST_ITEM_VALUE( &( pxMutexHolderTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxCurrentTCB->uxPriority );
}
// 如果持有该mutex的任务处于就绪列表中, 需要更新就绪列表, 因为这个任务的优先级已经变了.
if( listIS_CONTAINED_WITHIN( &( pxReadyTasksLists[ pxMutexHolderTCB->uxPriority ] ), &( pxMutexHolderTCB->xStateListItem ) ) != pdFALSE )
{
if( uxListRemove( &( pxMutexHolderTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
taskRESET_READY_PRIORITY( pxMutexHolderTCB->uxPriority );
}
// 更新持有mutex的任务优先级. 前面只是更新了其事件列表项的value
pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;
prvAddTaskToReadyList( pxMutexHolderTCB );
}
// 无论持有mutex的任务在不在就绪列表中,都要更新其优先级为当前任务的优先级
else
{
pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;
}

<------ // 返回true, 发生了优先级继承
xReturn = pdTRUE;
}
else
{
1.b ----> // 持有该mutex的任务优先级比当前任务的优先级低, 但是持有mutex的任务已经发生过优先级继承时, 也返回true
if( pxMutexHolderTCB->uxBasePriority < pxCurrentTCB->uxPriority )
{
<------- // 返回true, 发生过优先级继承
xReturn = pdTRUE;
}
}
}
<------- 2.-----> // 返回false, 未发生优先级继承
return xReturn;
}
  1. timeout到期, 会调用如下过程重设 当初发生 优先级继承的任务的优先级

持有该mutex的任务的优先级不是回到其初始的优先级, 如果有其他任务仍然在等待该mutex, 则需要从这些任务中找出最高的优先级(listGET_ITEM_VALUE_OF_HEAD_ENTRY( &( pxQueue->xTasksWaitingToReceive ) )),
然后设置到该最高优先级. 这个优先级被叫做次高优先级.
a. 有其他任务在等待take该mutex时, 持有mutex任务初始优先级大于等于其他take该mutex任务的最高优先级时, 重设为这些任务中的最高优先级
b. 没有其他任务在等待take该mutex时, 重设优先级为其初始的优先级( base). 或持有mutex任务初始优先级大于等于其他take该mutex任务的最高优先级时, 回到其初始优先级
持有该mutex的任务的优先级与将要变动到的优先级如果相同, 则后面什么也不做, 直接返回. 只有发生了变化时, 才进行后面的步骤
优先级需要变更的情况下:
持有该mutex的任务如果在就绪列表中, 需要重入readylists, 同时复位readylists的最高优先级

当前take mutex的任务到期后, 如果持有该mutex的任务在当前任务未到期时发生了或发生过优先级继承时, 需要重设持有该mutex的任务优先级.
重设过程只发生在持有该mutex的任务此时只持有了这一个mutex时, 如果持有多个mutex, 则不会进行重设

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
uxHighestWaitingPriority = prvGetDisinheritPriorityAfterTimeout( pxQueue );
static UBaseType_t prvGetDisinheritPriorityAfterTimeout( const Queue_t * const pxQueue )
{
UBaseType_t uxHighestPriorityOfWaitingTasks;
// 仍然有任务在等待take该mutex, 从这些任务中找到最高的优先级
if( listCURRENT_LIST_LENGTH( &( pxQueue->xTasksWaitingToReceive ) ) > 0 )
{
uxHighestPriorityOfWaitingTasks = configMAX_PRIORITIES - listGET_ITEM_VALUE_OF_HEAD_ENTRY( &( pxQueue->xTasksWaitingToReceive ) );
}
// 没有任务等待该mutex, 返回idle任务的优先级, idle任务的优先级应该是最低的那个
else
{
uxHighestPriorityOfWaitingTasks = tskIDLE_PRIORITY;
}

return uxHighestPriorityOfWaitingTasks;
}
vTaskPriorityDisinheritAfterTimeout( ( void * ) pxQueue->pxMutexHolder, uxHighestWaitingPriority );
void vTaskPriorityDisinheritAfterTimeout( TaskHandle_t const pxMutexHolder, UBaseType_t uxHighestPriorityWaitingTask )
{
TCB_t * const pxTCB = ( TCB_t * ) pxMutexHolder;
UBaseType_t uxPriorityUsedOnEntry, uxPriorityToUse;
const UBaseType_t uxOnlyOneMutexHeld = ( UBaseType_t ) 1;

if( pxMutexHolder != NULL )
{
// 中间变量, 持有该mutex的任务优先级有要继承的优先级的比较, 如果相等, 则后面的步骤就不用做了, 直接返回
2.a ----> // 重设优先级为次高任务优先级
if( pxTCB->uxBasePriority < uxHighestPriorityWaitingTask )
{
uxPriorityToUse = uxHighestPriorityWaitingTask;
}
2.b ----> // 回到其初始优先级, 这个对应上面返回 tskIDLE_PRIORITY 的情况, 和当前任务初始优先级大于其他任务优先级的情况
else
{
uxPriorityToUse = pxTCB->uxBasePriority;
}
if( pxTCB->uxPriority != uxPriorityToUse )
{
// 取消优先级继承的过程只发生在该任务只持有了一个mutex的情况.
if( pxTCB->uxMutexesHeld == 1 )
{
uxPriorityUsedOnEntry = pxTCB->uxPriority;
// 更新该任务的优先级为等待take该mutex任务中的最高优先级
pxTCB->uxPriority = uxPriorityToUse;
// 该任务列表没有其他用途时(判断有没有taskEVENT_LIST_ITEM_VALUE_IN_USE置位) 没有置位, 说明没有其他情况, 需要更新其事件列表项的value
if( ( listGET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ) ) & taskEVENT_LIST_ITEM_VALUE_IN_USE ) == 0UL )
{
listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriorityToUse ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
}
// 持有该优先级的任务在就绪列表中,需要重入readylists, 同时复位readylists的最高优先级
if( listIS_CONTAINED_WITHIN( &( pxReadyTasksLists[ uxPriorityUsedOnEntry ] ), &( pxTCB->xStateListItem ) ) != pdFALSE )
{
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
prvAddTaskToReadyList( pxTCB );
}
}
}
}

}