FreeRTOS. Взгляд изнутри.
Алгоритм работы планировщика. Часть 2

№ 6’2013
PDF версия
Статья продолжает цикл, посвященный внутреннему устройству FreeRTOS — операционной системы для микроконтроллеров. Темой статьи стал алгоритм работы планировщика в режиме вытесняющей многозадачности. В первой части статьи были рассмотрены состояние задачи готовности к выполнению и состояние выполнения, а также переходы между этими состояниями. В этой статье внимание будет уделено блокированному состоянию, когда задача ожидает наступления временного события.

Все статьи цикла

Введение

В первой части статьи [1] помимо вопросов о том, как организовано хранение готовых к выполнению задач и какие процессы происходят при создании задачи, был описан основополагающий механизм во FreeRTOS, который используется для реализации почти всех объектов FreeRTOS, — механизм списков. Списки используются и для хранения задач, находящихся в блокированном состоянии. Статья написана с учетом того, что читатель помнит основные моменты касательно организации списков во FreeRTOS.

 

Блокирование задачи

Рассмотрим, что происходит при вызове API-функции vTaskDelay(), которая переводит задачу в блокированное состояние на заданное количество системных квантов времени. Реализация vTaskDelay()находится в файле tasks.c:

void vTaskDelay( portTickType xTicksToDelay ) {
portTickType xTimeToWake;
signed portBASE_TYPE xAlreadyYielded = pdFALSE;

  /* Если указано не нулевое время. */
  if( xTicksToDelay > ( portTickType ) 0U ) {
     vTaskSuspendAll(); {

       /* Вычислить значение счетчика квантов времени, когда задача должна "проснуться". */
     xTimeToWake = xTickCount + xTicksToDelay;

       /* Исключить задачу из списка готовых к выполнению,
       т.к. один и тот же элемент списка используется для обоих списков. */
       if( uxListRemove( ( xListItem * ) &( pxCurrentTCB->xGenericListItem ) ) == 0 ) {
            portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );
       }
       prvAddCurrentTaskToDelayedList( xTimeToWake );
     }
     xAlreadyYielded = xTaskResumeAll();
  }
  /* Выполнить переключение контекста. */
  if( xAlreadyYielded == pdFALSE ) {
     portYIELD_WITHIN_API();
  }
}

Представить цепочку вызовов при выполнении vTaskDelay() удобно в графическом виде (рис. 14).

Диаграмма вызовов при блокировании задачи API-функцией vTaskDelay()

Рис. 14. Диаграмма вызовов при блокировании задачи API-функцией vTaskDelay()

Если в качестве аргумента vTaskDelay() задано значение «0», то задача блокирована не будет: произойдет принудительное переключение контекста (макрос portYIELD_WITHIN_API()).

Если же в качестве аргумента vTaskDelay() задано ненулевое время, то выполняется вход в критическую секцию с помощью приостановки планировщика API-функцией vTaskSuspendAll().

Реализация vTaskSuspendAll() сводится к увеличению на «1» системной переменной uxSchedulerSuspended:

void vTaskSuspendAll( void ) {
      ++uxSchedulerSuspended;
}

Переменная uxSchedulerSuspended определяет состояние планировщика: если она равна «0», значит, планировщик выполняется; отличное от нуля значение — планировщик приостановлен. API-функция возобновления работы планировщика xTaskResumeAll(), которая будет рассмотрена ниже, помимо принудительного переключения контекста уменьшает переменную uxSchedulerSuspended на «1». Такой подход к реализации критической секции позволяет корректно работать API-функциям vTaskSuspendAll() и xTaskResumeAll() при вложенных вызовах.

После входа в критическую секцию локальная переменная xTimeToWake принимает значение счетчика квантов системного времени, когда задача должна выйти из блокированного состояния. Значение xTimeToWakeформируется путем сложения текущего счетчика квантов с временем блокирования.

После этого необходимо удалить блокируемую задачу из массива списков готовых к выполнению задач. Это действие выполняет системная функция uxListRemove(), которая получает в качестве аргумента указатель на элемент списка, соответствующий блоку управления блокируемой задачи:

