FreeRTOS内核详解—-Queue
队列的主要作用是任务间或者任务与中断之间的通信或者同步,对于FreeRTOS内核而言,队列的主要用在信号量的实现上。因为供外部使用的函数基本都是对几个特定函数,使用宏进行包装实现的,这里只讲述主要的函数。同时FreeRTOS给每个队列基本操作函数提供了一个可选简化版本,简化版本中所有的操作都需要关中断,以此来达到简化操作,提高速度的目的。简化版本的函数中带有Alt,例如 QueueGenericSend
简化版本 QueueAltGenericSend
。这里只讲述通用版本的函数。
- 队列的结构
- 队列的创建
- 向队列中添加数据
- 从队列中获取数据
- 队列的锁定与解锁
- 中断中队列数据的处理
- 队列集
1.队列的结构
typedef struct QueueDefinition{ signed char *pcHead; signed char *pcTail; signed char *pcWriteTo; signed char *pcReadFrom; xList xTasksWaitingToSend; xList xTasksWaitingToReceive; volatile unsigned portBASE_TYPE uxMessagesWaiting; unsigned portBASE_TYPE uxLength; unsigned portBASE_TYPE uxItemSize; volatile signed portBASE_TYPE xRxLock; volatile signed portBASE_TYPE xTxLock; #if ( configUSE_TRACE_FACILITY == 1 ) unsigned char ucQueueNumber; unsigned char ucQueueType; #endif #if ( configUSE_QUEUE_SETS == 1 ) struct QueueDefinition *pxQueueSetContainer; #endif} xQUEUE;
- 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
xTasksWaitingToSend
:如果队列存储区中尚有空间时,向队列中加入数据,可以立刻存入,但是如果队列已经满了,而任务还想向队列中存储数据,就会将队列挂接到 xTasksWaitingToSend
上(前提是任务愿意等待),然后等待队列有空间时再将数据添加到队列中。
xTasksWaitingToReceive
:如果队列存储区有数据时,从队列中读取数据,可以立刻获取,但是如果队列为空,而任务还想从队列中获取数据,就会将队列挂接到 xTasksWaitingToReceive
上(前提是任务愿意等待),然后等待队列有数据时再将从队列中获取数据。
同时请注意 pcWriteTo
和 pcReadFrom
所指位置的差异。
2.队列的创建
在讲述队列的创建之前,先讲述一个队列复位函数
portBASE_TYPE xQueueGenericReset( xQueueHandle xQueue, portBASE_TYPE xNewQueue ){xQUEUE *pxQueue; pxQueue = ( xQUEUE * ) xQueue; configASSERT( pxQueue ); taskENTER_CRITICAL(); { pxQueue->pcTail = pxQueue->pcHead + ( pxQueue->uxLength * pxQueue->uxItemSize ); pxQueue->uxMessagesWaiting = ( unsigned portBASE_TYPE ) 0U; pxQueue->pcWriteTo = pxQueue->pcHead; pxQueue->pcReadFrom = pxQueue->pcHead + ( ( pxQueue->uxLength - ( unsigned portBASE_TYPE ) 1U ) * pxQueue->uxItemSize ); pxQueue->xRxLock = queueUNLOCKED; pxQueue->xTxLock = queueUNLOCKED; if( xNewQueue == pdFALSE ) { if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE ) { if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) == pdTRUE ) { portYIELD_WITHIN_API(); } } } else { vListInitialise( &( pxQueue->xTasksWaitingToSend ) ); vListInitialise( &( pxQueue->xTasksWaitingToReceive ) ); } } taskEXIT_CRITICAL(); return pdPASS;}
- 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
此函数的两个参数,第一个是需要复位的队列,第二个参数是当前队列是否是新队列,如果是新队列则需要初始化队列中的链表。如果是对已有队列进行复位则需要检查是否有任务正在等待向队列中发送数据,如果有则将将任务从等待链表中移除,同时进行任务调度。
xQueueHandle xQueueGenericCreate( unsigned portBASE_TYPE uxQueueLength, unsigned portBASE_TYPE uxItemSize, unsigned char ucQueueType ){ xQUEUE *pxNewQueue; size_t xQueueSizeInBytes; xQueueHandle xReturn = NULL; ( void ) ucQueueType; if( uxQueueLength > ( unsigned portBASE_TYPE ) 0 ) { pxNewQueue = ( xQUEUE * ) pvPortMalloc( sizeof( xQUEUE ) ); if( pxNewQueue != NULL ) { xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ) + ( size_t ) 1; pxNewQueue->pcHead = ( signed char * ) pvPortMalloc( xQueueSizeInBytes ); if( pxNewQueue->pcHead != NULL ) { pxNewQueue->uxLength = uxQueueLength; pxNewQueue->uxItemSize = uxItemSize; xQueueGenericReset( pxNewQueue, pdTRUE ); #if ( configUSE_TRACE_FACILITY == 1 ) { pxNewQueue->ucQueueType = ucQueueType; } #endif /* configUSE_TRACE_FACILITY */ #if( configUSE_QUEUE_SETS == 1 ) { pxNewQueue->pxQueueSetContainer = NULL; } #endif /* configUSE_QUEUE_SETS */ traceQUEUE_CREATE( pxNewQueue ); xReturn = pxNewQueue; } else { traceQUEUE_CREATE_FAILED( ucQueueType ); vPortFree( pxNewQueue ); } } } configASSERT( xReturn ); return xReturn;}
- 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
此函数包含3个参数:
uxQueueLength
: 队列项的个数
uxItemSize
: 队列项的尺寸
ucQueueType
: 队列的类型,没有用到
FreeRTOS的队列不止可以插入基本普通数据类型的数据(例如int char),还可以存储高级数据类型数据(例如:结构体、队列指针)。
队列创建过程中既需要给存储队列的数据区申请空间 uxQueueLength
* uxItemSize
+1),还需要给队列结构体本身申请空间,如果空间申请成功,则返回队列指针,如果不成功,则返回 NULL
。
创建和完成初始化的队列如下图:
3.向队列中添加数据
将数据添加到队列的代码如下:
static void prvCopyDataToQueue( xQUEUE *pxQueue, const void *pvItemToQueue, portBASE_TYPE xPosition ){ if( pxQueue->uxItemSize == ( unsigned portBASE_TYPE ) 0 ) { #if ( configUSE_MUTEXES == 1 ) { if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ) { vTaskPriorityDisinherit( ( void * ) pxQueue->pxMutexHolder ); pxQueue->pxMutexHolder = NULL; } } #endif } else if( xPosition == queueSEND_TO_BACK ) { memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue, ( unsigned ) pxQueue->uxItemSize ); pxQueue->pcWriteTo += pxQueue->uxItemSize; if( pxQueue->pcWriteTo >= pxQueue->pcTail ) { pxQueue->pcWriteTo = pxQueue->pcHead; } } else { memcpy( ( void * ) pxQueue->pcReadFrom, pvItemToQueue, ( unsigned ) pxQueue->uxItemSize ); pxQueue->pcReadFrom -= pxQueue->uxItemSize; if( pxQueue->pcReadFrom < pxQueue->pcHead ) { pxQueue->pcReadFrom = ( pxQueue->pcTail - pxQueue->uxItemSize ); } } ++( pxQueue->uxMessagesWaiting ); }
- 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
此函数包含3个参数:
pxQueue
: 拷入数据的目的队列
pvItemToQueue
: 需要拷入队列的数据
xPosition
: 插入队列的位置 结尾或者开头。
上面函数中用到一个函数 memcpy
,作用是拷贝数据,它的第一个参数是目的存储区,这里我们就可以理解为什么 pcWriteTo
要指向下次数据写入位置了,因为当写入数据到队列结尾时, pcWriteTo
直接带入函数就可以。当需要插入队列Head位置时,就是插入到上次读取数据的位置 pcReadFrom
。这样就会方便很多。
下图展示了向队列开头和结尾添加数据的不同:
插入数据头部(插入前)
插入数据头部(插入后)
插入数据尾部(插入前)
插入数据尾部(插入后)
向队列中添加数据不只是向数据区拷贝数据这样简单,还需要考虑队列满时,该如何添加数据,还要考虑如果有任务正在等待从队列中读取数据等情况,FreeRTOS使用下面的函数来处理向队列中添加数据过程中牵扯到的问题,如下:
signed portBASE_TYPE xQueueGenericSend( xQueueHandle xQueue, const void * const pvItemToQueue, portTickType xTicksToWait, portBASE_TYPE xCopyPosition ){ signed portBASE_TYPE xEntryTimeSet = pdFALSE; xTimeOutType xTimeOut; xQUEUE *pxQueue; pxQueue = ( xQUEUE * ) xQueue; configASSERT( pxQueue ); configASSERT( !( ( pvItemToQueue == NULL ) && ( pxQueue->uxItemSize != ( unsigned portBASE_TYPE ) 0U ) ) ); for( ;; ) { taskENTER_CRITICAL(); { if( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) { traceQUEUE_SEND( pxQueue ); prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition ); #if ( configUSE_QUEUE_SETS == 1 ) { if( pxQueue->pxQueueSetContainer != NULL ) { if( prvNotifyQueueSetContainer( pxQueue, xCopyPosition ) == pdTRUE ) { portYIELD_WITHIN_API(); } } else { if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ) { if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) == pdTRUE ) { portYIELD_WITHIN_API(); } } } } #else /* configUSE_QUEUE_SETS */ { if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ) { if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) == pdTRUE ) { portYIELD_WITHIN_API(); } } } #endif /* configUSE_QUEUE_SETS */ taskEXIT_CRITICAL(); return pdPASS; } else { if( xTicksToWait == ( portTickType ) 0 ) { taskEXIT_CRITICAL(); traceQUEUE_SEND_FAILED( pxQueue ); return errQUEUE_FULL; } else if( xEntryTimeSet == pdFALSE ) { vTaskSetTimeOutState( &xTimeOut ); xEntryTimeSet = pdTRUE; } } } taskEXIT_CRITICAL(); vTaskSuspendAll(); prvLockQueue( pxQueue ); if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ) { if( prvIsQueueFull( pxQueue ) != pdFALSE ) { traceBLOCKING_ON_QUEUE_SEND( pxQueue ); vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait ); prvUnlockQueue( pxQueue ); if( xTaskResumeAll() == pdFALSE ) { portYIELD_WITHIN_API(); } } else { prvUnlockQueue( pxQueue ); ( void ) xTaskResumeAll(); } } else { prvUnlockQueue( pxQueue ); ( void ) xTaskResumeAll(); traceQUEUE_SEND_FAILED( pxQueue ); return errQUEUE_FULL; } }}
- 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
这个函数的总体目的就是如果队列有空间,将数据加入队列,如果没有空间且有阻塞时间,则加入等待发送链表。数据直接加入队列看一下注释就不讲述了,这里解释一下如果队列满了之后,向队列添加数据的处理方法。
我们以一个向满列中添加数据,解释一下上述过程:
- 调用下面函数函数
vTaskSetTimeOutState( &xTimeOut )
记录尝试向队列加入数据的时间TICK,代码如下:
else if( xEntryTimeSet == pdFALSE ) { vTaskSetTimeOutState( &xTimeOut ); xEntryTimeSet = pdTRUE; }
因为队列是满列,则会调用 vTaskPlaceOnEventList
将任务从就绪列表中移除,添加到等待发送列表中,同时会进行一次任务调度,此时此函数不再继续执行。代码如下:
if( prvIsQueueFull( pxQueue ) != pdFALSE ){ traceBLOCKING_ON_QUEUE_SEND( pxQueue ); vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait ); prvUnlockQueue( pxQueue ); if( xTaskResumeAll() == pdFALSE ) { portYIELD_WITHIN_API(); }}
这段代码重新能够得到执行的条件是阻塞时间到或者有其他任务从队列中取出了数据,当前队列不为空,假如阻塞时间到,会执行下面代码返回一个向队列满错误 errQUEUE_FULL
。
else { prvUnlockQueue( pxQueue ); ( void ) xTaskResumeAll(); traceQUEUE_SEND_FAILED( pxQueue ); return errQUEUE_FULL; }
如果是因为队列有数据移除,当前已经不是满列,则执行下面代码。
else { prvUnlockQueue( pxQueue ); ( void ) xTaskResumeAll(); }
函数会执行一个死循环 for(;;)
,我们可以看到除非阻塞时间到或者将数据成功加入到队列,这个函数会一直执行,于是在上面代码执行完成之后又会从循环开始的地方重新执行,因为此时队列不再是满列,于是数据顺利的通过如下代码添加到队列中,同时退出循环:
if( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) { traceQUEUE_SEND( pxQueue ); prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition ); ... ... #else /* configUSE_QUEUE_SETS */ { if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ) { if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) == pdTRUE ) { portYIELD_WITHIN_API(); } } } #endif /* configUSE_QUEUE_SETS */ taskEXIT_CRITICAL(); return pdPASS; }
- 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
通过上面代码的分析,我们可以看到,无论是满列还是非满列,数据入列的过程都是通过这个函数执行,只不过,如果队列有空间,则直接添加数据,如果队列没有空间,则先将队列加入一个等待列表,待队列有空间时,在由其他任务或者中断将此任务从等待列表中移除,接着运行,继续向队列中添加数据。
4.从队列中获取数据
signed portBASE_TYPE xQueueGenericReceive( xQueueHandle xQueue, void * const pvBuffer, portTickType xTicksToWait, portBASE_TYPE xJustPeeking ){ signed portBASE_TYPE xEntryTimeSet = pdFALSE; xTimeOutType xTimeOut; signed char *pcOriginalReadPosition; xQUEUE *pxQueue; pxQueue = ( xQUEUE * ) xQueue; configASSERT( pxQueue ); configASSERT( !( ( pvBuffer == NULL ) && ( pxQueue->uxItemSize != ( unsigned portBASE_TYPE ) 0U ) ) ); for( ;; ) { taskENTER_CRITICAL(); { if( pxQueue->uxMessagesWaiting > ( unsigned portBASE_TYPE ) 0 ) { pcOriginalReadPosition = pxQueue->pcReadFrom; prvCopyDataFromQueue( pxQueue, pvBuffer ); if( xJustPeeking == pdFALSE ) { traceQUEUE_RECEIVE( pxQueue ); --( pxQueue->uxMessagesWaiting ); #if ( configUSE_MUTEXES == 1 ) { if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ) { pxQueue->pxMutexHolder = xTaskGetCurrentTaskHandle(); } } #endif if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE ) { if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) == pdTRUE ) { portYIELD_WITHIN_API(); } } } else { traceQUEUE_PEEK( pxQueue ); pxQueue->pcReadFrom = pcOriginalReadPosition; if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ) { if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ) { portYIELD_WITHIN_API(); } } } taskEXIT_CRITICAL(); return pdPASS; } else { if( xTicksToWait == ( portTickType ) 0 ) {/ taskEXIT_CRITICAL(); traceQUEUE_RECEIVE_FAILED( pxQueue ); return errQUEUE_EMPTY; } else if( xEntryTimeSet == pdFALSE ) { vTaskSetTimeOutState( &xTimeOut ); xEntryTimeSet = pdTRUE; } } } 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 ) { portENTER_CRITICAL(); { vTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder ); } portEXIT_CRITICAL(); } } #endif vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait ); prvUnlockQueue( pxQueue ); if( xTaskResumeAll() == pdFALSE ) { portYIELD_WITHIN_API(); } } else { prvUnlockQueue( pxQueue ); ( void ) xTaskResumeAll(); } } else { prvUnlockQueue( pxQueue ); ( void ) xTaskResumeAll(); traceQUEUE_RECEIVE_FAILED( pxQueue ); return errQUEUE_EMPTY; } }}
- 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
从上面代码可以看到,整体思路与向队列中写数据是一致的,这里就不再讲述了,看上述代码注释即可。
从队列中获取数据涉及到的出列过程如下图:
读取数据前
读取数据后
5.中断中队列数据的处理
signed portBASE_TYPE xQueueGenericSendFromISR( xQueueHandle xQueue, const void * const pvItemToQueue, signed portBASE_TYPE *pxHigherPriorityTaskWoken, portBASE_TYPE xCopyPosition ){signed portBASE_TYPE xReturn;unsigned portBASE_TYPE uxSavedInterruptStatus;xQUEUE *pxQueue; pxQueue = ( xQUEUE * ) xQueue; configASSERT( pxQueue ); configASSERT( !( ( pvItemToQueue == NULL ) && ( pxQueue->uxItemSize != ( unsigned portBASE_TYPE ) 0U ) ) ); uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR(); { if( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) { traceQUEUE_SEND_FROM_ISR( pxQueue ); prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition ); if( pxQueue->xTxLock == queueUNLOCKED ) { #if ( configUSE_QUEUE_SETS == 1 ) { if( pxQueue->pxQueueSetContainer != NULL ) { if( prvNotifyQueueSetContainer( pxQueue, xCopyPosition ) == pdTRUE ) { if( pxHigherPriorityTaskWoken != NULL ) { *pxHigherPriorityTaskWoken = pdTRUE; } } } else { if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ) { if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ) { if( pxHigherPriorityTaskWoken != NULL ) { *pxHigherPriorityTaskWoken = pdTRUE; } } } } } #else /* configUSE_QUEUE_SETS */ { if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ) { if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ) { if( pxHigherPriorityTaskWoken != NULL ) { *pxHigherPriorityTaskWoken = pdTRUE; } } } } #endif /* configUSE_QUEUE_SETS */ } else { ++( pxQueue->xTxLock ); } xReturn = pdPASS; } else { traceQUEUE_SEND_FROM_ISR_FAILED( pxQueue ); xReturn = errQUEUE_FULL; } } portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus ); return xReturn;}
- 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
从上面代码我们可以看到,与通用添加函数不同,中断中添加数据不能进入阻塞状态,也就是如果队列有空间,则立刻添加进去,如果没有空间,则返回队列满错误。同时如果队列处于发送数据锁定状态,则需要进行记录,以便退出中断后,能够对等待发送链表进行更新。如果队列没有锁定,则立刻更新等待发送链表,但是即使移除的任务比当前任务优先级高,也不会进行立刻调度,而是更新· *pxHigherPriorityTaskWoken = pdTRUE;
记录有需要调度, 然后在中断中调用 taskYIELD_YIELD_FROM_ISR()
进行调度。
signed portBASE_TYPE xQueueReceiveFromISR( xQueueHandle xQueue, void * const pvBuffer, signed portBASE_TYPE *pxHigherPriorityTaskWoken ){signed portBASE_TYPE xReturn;unsigned portBASE_TYPE uxSavedInterruptStatus;xQUEUE *pxQueue; pxQueue = ( xQUEUE * ) xQueue; configASSERT( pxQueue ); configASSERT( !( ( pvBuffer == NULL ) && ( pxQueue->uxItemSize != ( unsigned portBASE_TYPE ) 0U ) ) ); uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR(); { if( pxQueue->uxMessagesWaiting > ( unsigned portBASE_TYPE ) 0 ) { traceQUEUE_RECEIVE_FROM_ISR( pxQueue ); prvCopyDataFromQueue( pxQueue, pvBuffer ); --( pxQueue->uxMessagesWaiting ); if( pxQueue->xRxLock == queueUNLOCKED ) { if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE ) { if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE ) { if( pxHigherPriorityTaskWoken != NULL ) { *pxHigherPriorityTaskWoken = pdTRUE; } } } } else { ++( pxQueue->xRxLock ); } xReturn = pdPASS; } else { xReturn = pdFAIL; traceQUEUE_RECEIVE_FROM_ISR_FAILED( pxQueue ); } } portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus ); return xReturn;}
- 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
这个函数与通用函数的差异,与从中断接收数据函数类似,这里就不讲述了。
6.队列的锁定与解锁
#define prvLockQueue( pxQueue ) \ taskENTER_CRITICAL(); \ { \ if( ( pxQueue )->xRxLock == queueUNLOCKED ) \ { \ ( pxQueue )->xRxLock = queueLOCKED_UNMODIFIED; \ } \ if( ( pxQueue )->xTxLock == queueUNLOCKED ) \ { \ ( pxQueue )->xTxLock = queueLOCKED_UNMODIFIED; \ } \ } \ taskEXIT_CRITICAL()
队列锁定使用一个宏来实现,很简单,就是将 xTxLock
和 xRxLock
都设定为锁定状态,也就是值由-1改成0
队列锁定之后,需要使用队列解锁函数来进行,队列锁定期间,可能发生的数据入列和出列事件进行处理。函数如下:
static void prvUnlockQueue( xQUEUE *pxQueue ){ taskENTER_CRITICAL(); { while( pxQueue->xTxLock > queueLOCKED_UNMODIFIED ) { #if ( configUSE_QUEUE_SETS == 1 ) { if( pxQueue->pxQueueSetContainer != NULL ) { if( prvNotifyQueueSetContainer( pxQueue, queueSEND_TO_BACK ) == pdTRUE ) { vTaskMissedYield(); } } else { if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ) { if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ) { vTaskMissedYield(); } } else { break; } } } #else /* configUSE_QUEUE_SETS */ { if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ) { if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ) { vTaskMissedYield(); } } else { break; } } #endif /* configUSE_QUEUE_SETS */ --( pxQueue->xTxLock ); } pxQueue->xTxLock = queueUNLOCKED; } taskEXIT_CRITICAL(); taskENTER_CRITICAL(); { while( pxQueue->xRxLock > queueLOCKED_UNMODIFIED ) { if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE ) { if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE ) { vTaskMissedYield(); } --( pxQueue->xRxLock ); } else { break; } } pxQueue->xRxLock = queueUNLOCKED; } taskEXIT_CRITICAL();}
- 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
解锁队列函数除了去除任务锁定状态外,还有一个任务就是看看是否有中断向队列中发送或者读取数据,如果有,则更新两个链表,并且如果需要调度,更新一个变量 xMissedYield
。从之前的代码我们可以看到,通用的发送和接收数据代码,并不关注队列的锁定,队列锁定是专为中断中发送和接收数据而设的,因此在任务进行操作之前先要挂起任务调度,然后锁定队列,在解除队列锁定之后,必须调用 xTaskResumeAll()
解除任务调度的锁定,同时 xTaskResumeAll()
函数还会根据 xMissedYield
来决定是否需要进行调度。
7.队列集
首先队列集也是一个队列,只是其存储区放置的是队列的指针,队列集的作用是让一个任务可以同时从几个队列中获取数据或者阻塞在其中等待数据。队列集是很少使用,这里也讲述一下。
队列的创建 我们可以看到,队列集与普通队列创建方法是一样的,只是队列是队列指针,注意队列集的数据个数 uxEventQueueLength
必须设置为为所有被包含队列的数据个数总和,这一点后面讲述
xQueueSetHandle xQueueCreateSet( unsigned portBASE_TYPE uxEventQueueLength ) { xQueueSetHandle pxQueue; pxQueue = xQueueGenericCreate( uxEventQueueLength, sizeof( xQUEUE * ), queueQUEUE_TYPE_SET ); return pxQueue; }
向队列集中添加队列 仅仅是将队列的 pxQueueSetContainer
设置为当前队列集,这样后面就可以通过 pxQueueSetContainer
来判断和获取队列集了。
portBASE_TYPE xQueueAddToSet( xQueueSetMemberHandle xQueueOrSemaphore, xQueueSetHandle xQueueSet ) { portBASE_TYPE xReturn; if( ( ( xQUEUE * ) xQueueOrSemaphore )->pxQueueSetContainer != NULL ) { xReturn = pdFAIL; } else { taskENTER_CRITICAL(); { ( ( xQUEUE * ) xQueueOrSemaphore )->pxQueueSetContainer = xQueueSet; } taskEXIT_CRITICAL(); xReturn = pdPASS; } return xReturn; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
并没有一个专门函数处理向队列集中发送数据,而是当任务使用 xQueueGenericSend
向属于队列集中队列发送数据时会调用 prvNotifyQueueSetContainer
函数向队列集发送通知消息、以便将队列放入到队列集存储区中、检查是否有任务阻塞在队列集等待接收数据链表,如果有则移除。我们可以看到如果一个属于队列集的队列每次收到数据都会将其存储到队列集中。我们可能会注意到一个现象,并没有对队列集满情况进行处理进行处理,这就要求我们将队列集长度设置为所有被包含队列的数据长度总和.
static portBASE_TYPE prvNotifyQueueSetContainer( xQUEUE *pxQueue, portBASE_TYPE xCopyPosition ) { xQUEUE *pxQueueSetContainer = pxQueue->pxQueueSetContainer; portBASE_TYPE xReturn = pdFALSE; configASSERT( pxQueueSetContainer ); configASSERT( pxQueueSetContainer->uxMessagesWaiting < pxQueueSetContainer->uxLength ); if( pxQueueSetContainer->uxMessagesWaiting < pxQueueSetContainer->uxLength ) { traceQUEUE_SEND( pxQueueSetContainer ); prvCopyDataToQueue( pxQueueSetContainer, &pxQueue, xCopyPosition ); if( listLIST_IS_EMPTY( &( pxQueueSetContainer->xTasksWaitingToReceive ) ) == pdFALSE ) { if( xTaskRemoveFromEventList( &( pxQueueSetContainer->xTasksWaitingToReceive ) ) != pdFALSE ) { xReturn = pdTRUE; } } } return xReturn; }
- 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
从队列集中获取一个队列,使用的是通用的接收方法,我们可以看到它返回的是一个队列,而并不返回相应队列的值,也就是还需要使用 xQueueReceive
从返回的队列中读取数据,而且我们不能使用 xQueueReceive从
一个属于队列集的队列中直接获取数据 ,必须先调用 xQueueSelectFromSet
,然后再调用 xQueueReceive
获取队列中的数据。
xQueueSetMemberHandle xQueueSelectFromSet( xQueueSetHandle xQueueSet, portTickType xBlockTimeTicks ){xQueueSetMemberHandle xReturn = NULL; xQueueGenericReceive( ( xQueueHandle ) xQueueSet, &xReturn, xBlockTimeTicks, pdFALSE ); return xReturn;}
1、创建队列集2、将队列加入队列集3、当队列集中的队列有数据存入时,就会将队列存入队列集的数据存储区中,并且更新等待接收链表4、需要从任务集中获取数据的任务调用xQueueSelectFromSet,查看和获取队列集的数据存储区中的队列数据5、再使用xQueueReceive从上步获取的队列中获取数据
例子来自官网,代码如下:
#define QUEUE_LENGTH_1 10#define QUEUE_LENGTH_2 10#define BINARY_SEMAPHORE_LENGTH 1#define ITEM_SIZE_QUEUE_1 sizeof( uint32_t )#define ITEM_SIZE_QUEUE_2 sizeof( something_else_t )#define COMBINED_LENGTH ( QUEUE_LENGTH_1 + QUEUE_LENGTH_2 + BINARY_SEMAPHORE_LENGTH )void vAFunction( void ){static QueueSetHandle_t xQueueSet;QueueHandle_t xQueue1, xQueue2, xSemaphore;QueueSetMemberHandle_t xActivatedMember;uint32_t xReceivedFromQueue1;something_else_t xReceivedFromQueue2; xQueueSet = xQueueCreateSet( COMBINED_LENGTH ); xQueue1 = xQueueCreate( QUEUE_LENGTH_1, ITEM_SIZE_QUEUE_1 ); xQueue2 = xQueueCreate( QUEUE_LENGTH_2, ITEM_SIZE_QUEUE_2 ); xSemaphore = xSemaphoreCreateBinary(); configASSERT( xQueueSet ); configASSERT( xQueue1 ); configASSERT( xQueue2 ); configASSERT( xSemaphore ); xQueueAddToSet( xQueue1, xQueueSet ); xQueueAddToSet( xQueue2, xQueueSet ); xQueueAddToSet( xSemaphore, xQueueSet ); for( ;; ) { xActivatedMember = xQueueSelectFromSet( xQueueSet, 200 / portTICK_PERIOD_MS ); if( xActivatedMember == xQueue1 ) { xQueueReceive( xActivatedMember, &xReceivedFromQueue1, 0 ); vProcessValueFromQueue1( xReceivedFromQueue1 ); } else if( xActivatedMember == xQueue2 ) { xQueueReceive( xActivatedMember, &xReceivedFromQueue2, 0 ); vProcessValueFromQueue2( &xReceivedFromQueue2 ); } else if( xActivatedMember == xSemaphore ) { xSemaphoreTake( xActivatedMember, 0 ); vProcessEventNotifiedBySemaphore(); break; } else { } }}
- 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