Вопросы и ответы
Кто-то однажды спросил меня, как запомнить так много API во FreeRTOS? Я хотел бы сказать, что API не сложно запомнить, но его немного сложно найти, потому что многие API FreeRTOS имеют макросы параметров, поэтому прыгать по ним сложнее, а также много комментариев. найти не так просто, но тоже ничего, обычно есть мануал по API, скажу я вам:FreeRTOS Kernel: Reference ManualЯдро FreeRTOS: Справочное руководство, вы можете найти его по адресуЗагрузка с официального сайта, также доступный в фоновом режиме. Конечно, книга на английском языке.Если вы не так хорошо владеете английским языком, как я, вы можете использовать Google Chrome, чтобы прочитать руководство по API прямо на официальном сайте и перевести его напрямую. Портал:www.freertos.org/a00018.html
Очередь сообщений FreeRTOS
Приложение на основе FreeRTOS состоит из набора независимых задач — каждая задача представляет собой программу с независимыми разрешениями. Связь и синхронизация между этими независимыми задачами обычно основаны на механизме связи IPC, предоставляемом операционной системой, а все механизмы связи и синхронизации в FreeRTOS реализованы на основе очередей. Очередь сообщений — это структура данных, обычно используемая для связи между задачами.Очередь может передавать информацию между задачами, прерываниями и задачами, а также позволяет задачам получать сообщения переменной длины от других задач или прерываний. Задача может читать сообщения из очереди.Когда сообщение в очереди пусто, задача чтения приостанавливается, и пользователь также может указать время приостановленной задачи, когда в очереди появляются новые сообщения, задача приостановленного чтения пробуждается. А для обработки новых сообщений очереди сообщений представляют собой асинхронный способ связи.
характеристики очереди
1. Хранение данных
Очередь может содержать конечное число единиц данных определенной длины. Максимальное количество ячеек, которое может содержать очередь, называется «глубиной» очереди. Глубина и размер каждой ячейки должны быть установлены при создании очереди. Обычно очередь используется как буфер FIFO (first in, first out), то есть данные записываются с конца очереди и считываются с начала очереди. Конечно, запись из головы очереди тоже возможна. Запись данных в очередь заключается в копировании и сохранении данных в очереди посредством копирования байтов; чтение данных из очереди удаляет копию данных в очереди.
2. Блокировка чтения
Когда задача пытается прочитать очередь, она может указать тайм-аут блокировки. В течение этого времени, если очередь пуста, задача будет оставаться заблокированной, ожидая, пока данные очереди станут доступными. Когда другие задачи или программы обработки прерываний записывают данные в очередь ожидания, задача автоматически переходит из состояния блокировки в состояние готовности. Когда время ожидания превышает указанное время блокировки, даже если в очереди нет допустимых данных, задача автоматически перейдет из состояния блокировки в состояние готовности. Поскольку очередь может быть прочитана несколькими задачами, для одной очереди также может быть заблокировано несколько задач, ожидающих доступности данных очереди. В этом случае, как только данные очереди станут действительными, будет разблокирована только одна задача, и эта задача является задачей с наивысшим приоритетом среди всех ожидающих задач. И если все ожидающие задачи имеют одинаковый приоритет, разблокированной будет та задача, которая ждала дольше всех.
Кстати, у ucos есть широковещательные сообщения, когда в очереди заблокировано несколько задач, вы можете выбрать широковещательные сообщения при отправке сообщений, тогда эти заблокированные задачи можно будет разблокировать.
3. Блокировка записи
В отличие от блокировки чтения, задачи также могут указывать тайм-аут блокировки при записи в очередь. Это время является максимальным временем, в течение которого задача переходит в состояние блокировки, чтобы ожидать освобождения места в очереди, когда очередь записи заполнена. Поскольку записи в очереди могут выполняться несколькими задачами, для одной очереди также может быть заблокировано несколько задач, ожидающих освобождения места в очереди. В этом случае, как только освободится место в очереди, будет разблокирована только одна задача, и эта задача является задачей с наивысшим приоритетом среди всех ожидающих задач. И если все ожидающие задачи имеют одинаковый приоритет, разблокированной будет та задача, которая ждала дольше всех.
Рабочий процесс очереди сообщений
1. Отправить сообщение
Задачи или подпрограммы обслуживания прерываний могут отправлять сообщения в очередь сообщений.При отправке сообщения, если очередь не заполнена или разрешена перезапись очереди, FreeRTOS скопирует сообщение в конец очереди сообщений.В противном случае оно будет выполняется в соответствии с тайм-аутом блокировки, указанным пользователем.Блокировка, в течение этого времени, если очередь не была допущена в очередь, задача останется заблокированной, ожидая, пока очереди будет разрешено войти в очередь. Когда другие задачи считывают данные из своей очереди ожидания (очередь не заполнена), задача автоматически переходит из состояния блокировки в состояние готовности. Когда время ожидания задачи превышает указанное время блокировки, даже если очереди не разрешен вход в очередь, задача автоматически перейдет из состояния блокировки в состояние готовности.В это время задача или программа прерывания, которая отправляет сообщение получит код ошибки errQUEUE_FULL. Процесс отправки срочного сообщения почти такой же, как и отправка сообщения, с той лишь разницей, что при отправке срочного сообщения оно отправляется в начало очереди сообщений, а не в конец очереди, так что получатель может получить срочное сообщение первым, чтобы его можно было отправить вовремя. Ниже приведен API-интерфейс отправки очереди сообщений.Функция FromISR в функции указывает, что она используется в прерывании.
1 /*-----------------------------------------------------------*/
2 BaseType_t xQueueGenericSend( QueueHandle_t xQueue, (1)
3 const void * const pvItemToQueue, (2)
4 TickType_t xTicksToWait, (3)
5 const BaseType_t xCopyPosition ) (4)
6 {
7 BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired;
8 TimeOut_t xTimeOut;
9 Queue_t * const pxQueue = ( Queue_t * ) xQueue;
10
11 /* 已删除一些断言操作 */
12
13 for ( ;; ) {
14 taskENTER_CRITICAL(); (5)
15 {
16 /* 队列未满 */
17 if ( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength )
18 || ( xCopyPosition == queueOVERWRITE ) ) { (6)
19 traceQUEUE_SEND( pxQueue );
20 xYieldRequired =
21 prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition ); (7)
22
23 /* 已删除使用队列集部分代码 */
24 /* 如果有任务在等待获取此消息队列 */
25 if ( listLIST_IS_EMPTY(&(pxQueue->xTasksWaitingToReceive))==pdFALSE){ (8)
26 /* 将任务从阻塞中恢复 */
27 if ( xTaskRemoveFromEventList(
28 &( pxQueue->xTasksWaitingToReceive ) )!=pdFALSE) { (9)
29 /* 如果恢复的任务优先级比当前运行任务优先级还高,
30 那么需要进行一次任务切换 */
31 queueYIELD_IF_USING_PREEMPTION(); (10)
32 } else {
33 mtCOVERAGE_TEST_MARKER();
34 }
35 } else if ( xYieldRequired != pdFALSE ) {
36 /* 如果没有等待的任务,拷贝成功也需要任务切换 */
37 queueYIELD_IF_USING_PREEMPTION(); (11)
38 } else {
39 mtCOVERAGE_TEST_MARKER();
40 }
41
42 taskEXIT_CRITICAL(); (12)
43 return pdPASS;
44 }
45 /* 队列已满 */
46 else { (13)
47 if ( xTicksToWait == ( TickType_t ) 0 ) {
48 /* 如果用户不指定阻塞超时时间,退出 */
49 taskEXIT_CRITICAL(); (14)
50 traceQUEUE_SEND_FAILED( pxQueue );
51 return errQUEUE_FULL;
52 } else if ( xEntryTimeSet == pdFALSE ) {
53 /* 初始化阻塞超时结构体变量,初始化进入
54 阻塞的时间xTickCount和溢出次数xNumOfOverflows */
55 vTaskSetTimeOutState( &xTimeOut ); (15)
56 xEntryTimeSet = pdTRUE;
57 } else {
58 mtCOVERAGE_TEST_MARKER();
59 }
60 }
61 }
62 taskEXIT_CRITICAL(); (16)
63 /* 挂起调度器 */
64 vTaskSuspendAll();
65 /* 队列上锁 */
66 prvLockQueue( pxQueue );
67
68 /* 检查超时时间是否已经过去了 */
69 if (xTaskCheckForTimeOut(&xTimeOut, &xTicksToWait)==pdFALSE){ (17)
70 /* 如果队列还是满的 */
71 if ( prvIsQueueFull( pxQueue ) != pdFALSE ) { (18)
72 traceBLOCKING_ON_QUEUE_SEND( pxQueue );
73 /* 将当前任务添加到队列的等待发送列表中
74 以及阻塞延时列表,延时时间为用户指定的超时时间xTicksToWait */
75 vTaskPlaceOnEventList(
76 &( pxQueue->xTasksWaitingToSend ), xTicksToWait );(19)
77 /* 队列解锁 */
78 prvUnlockQueue( pxQueue ); (20)
79
80 /* 恢复调度器 */
81 if ( xTaskResumeAll() == pdFALSE ) {
82 portYIELD_WITHIN_API();
83 }
84 } else {
85 /* 队列有空闲消息空间,允许入队 */
86 prvUnlockQueue( pxQueue ); (21)
87 ( void ) xTaskResumeAll();
88 }
89 } else {
90 /* 超时时间已过,退出 */
91 prvUnlockQueue( pxQueue ); (22)
92 ( void ) xTaskResumeAll();
93
94 traceQUEUE_SEND_FAILED( pxQueue );
95 return errQUEUE_FULL;
96 }
97 }
98 }
99 /*-----------------------------------------------------------*/
Если время блокировки не равно 0, задача будет заблокирована, поскольку она ожидает постановки в очередь. поставить в очередь, так как это может привести к разблокировке других задач, что может вызвать инверсию приоритета. Например, приоритет задачи А ниже, чем у текущей задачи, но когда текущая задача входит в процесс блокировки, задача А разблокируется по другим причинам, что, очевидно, категорически запрещено. Поэтому FreeRTOS использует ожидающий планировщик, чтобы запретить другим задачам работать с очередью, потому что ожидающий планировщик означает, что задачи не могут быть переключены и им не разрешено вызывать функции API, которые могут вызвать переключение задач. Однако приостановка работы планировщика не запрещает прерывания.Функция обслуживания прерываний по-прежнему может управлять списком блокировки очереди, что может разблокировать задачи и выполнять переключение контекста, что также не разрешено. тогда,Решение FreeRTOS состоит в том, чтобы не только приостановить планировщик, но также заблокировать очередь и отключить любые прерывания для работы с очередью.Давайте посмотрим на блок-схему:По сравнению с функцией отправки, вызываемой в задаче, функция, вызываемая в прерывании, будет проще, и в ней нет операции блокировки задачи. После того, как данные будут вставлены в функцию xQueueGenericSend, она проверит, есть ли задачи, ожидающие получения в списке ожидания, и если да, то возобновит готовность. Если приоритет восстановленной задачи выше, чем у текущей задачи, сработает переключение задачи, однако метод этой функции, вызываемой в прерывании, состоит в том, чтобы вернуть флаг параметра, нужно ли срабатывать переключение задачи, и задача будет не переключаться в прерывании. В функции, вызываемой в задаче, есть операции для блокировки и разблокировки очереди.Когда очередь заблокирована, список событий очереди не может быть изменен. Обработка отправки сообщения в прерванном состоянии: когда очередь заблокирована, после вставки новых данных в очередь она не будет напрямую возобновлять задачу, ожидающую получения, а накапливать счетчик, когда очередь разблокирована, будет восстановить несколько задач по этому счету. Когда очередь заполнена, функция вернется напрямую, вместо блокировки и ожидания, потому что блокировка в прерываниях не разрешена! ! !
1 BaseType_t xQueueGenericSendFromISR(
2 QueueHandle_t xQueue,
3 const void * const pvItemToQueue,
4 /* 不在中断函数中触发任务切换, 而是返回一个标记 */
5 BaseType_t * const pxHigherPriorityTaskWoken,
6 const BaseType_t xCopyPosition )
7{
8 BaseType_t xReturn;
9 UBaseType_t uxSavedInterruptStatus;
10 Queue_t * const pxQueue = ( Queue_t * ) xQueue;
11
12 uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
13 {
14 // 判断队列是否有空间插入新内容
15 if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
16 {
17 const int8_t cTxLock = pxQueue->cTxLock;
18
19 // 中断中不能使用互斥锁, 所以拷贝函数只是拷贝数据,
20 // 没有任务优先级继承需要考虑
21 ( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
22
23 // 判断队列是否被锁定
24 if( cTxLock == queueUNLOCKED )
25 {
26 #if ( configUSE_QUEUE_SETS == 1 )
27 // 集合相关代码
28 #else /* configUSE_QUEUE_SETS */
29 {
30 // 将最高优先级的等待任务恢复到就绪链表
31 if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
32 {
33 if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE)
34 {
35 // 如果有高优先级的任务被恢复
36 // 此处不直接触发任务切换, 而是返回一个标记
37 if( pxHigherPriorityTaskWoken != NULL )
38 {
39 *pxHigherPriorityTaskWoken = pdTRUE;
40 }
41 }
42 }
43 }
44 #endif /* configUSE_QUEUE_SETS */
45 }
46 else
47 {
48 // 队列被锁定, 不能修改事件链表
49 // 增加计数, 记录需要接触几个任务到就绪
50 // 在解锁队列的时候会根据这个计数恢复任务
51 pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 );
52 }
53 xReturn = pdPASS;
54 }
55 else
56 {
57 // 队列满 直接返回 不阻塞
58 xReturn = errQUEUE_FULL;
59 }
60 }
61
62 // 恢复中断的优先级
63 portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
64
65 return xReturn;
66}
очередь сообщений прочитана
Задача вызывает функцию-получатель для получения сообщений из очереди.Функция сначала определяет, есть ли в текущей очереди непрочитанные сообщения.Если нет, то определяет параметр xTicksToWait, и решает, возвращаться ли в функцию напрямую или блокировать и ждать. Если в очереди есть непрочитанное сообщение, сообщение, которое нужно прочитать, будет скопировано в указатель, переданный первым, а затем, когда будет оценен параметр функции xJustPeeking == pdFALSE, если оно совпадает, это означает, что функция прочитала сообщение. данные и должны быть Считанные данные обрабатываются в очереди, если нет, то просто просматриваются и данные возвращаются, но данные не очищаются. Для нормальной работы чтения данных очередь будет свободна после очистки данных, поэтому проверьте, есть ли в очереди задачи, ожидающие отправки данных и приостановленные, требуется переключение задач. Для тех, кто только просматривает данные, поскольку данные не очищаются, нет места для нового освобождения.Нет необходимости проверять список ожидания отправки, но список ожидания получения будет проверен.Если есть ожидающая задача , он переключится в режим готовности и определит, нужно ли его переключать.
Анализ процесса исключения из очереди сообщений на самом деле аналогичен анализу записи в очередь, см. примечания:
1 /*-----------------------------------------------------------*/
2 BaseType_t xQueueGenericReceive( QueueHandle_t xQueue, (1)
3 void * const pvBuffer, (2)
4 TickType_t xTicksToWait, (3)
5 const BaseType_t xJustPeeking ) (4)
6 {
7 BaseType_t xEntryTimeSet = pdFALSE;
8 TimeOut_t xTimeOut;
9 int8_t *pcOriginalReadPosition;
10 Queue_t * const pxQueue = ( Queue_t * ) xQueue;
11
12 /* 已删除一些断言 */
13 for ( ;; ) {
14 taskENTER_CRITICAL(); (5)
15 {
16 const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
17
18 /* 看看队列中有没有消息 */
19 if ( uxMessagesWaiting > ( UBaseType_t ) 0 ) { (6)
20 /*防止仅仅是读取消息,而不进行消息出队操作*/
21 pcOriginalReadPosition = pxQueue->u.pcReadFrom; (7)
22 /* 拷贝消息到用户指定存放区域pvBuffer */
23 prvCopyDataFromQueue( pxQueue, pvBuffer ); (8)
24
25 if ( xJustPeeking == pdFALSE ) { (9)
26 /* 读取消息并且消息出队 */
27 traceQUEUE_RECEIVE( pxQueue );
28
29 /* 获取了消息,当前消息队列的消息个数需要减一 */
30 pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1; (10)
31 /* 判断一下消息队列中是否有等待发送消息的任务 */
32 if ( listLIST_IS_EMPTY( (11)
33 &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE ) {
34 /* 将任务从阻塞中恢复 */
35 if ( xTaskRemoveFromEventList( (12)
36 &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE ) {
37 /* 如果被恢复的任务优先级比当前任务高,会进行一次任务切换 */
38 queueYIELD_IF_USING_PREEMPTION(); (13)
39 } else {
40 mtCOVERAGE_TEST_MARKER();
41 }
42 } else {
43 mtCOVERAGE_TEST_MARKER();
44 }
45 } else { (14)
46 /* 任务只是看一下消息(peek),并不出队 */
47 traceQUEUE_PEEK( pxQueue );
48
49 /* 因为是只读消息 所以还要还原读消息位置指针 */
50 pxQueue->u.pcReadFrom = pcOriginalReadPosition; (15)
51
52 /* 判断一下消息队列中是否还有等待获取消息的任务 */
53 if ( listLIST_IS_EMPTY( (16)
54 &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ) {
55 /* 将任务从阻塞中恢复 */
56 if ( xTaskRemoveFromEventList(
57 &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ) {
58 /* 如果被恢复的任务优先级比当前任务高,会进行一次任务切换 */
59 queueYIELD_IF_USING_PREEMPTION();
60 } else {
61 mtCOVERAGE_TEST_MARKER();
62 }
63 } else {
64 mtCOVERAGE_TEST_MARKER();
65 }
66 }
67
68 taskEXIT_CRITICAL(); (17)
69 return pdPASS;
70 } else { (18)
71 /* 消息队列中没有消息可读 */
72 if ( xTicksToWait == ( TickType_t ) 0 ) { (19)
73 /* 不等待,直接返回 */
74 taskEXIT_CRITICAL();
75 traceQUEUE_RECEIVE_FAILED( pxQueue );
76 return errQUEUE_EMPTY;
77 } else if ( xEntryTimeSet == pdFALSE ) {
78 /* 初始化阻塞超时结构体变量,初始化进入
79 阻塞的时间xTickCount和溢出次数xNumOfOverflows */
80 vTaskSetTimeOutState( &xTimeOut ); (20)
81 xEntryTimeSet = pdTRUE;
82 } else {
83 mtCOVERAGE_TEST_MARKER();
84 }
85 }
86 }
87 taskEXIT_CRITICAL();
88
89 vTaskSuspendAll();
90 prvLockQueue( pxQueue ); (21)
91
92 /* 检查超时时间是否已经过去了*/
93 if ( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ) {(22)
94 /* 如果队列还是空的 */
95 if ( prvIsQueueEmpty( pxQueue ) != pdFALSE ) {
96 traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue ); (23)
97 /* 将当前任务添加到队列的等待接收列表中
98 以及阻塞延时列表,阻塞时间为用户指定的超时时间xTicksToWait */
99 vTaskPlaceOnEventList(
100 &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
101 prvUnlockQueue( pxQueue );
102 if ( xTaskResumeAll() == pdFALSE ) {
103 /* 如果有任务优先级比当前任务高,会进行一次任务切换 */
104 portYIELD_WITHIN_API();
105 } else {
106 mtCOVERAGE_TEST_MARKER();
107 }
108 } else {
109 /* 如果队列有消息了,就再试一次获取消息 */
110 prvUnlockQueue( pxQueue ); (24)
111 ( void ) xTaskResumeAll();
112 }
113 } else {
114 /* 超时时间已过,退出 */
115 prvUnlockQueue( pxQueue ); (25)
116 ( void ) xTaskResumeAll();
117
118 if ( prvIsQueueEmpty( pxQueue ) != pdFALSE ) {
119 /* 如果队列还是空的,返回错误代码errQUEUE_EMPTY */
120 traceQUEUE_RECEIVE_FAILED( pxQueue );
121 return errQUEUE_EMPTY; (26)
122 } else {
123 mtCOVERAGE_TEST_MARKER();
124 }
125 }
126 }
127 }
128 /*-----------------------------------------------------------*/
намекать
Если данные, хранящиеся в очереди, большие, лучше использовать очередь для передачи указателя данных, а не самих данных, потому что при передаче данных ЦП необходимо скопировать данные в очередь или из очереди байтом байт. . Передача указателей более эффективна с точки зрения скорости обработки и использования памяти. Однако при использовании очереди для передачи указателей будьте очень осторожны, чтобы сделать две вещи:
1. Владение пространством памяти, на которое указывает указатель, должно быть ясно
Когда задачи совместно используют память через указатели, должно быть принципиально гарантировано, что никакие две задачи не будут одновременно изменять данные в общей памяти или иным образом делать недействительными данные в общей памяти или вызывать проблемы согласованности. В принципе, до отправки указателя разделяемой памяти в очередь доступ к его содержимому разрешен только задаче-отправителю; после того как указатель разделяемой памяти считывается из очереди, доступ к его содержимому разрешен только получение задания.
2. Область памяти, на которую указывает указатель, должна быть допустимой.
Если пространство памяти, на которое указывает указатель, выделяется динамически, за его освобождение должна отвечать только одна задача. Когда это пространство памяти освобождается, ни одна задача не должна снова обращаться к этому пространству. и самое главное запретить доступ по указателямв стеке задачпространство, то есть локальные переменные. Потому что при изменении стека данные в стеке перестают быть действительными.
Подписывайтесь на меня
Добро пожаловать в публичный аккаунт «Развития Интернета вещей IoT»