unsigned portBASE_TYPE uxListRemove( xListItem *pxItemToRemove ) {
  xList * pxList;

  /* "Перебросить" указатели предыдущего и следующего элементов в списке так,
  чтобы они указывали друг на друга, а не на удаляемый элемент. */
  pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
  pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;

  /* Получить указатель на список, в котором находится удаляемый элемент. */
  pxList = ( xList * ) pxItemToRemove->pvContainer;

  /* Если индекс списка указывает на удаляемый элемент,
  то переместить индекс на предыдущий элемент. */
  if( pxList->pxIndex == pxItemToRemove ) {
      pxList->pxIndex = pxItemToRemove->pxPrevious;
  }

  /* Элемент больше не включен в список,
  установить значение контейнера элемента в NULL. */
  pxItemToRemove->pvContainer = NULL;

  /* Уменьшить на 1 счетчик кол-ва элементов в списке. */
  ( pxList->uxNumberOfItems )--;

  return pxList->uxNumberOfItems;
}

В качестве примера рассмотрим удаление задачи из списка, содержащего три элемента (рис. 15).

Удаление задачи из списка готовых к выполнению

Рис. 15. Удаление задачи из списка готовых к выполнению

Системная функция uxListRemove() в качестве аргумента получает указатель на элемент, который необходимо исключить из списка (рис. 15а, пункт 1). Далее указатель на предыдущий элемент следующего по списку элемента переустанавливается с удаляемого элемента на предыдущий относительно его элемент (рис. 15б, пункт 2). Указатель же на следующий элемент предыдущего по списку элемента (рис. 15б, пункт 3) «перебрасывается» с удаляемого элемента на следующий по списку. При этом указатели на следующий и предыдущий элемент удаляемого элемента сохраняют свои старые значения.

Однако указатели соседних элементов списка (относительно удаляемого элемента) оказываются изменены так, что они теряют связь с удаляемым элементом, а связываются друг с другом. Теперь при проходе списка (с помощью указателя на элемент) к удаленному элементу нельзя получить доступ, хотя «физически» сам элемент не удален.

В завершение операции удаления элемента его контейнер (то есть указатель на список pvContainer), в котором данный элемент находится, устанавливается в значение NULL (рис. 15в, пункт 4), а счетчик элементов в списке uxNumberOfItems уменьшается на «1». Функция uxListRemove() возвращает текущее количество элементов в списке после удаления элемента.

Вернемся к рассмотрению реализации API-функции vTaskDelay(). Если функция uxListRemove() удалила последний элемент в списке (вернула значение «0»), то происходит вызов макроса portRESET_READY_PRIORITY(), однако для порта FreeRTOS для микроконтроллеров AVR этот макрос задан пустым. Макрос portRESET_READY_PRIORITY() используется для архитектур, которые позволяют аппаратно оптимизировать выбор следующей задачи. Архитектура AVR к таковым не относится.

Далее следует вызов системной функции prvAddCurrentTaskToDelayedList(), в качестве аргумента она получает полученное ранее значение счетчика системных квантов времени, при достижении которого задача должна выйти из блокированного состояния. Именно prvAddCurrentTaskToDelayedList() добавляет задачу в список блокированных задач. На самом деле используется два списка:

static void prvAddCurrentTaskToDelayedList( portTickType xTimeToWake ) {
  /* В порядок элемента записать время, когда задача должна "проснуться". */
  listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xGenericListItem ), xTimeToWake );
  /* Проверить, переполнится ли счетчик, пока досчитает до времени пробуждения. */
  if( xTimeToWake < xTickCount ) {
     /* Если время пробуждения оказалось меньше счетчика квантов,
     это означает, что счетчик переполнится, пока досчитает до времени пробуждения.
     В этом случае задача помещается в список pxOverflowDelayedTaskList.*/
     vListInsert( ( xList * ) pxOverflowDelayedTaskList, ( xListItem * ) &( pxCurrentTCB->xGenericListItem ) );
  } else {
     /* Задача должна «проснуться» до переполнения счетчика. Поместить задачу в "обычный" список блокированных задач. */
     vListInsert( ( xList * ) pxDelayedTaskList, ( xListItem * ) &( pxCurrentTCB->xGenericListItem ) );
     /* Обновить значение xNextTaskUnblockTime - время пробуждения очередной задачи,
     если блокируемая задача должна "проснуться" раньше остальных. */
     if( xTimeToWake < xNextTaskUnblockTime ) {
     xNextTaskUnblockTime = xTimeToWake;
     }
  }
}

Прежде всего, с помощью макроса listSET_LIST_ITEM_VALUE() в поле xItemValue (вес элемента) записывается время, когда задача должна «проснуться»: записывается значение локальной переменной xTimeToWake. Следует отметить, что для включения задачи в список блокированных задач используется тот же элемент блока управления, что и для включения задачи в список готовых к выполнению — поле xGenericListItemструктуры tskTCB.

