Блокировка это один из режимов задачи, когда она приостановлена в ожидании какого-либо события - например, выдача семафора или появление сообщения в очереди. Наборы очередей (queue sets) это функция FreeRTOS, которая позволяет задаче заблокироваться, когда она одновременно получает событие от нескольких очередей и/или семафоров. При этом очереди и семафоры группируются в набор (set), и тогда вместо того, чтобы блокироваться на индивидуальной очереди или на индивидуальном семафоре, задача блокируется на этом наборе.
Примечание: хотя иногда необходимо осуществлять блокировку (ожидание) на более чем одной очереди, если Вы интегрируете FreeRTOS со сторонним или устаревшим кодом, конструкции, свободные от таких ограничений, могут быть нормально реализованы более эффективно с тем же самым функционалом. Как это делается, описывается далее в этой статье (перевод документации [1]).
[Использование наборов очередей]
Наборы очередей используются наподобие API-функции select() и связанных с ней функций, которые входят как часть в стандартную библиотеку Berkeley sockets networking API.
Наборы очередей могут содержать очереди и семафоры, которые входят в набор очереди как его члены. Параметры API-функции и возвращаемые значения, которые могут принимать либо дескриптор очереди (queue handle), либо дескриптор семафора (semaphore handle), используют тип QueueSetMemberHandle_t. Переменные типа QueueHandle_t и SemaphoreHandle_t могут нормально и неявно быть преобразованы в параметр или возвращаемое значение типа QueueSetMemberHandle_t без генерации предупреждения компилятора (не требуется явное преобразование типа для совместимости с типом QueueSetMemberHandle_t).
Использование набора очередей подразумевает выполнение следующих действий:
Создание набора очередей. Перед тем, как queue set можно использовать, он должен быть создан с помощью API-функции xQueueCreateSet(). Как только набор очередей создан, к нему происходит обращение через переменную типа QueueSetHandle_t. Пример:
Добавление в набор очередей. Для добавление в queue set очереди или семафора используется API-функция xQueueAddToSet().
Блокировка на наборе очередей. API-функция xQueueSelectFromSet() используется для проверки, готовы ли для чтения какие-либо элементы набора – здесь под "чтением" подразумевается "получение" (receiving), когда элемент это очередь, и взятие (taking), когда элемент это семафор. Точно так же, как используются API-функции xQueueReceive() и xSemaphoreTake(), функция xQueueSelectFromSet() позволяет реализовать опциональную блокировку вызывающей задачи, пока элемент в наборе не будет готов для чтения.
Из вызова xQueueSelectFromSet() будет возвращен NULL, если произошел таймаут. Иначе xQueueSelectFromSet() вернет дескриптор элемента набора, который готов к чтению, что позволяет вызывающей задаче немедленно вызвать xQueueReceive() или xSemaphoreTake() (на дескрипторе очереди или дескрипторе семафора соответственно) с гарантией, что эта операция будет успешно осуществлена.
На страничке с документацией по xQueueCreateSet() [3] есть пример использования. В демонстрационном/тестовом модуле QueueSet.c (находится в каталоге FreeRTOS/Demo/Common/Minimal загружаемого пакета FreeRTOS) также есть подробный пример.
[Альтернатива для наборов очередей]
Если не существует конкретной проблемы интеграции, которая требует блокировки на нескольких очередях, та же самая функциональность может быть реализована с меньшими затратами памяти кода, ОЗУ и накладных расходов процессорного времени с использованием одной очереди. Реализация FreeRTOS+UDP предоставляет удобный пример такого подхода, что описывается далее.
Проблема, которая требовала решения в стеке UDP/IP: задача, которая обслуживает стек FreeRTOS+UDP, управляется событиями. Существует несколько источников событий. Некоторые события не имеют каких-либо связанных с событием данных, а некоторые события связаны с переменным объемом данных. Вот эти события:
• Аппаратура Ethernet приняла фрейм. Фреймы содержат большой объем данных, который может быть разного размера. • Аппаратура Ethernet завершила передачу фрейма, освободив сетевой буфер и буфер DMA. • Задача приложения отправила пакет. Пакеты могут содержать большой объем данных переменной длины. • Различные программные таймеры, включая таймер ARP. События таймера не связаны с какими-либо данными.
Решение проблемы "в лоб" подразумевает, что для UDP/IP создаются отдельные очереди для каждого источника события, после чего эти очереди объединяются в один набор очередей, и используется блокировка уже на нем. Но есть способ эффективнее, что реализовано в стеке UDP/IP:
1. Определяется структура, которая содержит одно поле для хранения типа события, и другое поле для хранения данных (в случае стека UDP/IP это будет просто указатель на данные), которые связаны с событием. 2. Создается только одна очередь, предназначенная для этой структуры. Каждый источник события помещает в свое событие в одну и ту же очередь. 3. Задача блокируется на единственной очереди, и при выходе из блокировки извлекает из очереди элемент. Затем проверяет поле типа события, и в зависимости от его значения выполняет нужные действия.
Ниже показано определение структуры.
typedef struct IP_TASK_COMMANDS
{
/* Это поле сообщает принимающей событие задаче, с каким именно
событием предстоит иметь дело: */
eIPEvent_t eEventType;
/* Это поле хранит любые данные, связанные с событием: */
void *pvData;
} xIPStackEvent_t;
Например, когда истек таймер ARP, он посылает в очередь событие, где поле eEventType установлено в eARPTimerEvent (это значение типа eIPEvent_t, перечисление enum). События таймера ARP не связаны с какими-либо данными, поэтому поле pvData не устанавливается. Когда драйвер Ethernet получает фрейм по сети, он посылает в очередь сообщение, где поле eEventType установлено в eEthernetRxEvent, и поле pvData установлено на адрес буфера фрейма.
Стек UDP/IP обрабатывает события в простом цикле:
/* Переменная, которая используется для приема сообщения из очереди: */
xIPStackEvent_t xReceivedEvent;
for( ;; )
{
/* Ожидание события на очереди, которое покажет, что что-то произошло: */
xQueueReceive( xNetworkEventQueue, &xReceivedEvent, portMAX_DELAY );
/* В зависимости от типа события выполняются разные действия: */
switch( xReceivedEvent.eEventType )
{
case eNetworkDownEvent:
prvProcessNetworkDownEvent();
break;
case eEthernetRxEvent:
prvProcessEthernetFrame( xReceivedEvent.pvData );
break;
case eARPTimerEvent:
prvAgeARPCache();
break;
case eStackTxEvent:
prvProcessGeneratedPacket( xReceivedEvent.pvData );
break;
case eDHCPEvent:
vDHCPProcess();
break;
default:
/* Сюда мы никогда не должны попасть. */
break;
}
}
[Ссылки]
1. Blocking on Multiple RTOS Objects site:freertos.org. 2. Очереди FreeRTOS. 3. xQueueCreateSet() site:freertos.org. |