Собственно добавление задачи в один из списков блокированных выполняет системная функция vListInsert(). В качестве аргументов она получает указатель на список xList, куда добавляется элемент, и указатель на сам элемент xListItem. Реализация vListInsert() из файла list.h:

/* Вставить элемент в список, отсортированный в порядке возрастания. */
void vListInsert( xList *pxList, xListItem *pxNewListItem ) {
  /* Указатель на элемент списка, после которого необходимо вставить новый элемент. */
  volatile xListItem *pxIterator;
  /* Вес нового элемента. */
  portTickType xValueOfInsertion;

  /* Запомнить значение веса нового элемента в переменной xValueOfInsertion. */
  xValueOfInsertion = pxNewListItem->xItemValue;

  /* Если список содержит элемент с таким же весом, то новый будет записан после него. */
  /* Если вес задан максимальным - portMAX_DELAY, то записать элемент последним в списке. */
  if( xValueOfInsertion == portMAX_DELAY ) {
     pxIterator = pxList->xListEnd.pxPrevious;
  } else {
     /* Перебор элементов списка до тех пор, пока следующий за pxIterator элемент не будет иметь больший вес,
     чем у добавляемого элемента. */
     for( pxIterator = ( xListItem * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext ) {
     }
  }

  /* Вставка элемента после того, на который указывает pxIterator.
  Сортировка массива по возрастанию веса элемента не будет нарушена. */
  /* Пункт 3 */
  pxNewListItem->pxNext = pxIterator->pxNext;
  /* Пункт 4 */
  pxNewListItem->pxNext->pxPrevious = ( volatile xListItem * ) pxNewListItem;
  /* Пункт 5 */
  pxNewListItem->pxPrevious = pxIterator;
  /* Пункт 6 */
  pxIterator->pxNext = ( volatile xListItem * ) pxNewListItem;

  /* Записать значение контейнера для вновь добавленного элемента. */
  /* Пункт 7 */
  pxNewListItem->pvContainer = ( void * ) pxList;

  /* Увеличить на 1 счетчик элементов списка. */
  ( pxList->uxNumberOfItems )++;
}

Графически процесс добавления задачи в список блокированных показан на рис. 16. В целях упрощения рисунка не приведены значения полей «хозяин элемента» (pvOwner) и «контейнер элемента» (pvContainer).

Пример добавления задачи в список блокированных задач (упрощенный вид)

Рис. 16. Пример добавления задачи в список блокированных задач (упрощенный вид)

Пусть список уже содержит две блокированные задачи, Задача 1 должна «проснуться», когда счетчик квантов системного времени досчитает до 200, Задача 2 — когда счетчик досчитает до 400. Значение счетчика системных квантов в этот момент равно 100. Заметьте, что список отсортирован в порядке возрастания времени «пробуждения» — в порядке возрастания веса входящих в него элементов (рис. 16а), то есть первой в списке идет Задача 1, которая должна «проснуться» раньше остальных.

Пусть в какой-то момент в программе произошел вызов API-функции vTaskDelay() или vTaskDelayUntil(), которая блокирует выполняющуюся в данный момент Задачу 3 на время 200 системных квантов. Так как в данный момент счетчик системных квантов xTickCount равен 100, то вес элемента списка, соответствующего этой задаче, устанавливается равным 100+200 = 300 квантов.

Далее в цикле for() выполняется перебор элементов списка, до тех пор, пока указатель pxIterator не остановится на таком элементе, следующий элемент относительно которого будет иметь вес больший, чем вес добавляемого элемента. В нашем случае это будет элемент, соответствующий Задаче 1, так как следующий за ним элемент имеет вес 400, что больше веса добавляемого элемента 300 (рис. 16а, пункт 2).

Новый элемент вставляются в список следующим, за тем, на который указывает указатель pxIterator, для чего устанавливаются связи нового элемента со следующим по списку (рис. 16б пункты 3, 4), а также с предыдущим по списку (рис. 16б, пункты 5, 6).

Далее в поле контейнер нового элемента записывается указатель на список, в который этот элемент был только что добавлен (рис. 16б, пункт 7), а количество элементов в списке увеличивается до трех.

Таким образом, вновь добавленная задача вставляется в список блокированных задач, причем сортировка списка по возрастанию времени «пробуждения» не нарушается.

Два списка блокированных задач и соответственно указатели на них pxDelayedTaskList и pxOverflowDelayedTaskList используются для того, чтобы не нарушить сортировку списка в порядке возрастания по времени «пробуждения» для случаев, когда счетчик квантов системного времени переполнится в то время, когда задача находится в блокированном состоянии.

Если бы использовался один список, то могла бы возникнуть ситуация, показанная на рис. 17а. Пусть счетчик квантов имеет разрядность 16 бит, тогда переполнение происходит при достижении значения 65 536. (Разрядность счетчика можно задавать с помощью конфигурационного макроопределения configUSE_16_BIT_TICKS: если оно равно 1, то используется 16 бит, если 0 — то 32 бит.)

Причина использования двух списков блокированных задач

Рис. 17. Причина использования двух списков блокированных задач

Предположим, что счетчик квантов системного времени имеет значение 65 400, были заблокированы Задача 1 на время 100 квантов и Задача 2 на время 120 квантов. При этом они расположились в списке в порядке возрастания времени «пробуждения» — ошибки нет.

Далее пусть блокируются Задача 3 и Задача 4 на время 300 и 400 квантов соответственно. При этом время «пробуждения» Задачи 3 равно 65 400+300 = 65 700, однако этот результат выходит за пределы, обусловленные 16-битным представлением числа, поэтому результат за счет переполнения станет равным 65 400+300–65 536 = 164. Для Задачи 4 время «пробуждения», подсчитанное таким образом, составит 264 кванта (рис. 17а).

В такой ситуации для того, чтобы задачи хранились в порядке их «пробуждения», их необходимо записать в список в порядке, показанном на рис. 17а. Однако в этом случае сортировка по увеличению веса элементов списка оказалась бы нарушена.

Поэтому во FreeRTOS используются два списка блокированных задач. В список, на который указывает pxDelayedTaskList, помещаются задачи, чье время «пробуждения» наступит до переполнения счетчика квантов системного времени. В список, на который указывает pxOverflowDelayedTaskList, помещаются те задачи, чье «пробуждение» наступит после переполнения счетчика квантов (рис. 17б). При каждом переполнении счетчика квантов системного времени списки pxDelayedTaskList и pxOverflowDelayedTaskList меняются местами. (Подробнее об этом мы расскажем при рассмотрении функции vTaskIncrementTick().)

Завершается выполнение API-функции vTaskDelay() выходом из критической секции, с помощью вызова API-функции xTaskResumeAll(). Помимо уменьшения на «1» системной переменной uxSchedulerSuspendedAPI-функция xTaskResumeAll() вызывает принудительное переключение контекста, что необходимо, так как текущая задача была заблокирована, и управление должно быть передано другой задаче. Упрощенная реализация API-функции xTaskResumeAll() следующая (из файла tasks.c):

signed portBASE_TYPE xTaskResumeAll(void) {
  register tskTCB *pxTCB;
  signed portBASE_TYPE xAlreadyYielded = pdFALSE;

  taskENTER_CRITICAL();
  {
     --uxSchedulerSuspended;


     /* ... */
     /* Здесь пропущен код, отвечающий за обработку задач, которые стали готовы к выполнению, в то время когда планировщик или сама задача были приостановлены. */

#if configUSE_PREEMPTION == 1
     {
       xYieldRequired = pdTRUE;
     }
#endif

     if ((xYieldRequired == pdTRUE) || (xMissedYield == pdTRUE)) {
       xAlreadyYielded = pdTRUE;
       xMissedYield = pdFALSE;
       portYIELD_WITHIN_API();
     }
  }
  taskEXIT_CRITICAL();

  return xAlreadyYielded;
}

Видно, что если планировщик работает в режиме вытесняющей многозадачности (конфигурационное макроопределение configUSE_PREEMPTION равно «1»), то в любом случае произойдет вызов системного макроса portYIELD_WITHIN_API(), который в конечном счете сводится к вызову системной функции принудительного переключения контекста vPortYield() (для порта FreeRTOS для микроконтроллеров AVR).

Системная функция vPortYield() определена в файле port.c, ее тело:

void vPortYield( void ) __attribute__ ( ( naked ) );
void vPortYield( void )
{
  portSAVE_CONTEXT();
  vTaskSwitchContext();
  portRESTORE_CONTEXT();

  asm volatile ( "ret" );
}

В приведенной выше реализации xTaskResumeAll() опущена часть кода, который отвечает за обработку задач, которые были разблокированы, когда планировщик находился в приостановленном состоянии. Для хранения таких задач используется список xPendingReadyList. Однако рассмотрение процессов, происходящих, когда планировщик приостановлен, выходит за рамки этой статьи.

 

Разблокирование задачи

Мы рассмотрели процесс добавления задачи в список блокированных. Сейчас необходимо ответить на вопрос, как во FreeRTOS реализован переход задачи, блокированной до истечения заданного времени, в состояние готовности к выполнению. Проверка списка блокированных задач осуществляется каждый квант системного времени при выполнении обработчика прерывания системного таймера внутри функции vTaskIncrementTick():

void vPortYieldFromTick( void )
{
  portSAVE_CONTEXT();
  vTaskIncrementTick();
  vTaskSwitchContext();
  portRESTORE_CONTEXT();

  asm volatile ( "ret" );
}

После сохранения контекста вызывается функция vTaskIncrementTick(), которая, помимо увеличения счетчика квантов системного времени на единицу, проверяет, не пришло ли время «разбудить» очередную задачу. Упрощенная реализация vTaskIncrementTick(), взятая из файла task.c, имеет вид:

void vTaskIncrementTick(void) {
  tskTCB * pxTCB;
  /* Если планировщик не приостановлен */
  if (uxSchedulerSuspended == (unsigned portBASE_TYPE ) pdFALSE) {
     /* Увеличить на 1 счетчик квантов. */
     ++xTickCount;
     /* Если произошло переполнение счетчика. */
     if (xTickCount == (portTickType) 0U) {
       xList *pxTemp;

       /* Присвоить указателю pxDelayedTaskList значение pxOverflowDelayedTaskList.
       Теперь pxDelayedTaskList будет указывать на список блокированных задач,
       для которых счетчик должен был переполниться, прежде чем они выйдут из блокированного состояния. */
       pxTemp = pxDelayedTaskList;
       pxDelayedTaskList = pxOverflowDelayedTaskList;
       pxOverflowDelayedTaskList = pxTemp;
       xNumOfOverflows++;

       if (listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE) {
          /* Если список пуст, то переменной xNextTaskUnblockTime присвоить макс. значение. */
          xNextTaskUnblockTime = portMAX_DELAY;
       } else {
          /* Если в списке есть задачи, то присвоить переменной xNextTaskUnblockTime
          время «пробуждения» первой из списка задачи. */
          pxTCB = (tskTCB *) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
          xNextTaskUnblockTime = listGET_LIST_ITEM_VALUE( &( pxTCB->xGenericListItem ) );
       }
     }
     /* Проверить, не пришло ли время "разбудить" одну из задач. */
     prvCheckDelayedTasks();
  } else {
     /* Если планировщик приостановлен. */
     ++uxMissedTicks;
     /* Выполнить тело задачи "Бездействие". */
#if ( configUSE_TICK_HOOK == 1 )
     {
       vApplicationTickHook();
     }
#endif
  }

#if ( configUSE_TICK_HOOK == 1 )
  {
     if( uxMissedTicks == ( unsigned portBASE_TYPE ) 0U )
     {
       vApplicationTickHook();
     }
  }
#endif
}

Здесь происходит проверка, было ли переполнение счетчика квантов, если да, то списки pxDelayedTaskList и pxOverflowDelayedTaskList меняются местами — указатель pxDelayedTaskList становится равным адресу того списка, куда ранее записывались задачи, чье время «пробуждения» наступит после переполнения счетчика квантов системного времени. Теперь этот список содержит задачи с актуальными значениями счетчика квантов, когда задачи должны «проснуться».

Список, который ранее использовался для хранения задач, блокированных до переполнения счетчика, в момент переполнения, естественно, пуст, поэтому он теперь задействуется для хранения задач, которые разблокируются после переполнения счетчика. Таким образом, при каждом переполнении счетчика списки pxDelayedTaskList и pxOverflowDelayedTaskList меняются местами, при этом не нарушается логика работы с основным списком блокированных задач pxDelayedTaskList. Графически это можно представить в виде, изображенном на рис. 18.

Использование двух списков и двух указателей для хранения блокированных задач при переполнении счетчика квантов системного времени

Рис. 18. Использование двух списков и двух указателей для хранения блокированных задач при переполнении счетчика квантов системного времени

В переменную xNextTaskUnblockTime записывается ближайшее время разблокирования одной из задач. Если ни одна задача не блокирована, то переменная xNextTaskUnblockTime получает значение portMAX_DELAY.

Непосредственно проверку, пришло ли время разблокировать одну из задач, выполняет макрос prvCheckDelayedTasks(), реализованный в файле tasks.c:

#define prvCheckDelayedTasks() {
  portTickType xItemValue;
  /* Если счетчик квантов сравнялся с ближайшим временем разблокирования одной из задач. */
  if( xTickCount >= xNextTaskUnblockTime ) {
     for( ;; ) {
       if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE ) {
          /* Список блокированных задач пуст.
          Задать ближайшее время разблокирования задачи равным макс. возможному значению. */
          xNextTaskUnblockTime = portMAX_DELAY;
          break;
       } else {

          /* Список блокированных задач не пуст.
          Получить указатель на блок управления первой задачи в списке (pxTCB).
          Получить вес элемента - время разблокирования задачи (xItemValue). */
          pxTCB = ( tskTCB * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
          xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xGenericListItem ) );

          if( xTickCount < xItemValue ) {
            /* Если время разблокировки еще не пришло - запомнить его в xNextTaskUnblockTime. */
            xNextTaskUnblockTime = xItemValue;
            break;
          }

          /* Удалить задачу из списка блокированных. */
          uxListRemove( &( pxTCB->xGenericListItem ) );

          /* Если задача ожидает также наступления события синхронизации,
          то удалить ее из списка блокированных по событию синхронизации.*/
          if( pxTCB->xEventListItem.pvContainer != NULL ) {
            uxListRemove( &( pxTCB->xEventListItem ) );
          }

          /* Добавить задачу в массив готовых к выполнению задач. */
          prvAddTaskToReadyQueue( pxTCB );
       }
     }
  }
}

Алгоритм работы prvCheckDelayedTasks() сводится к следующему. Признаком того, что одна из задач нуждается в «пробуждении», является достижение счетчиком квантов xTickCount значения переменной xNextTaskUnblockTime, в которой хранится ближайшее время «пробуждения» одной из задач.

Далее, если список блокированных задач не пуст, указатель pxTCB устанавливается на первую задачу в списке. А так как список отсортирован в порядке увеличения времени «пробуждения» задач, то первой задачей окажется та, которая должна «проснуться» первой. Выбор первой задачи в списке осуществляет макрос listGET_OWNER_OF_HEAD_ENTRY():

#define listGET_OWNER_OF_HEAD_ENTRY( pxList ) ( (&( ( pxList )->xListEnd ))->pxNext->pvOwner )

Он использует тот факт, что список организован закольцованным: указатель pxNext последнего элемента xListEnd указывает на первый элемент в списке.

Далее в локальную переменную xItemValue записывается вес элемента, соответствующего задаче, — то есть время ее «пробуждения». Это выполняет макрос listGET_LIST_ITEM_VALUE():

#define listGET_LIST_ITEM_VALUE( pxListItem )  ( ( pxListItem )->xItemValue )

Если по какой-либо причине окажется, что время «пробуждения» задачи еще не пришло, то это время запоминается в переменной xNextTaskUnblockTime.

В противном случае найденная задача удаляется из списка блокированных с помощью функции uxListRemove(). После этого задача добавляется в список готовых к выполнению с помощью функции prvAddTaskToReadyQueue(). Функции uxListRemove() и prvAddTaskToReadyQueue() были изучены ранее при рассмотрении процессов блокирования задачи и создания задачи соответственно. Таким образом, задача оказывается исключенной из списка блокированных и добавлена в список готовых к выполнению.

Операции по извлечению задачи из списка блокированных и добавления в список готовых к выполнению будут повторяться в цикле for(;;) до тех пор, пока список не окажется пуст или пока не окажется, что время «пробуждения» первой задачи в списке еще не пришло.

Если вернуться к рассмотрению функции vTaskIncrementTick(), то можно видеть, что далее в ней происходит вызов функции обратного вызова vApplicationTickHook(), реализация которой должна быть в прикладной программе. В vApplicationTickHook() реализуют какие-либо действия, которые должны происходить каждый системный квант времени. Подробнее функция vApplicationTickHook() описана в [1, № 8`2011].

 

Выводы

В этой статье были освещены следующие вопросы внутреннего устройства FreeRTOS:

  • Как хранятся задачи в блокированном состоянии?
  • Что происходит при блокировании задачи на заданное число квантов?
  • Как задача выходит из блокированного состояния?

Это далеко не полный список аспектов работы планировщика в режиме вытесняющей многозадачности. Например, за рамками статьи остались следующие вопросы:

  • Где хранятся приостановленные (suspended) задачи?
  • Как задача входит и выходит из приостановленного состояния?
  • Как организуется блокирование задачи, ожидающей события синхронизации, и ее разблокирование?
  • Как реализованы такие объекты FreeRTOS, как очереди, семафоры и мьютексы?
  • Как реализованы сопрограммы?

Кроме того, вне поля зрения остались вопросы о работе FreeRTOS в режиме кооперативной и гибридной многозадачности.

Тем не менее, основываясь на изложенном в статье и в [1] материале, читатель может сам продолжить изучение внутреннего устройства планировщика FreeRTOS. Многие рассмотренные в статье аспекты применяются при реализации других возможностей FreeRTOS. Например, списки xList используются для реализации сопрограмм и очередей.

xosotin chelseathông tin chuyển nhượngcâu lạc bộ bóng đá arsenalbóng đá atalantabundesligacầu thủ haalandUEFAevertonxosofutebol ao vivofutemaxmulticanaisonbetbóng đá world cupbóng đá inter milantin juventusbenzemala ligaclb leicester cityMUman citymessi lionelsalahnapolineymarpsgronaldoserie atottenhamvalenciaAS ROMALeverkusenac milanmbappenapolinewcastleaston villaliverpoolfa cupreal madridpremier leagueAjaxbao bong da247EPLbarcelonabournemouthaff cupasean footballbên lề sân cỏbáo bóng đá mớibóng đá cúp thế giớitin bóng đá ViệtUEFAbáo bóng đá việt namHuyền thoại bóng đágiải ngoại hạng anhSeagametap chi bong da the gioitin bong da lutrận đấu hôm nayviệt nam bóng đátin nong bong daBóng đá nữthể thao 7m24h bóng đábóng đá hôm naythe thao ngoai hang anhtin nhanh bóng đáphòng thay đồ bóng đábóng đá phủikèo nhà cái onbetbóng đá lu 2thông tin phòng thay đồthe thao vuaapp đánh lô đềdudoanxosoxổ số giải đặc biệthôm nay xổ sốkèo đẹp hôm nayketquaxosokq xskqxsmnsoi cầu ba miềnsoi cau thong kesxkt hôm naythế giới xổ sốxổ số 24hxo.soxoso3mienxo so ba mienxoso dac bietxosodientoanxổ số dự đoánvé số chiều xổxoso ket quaxosokienthietxoso kq hôm nayxoso ktxổ số megaxổ số mới nhất hôm nayxoso truc tiepxoso ViệtSX3MIENxs dự đoánxs mien bac hom nayxs miên namxsmientrungxsmn thu 7con số may mắn hôm nayKQXS 3 miền Bắc Trung Nam Nhanhdự đoán xổ số 3 miềndò vé sốdu doan xo so hom nayket qua xo xoket qua xo so.vntrúng thưởng xo sokq xoso trực tiếpket qua xskqxs 247số miền nams0x0 mienbacxosobamien hôm naysố đẹp hôm naysố đẹp trực tuyếnnuôi số đẹpxo so hom quaxoso ketquaxstruc tiep hom nayxổ số kiến thiết trực tiếpxổ số kq hôm nayso xo kq trực tuyenkết quả xổ số miền bắc trực tiếpxo so miền namxổ số miền nam trực tiếptrực tiếp xổ số hôm nayket wa xsKQ XOSOxoso onlinexo so truc tiep hom nayxsttso mien bac trong ngàyKQXS3Msố so mien bacdu doan xo so onlinedu doan cau loxổ số kenokqxs vnKQXOSOKQXS hôm naytrực tiếp kết quả xổ số ba miềncap lo dep nhat hom naysoi cầu chuẩn hôm nayso ket qua xo soXem kết quả xổ số nhanh nhấtSX3MIENXSMB chủ nhậtKQXSMNkết quả mở giải trực tuyếnGiờ vàng chốt số OnlineĐánh Đề Con Gìdò số miền namdò vé số hôm nayso mo so debach thủ lô đẹp nhất hôm naycầu đề hôm naykết quả xổ số kiến thiết toàn quốccau dep 88xsmb rong bach kimket qua xs 2023dự đoán xổ số hàng ngàyBạch thủ đề miền BắcSoi Cầu MB thần tàisoi cau vip 247soi cầu tốtsoi cầu miễn phísoi cau mb vipxsmb hom nayxs vietlottxsmn hôm naycầu lô đẹpthống kê lô kép xổ số miền Bắcquay thử xsmnxổ số thần tàiQuay thử XSMTxổ số chiều nayxo so mien nam hom nayweb đánh lô đề trực tuyến uy tínKQXS hôm nayxsmb ngày hôm nayXSMT chủ nhậtxổ số Power 6/55KQXS A trúng roycao thủ chốt sốbảng xổ số đặc biệtsoi cầu 247 vipsoi cầu wap 666Soi cầu miễn phí 888 VIPSoi Cau Chuan MBđộc thủ desố miền bắcthần tài cho sốKết quả xổ số thần tàiXem trực tiếp xổ sốXIN SỐ THẦN TÀI THỔ ĐỊACầu lô số đẹplô đẹp vip 24hsoi cầu miễn phí 888xổ số kiến thiết chiều nayXSMN thứ 7 hàng tuầnKết quả Xổ số Hồ Chí Minhnhà cái xổ số Việt NamXổ Số Đại PhátXổ số mới nhất Hôm Nayso xo mb hom nayxxmb88quay thu mbXo so Minh ChinhXS Minh Ngọc trực tiếp hôm nayXSMN 88XSTDxs than taixổ số UY TIN NHẤTxs vietlott 88SOI CẦU SIÊU CHUẨNSoiCauVietlô đẹp hôm nay vipket qua so xo hom naykqxsmb 30 ngàydự đoán xổ số 3 miềnSoi cầu 3 càng chuẩn xácbạch thủ lônuoi lo chuanbắt lô chuẩn theo ngàykq xo-solô 3 càngnuôi lô đề siêu vipcầu Lô Xiên XSMBđề về bao nhiêuSoi cầu x3xổ số kiến thiết ngày hôm nayquay thử xsmttruc tiep kết quả sxmntrực tiếp miền bắckết quả xổ số chấm vnbảng xs đặc biệt năm 2023soi cau xsmbxổ số hà nội hôm naysxmtxsmt hôm nayxs truc tiep mbketqua xo so onlinekqxs onlinexo số hôm nayXS3MTin xs hôm nayxsmn thu2XSMN hom nayxổ số miền bắc trực tiếp hôm naySO XOxsmbsxmn hôm nay188betlink188 xo sosoi cầu vip 88lô tô việtsoi lô việtXS247xs ba miềnchốt lô đẹp nhất hôm naychốt số xsmbCHƠI LÔ TÔsoi cau mn hom naychốt lô chuẩndu doan sxmtdự đoán xổ số onlinerồng bạch kim chốt 3 càng miễn phí hôm naythống kê lô gan miền bắcdàn đề lôCầu Kèo Đặc Biệtchốt cầu may mắnkết quả xổ số miền bắc hômSoi cầu vàng 777thẻ bài onlinedu doan mn 888soi cầu miền nam vipsoi cầu mt vipdàn de hôm nay7 cao thủ chốt sốsoi cau mien phi 7777 cao thủ chốt số nức tiếng3 càng miền bắcrồng bạch kim 777dàn de bất bạion newsddxsmn188betw88w88789bettf88sin88suvipsunwintf88five8812betsv88vn88Top 10 nhà cái uy tínsky88iwinlucky88nhacaisin88oxbetm88vn88w88789betiwinf8betrio66rio66lucky88oxbetvn88188bet789betMay-88five88one88sin88bk88xbetoxbetMU88188BETSV88RIO66ONBET88188betM88M88SV88Jun-68Jun-88one88iwinv9betw388OXBETw388w388onbetonbetonbetonbet88onbet88onbet88onbet88onbetonbetonbetonbetqh88mu88Nhà cái uy tínpog79vp777vp777vipbetvipbetuk88uk88typhu88typhu88tk88tk88sm66sm66me88me888live8live8livesm66me88win798livesm66me88win79pog79pog79vp777vp777uk88uk88tk88tk88luck8luck8kingbet86kingbet86k188k188hr99hr99123b8xbetvnvipbetsv66zbettaisunwin-vntyphu88vn138vwinvwinvi68ee881xbetrio66zbetvn138i9betvipfi88clubcf68onbet88ee88typhu88onbetonbetkhuyenmai12bet-moblie12betmoblietaimienphi247vi68clupcf68clupvipbeti9betqh88onb123onbefsoi cầunổ hũbắn cáđá gàđá gàgame bàicasinosoi cầuxóc đĩagame bàigiải mã giấc mơbầu cuaslot gamecasinonổ hủdàn đềBắn cácasinodàn đềnổ hũtài xỉuslot gamecasinobắn cáđá gàgame bàithể thaogame bàisoi cầukqsssoi cầucờ tướngbắn cágame bàixóc đĩaAG百家乐AG百家乐AG真人AG真人爱游戏华体会华体会im体育kok体育开云体育开云体育开云体育乐鱼体育乐鱼体育欧宝体育ob体育亚博体育亚博体育亚博体育亚博体育亚博体育亚博体育开云体育开云体育棋牌棋牌沙巴体育买球平台新葡京娱乐开云体育mu88qh88
Литература
  1. Курниц А. FreeRTOS. Взгляд изнутри // Компоненты и технологии. 2012. № 7, 11.
  2. Курниц А. FreeRTOS — операционная система для микроконтроллеров // Компоненты и технологии. 2011. № 2–11.
  3. freertos.org

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *