Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Кармин Новиелло - Освоение STM32.pdf
Скачиваний:
2740
Добавлен:
23.09.2021
Размер:
47.68 Mб
Скачать

23. Запуск FreeRTOS

Воспользоваться всеми преимуществами вычислительной мощности 32-разрядных микроконтроллеров непросто, особенно для мощных серий STM32F2/F4/F7. Если нашему устройству не нужно выполнять достаточно простые задачи, то при разработке микропрограммы правильное распределение вычислительных ресурсов требует особого внимания. Кроме того, использование неподходящих структур синхронизации и плохо спроектированных процедур обслуживания прерываний может привести к потере важных асинхронных событий и к непредсказуемому поведению нашего устройства в целом.

Операционные системы реального времени (ОСРВ, англ. Real Time Operating Systems, RTOS)

используют преимущества системы исключений, предоставляемой ядрами Cortex-M, чтобы донести до программистов понятие потока (thread)1 – независимого потока выполнения, который «конкурирует» с другими потоками микроконтроллера, участвующими в конкурентных действиях2. Кроме того, они предлагают продвинутые примитивы синхронизации, которые позволяют одновременно координировать параллельный доступ к аппаратным ресурсам из разных потоков и не тратить тактовые циклы ЦПУ на ожидание медленных и асинхронных событий.

Сегмент рынка ОСРВ в настоящее время довольно многолюдный: для программистов доступно несколько коммерческих и бесплатных решений с открытым исходным

1Некоторые ОСРВ, такие как FreeRTOS, используют термин задача (task) для обозначения независимого потока выполнения, конкурирующего с другими задачами. Однако автор книги считает эту терминологию неуместной. Традиционно в операционных системах общего назначения многозадачность – это метод, с помощью которого несколько задач, также называемых процессами (process), совместно используют общие аппаратные ресурсы (в основном ЦПУ и ОЗУ). С многозадачной ОС, такой как Linux, вы можете одновременно запускать несколько приложений. Многозадачность относится к способности ОС быстро переключаться между каждой вычислительной задачей для создания впечатления того, что разные приложения выполняют несколько действий одновременно. Процесс имеет одну важную характеристику: его пространство памяти физически изолировано от других процессов благодаря возможностям, предлагае-

мым модулем управления памятью (Memory Management Unit, MMU) внутри ЦПУ. Многопоточность расши-

ряет идею многозадачности в отдельных процессах, так что вы можете разделять определенные операции в рамках одного приложения на отдельные потоки. Каждый поток может работать параллельно. Важной особенностью потоков является то, что они совместно используют одно адресное пространство памяти. Настоящие встроенные архитектуры, такие как STM32, не предоставляют MMU (в некоторых из них доступен только модуль защиты памяти (Memory Protection Unit, MPU) с ограниченным набором функций). Отсутствие этого модуля не позволяет разделять адресные пространства, поскольку невозможно наложить физические адреса с логическими. Это означает, что они могут выполнять только одно приложение, которое в конечном итоге может быть разделено на несколько потоков, делящих между собой одно и то же адресное пространство памяти. По этой причине мы будем говорить о потоках в данной книге, даже если иногда будем использовать слово «задача» при рассмотрении некоторых API-интерфейсов FreeRTOS или для обозначения работы микропрограммы в общем виде.

2Конкурентные действия, конкурентное программирование, конкурирующие задачи можно считать параллельными: в словарях «concurrent …» переводится как «параллельный …», однако следует понимать, что это не совсем верно. Хотя и считается, что конкурентные вычисления включают в себя параллельные, у них есть существенные отличия. Конкурентные вычисления – это способ организации параллелизма в одноядерных системах, при этом параллельными они не являются, поскольку конкурирующие потоки не выполняются одновременно, а лишь быстро переключаются в соответствии с алгоритмом планирования. Далее словосочетания «параллельная задача/поток/вычисления» следует воспринимать как «конкурирующая задача/поток/вычисления». (прим. переводчика)

Запуск FreeRTOS

602

кодом. Поскольку Cortex-M является стандартизированной архитектурой для многих производителей интегральных схем, разработчики STM32 могут выбирать из действительно широкого ассортимента систем ОСРВ, в зависимости от сложности работы с ними и специализированной (и, возможно, коммерческой) поддержки. ST Microelectronics приняла одну популярную бесплатную ОС с открытым исходным кодом в качестве официального инструмента для платформы CubeHAL: FreeRTOS.

По некоторым данным, сегодня FreeRTOS является наиболее распространенной ОСРВ на рынке. Благодаря двойной лицензии, которая позволяет продавать коммерческие продукты без каких-либо ограничений3, FreeRTOS стала своего рода стандартом в электронной промышленности, и она также широко используется сообществом Open Source. Хотя она и не единственное решение, доступное для платформы STM32, в данной книге мы сосредоточим наше внимание исключительно на этой ОС, поскольку именно она официально поддерживается и интегрируется в CubeHAL.

FreeRTOS была приобретена Amazon AWS в 2017 году, и теперь она официально распространяется по более снисходительной и «коммерчески дружественной» лицензии MIT. После ее приобретения AWS, была официально распространена новая основная версия (v10.0). Эта новая версия была разработана для замены совместимой FreeRTOS 9.x. Последний выпуск все еще не поддерживается CubaHAL. Данная глава будет обновлена, как только инже-

неры ST примут FreeRTOS 10.x.

23.1.Введение в концепции, лежащие в основе ОСРВ

Данный параграф приводит краткое введение в базовые концепции, лежащие в основе современных операционных систем. Опытные пользователи могут смело пропустить его.

За исключением ISR и обработчиков исключений, все организованные до сих пор примеры спроектированы так, что наши приложения состоят только из одного потока выполнения. Как правило, начиная с процедуры main(), большой и бесконечный цикл while выполнял задачи микропрограммы:

...

while(1) { doOperation1(); doOperation2();

...

doOperationN();

}

3 FreeRTOS лицензируется по модифицированной лицензии GPL 2.0, которая позволяет компаниям продавать свои устройства на базе FreeRTOS без каких-либо ограничений, если только они не изменяют код FreeRTOS и не продают/распространяют производное микропрограммное обеспечение. В этом случае, им также нужно распространять исходный код FreeRTOS, оставляя свой исходный код закрытым, если они этого хотят. Для получения дополнительной информации о модели лицензирования FreeRTOS см.

эту страницу на официальном веб-сайте (http://www.freertos.org/a00114.html).

Запуск FreeRTOS

603

Время, затрачиваемое каждой функцией doOperationX(), в целом, оценивается разработчиком, который несет ответственность за то, чтобы одна из этих функций не выполнялась слишком долго, поскольку это не позволяет другим частям микропрограммы работать правильно. Кроме того, планирование (schedules) их выполнения задается порядком вызова функций, определяющим последовательность операций, выполняемых микро-

программой. Это, безусловно, форма кооперативного (совместного) планирования

(cooperative scheduling)4, когда каждая функция соглашается с выполнением следующего действия, периодически добровольно освобождая управление.

В этой ранней форме многозадачности (multiprogramming) нет никакой гарантии, что функция не сможет установить полный контроль над ЦПУ. Разработчик приложения должен тщательно следить за тем, чтобы каждая функция выполнялась в кратчайшие сроки. В такой модели выполнения «невинный» цикл активного ожидания (busy loop) может иметь печальные последствия. Давайте рассмотрим следующий псевдокод:

uint32_t timeKeep = HAL_GetTick();

uint32_t uartData[20];

void blinkTask() {

while(HAL_GetTick() - timeKeep < 500); HAL_GPIO_TooglePin(GPIOA, GPIO_PIN_5); timeKeep = HAL_GetTick();

}

uint8_t readUART2Task() {

if(HAL_UART_Receive(&huart2, &uartData, 20, 1) == HAL_TIMEOUT) return 0;

return 1;

}

while(1) { blinkTask(); readUART2Task();

}

Данный код довольно распространен среди нескольких неопытных разработчиков встраиваемых систем, и в некоторых случаях он также является правильным. Тем не менее, этот код имеет едва уловимое странное поведение. blinkTask() спроектирована так, что она простаивает в течение 500 мс, прежде чем освобождает управление. Если в течение этого периода по интерфейсу UART поступят данные, readUART2Task() наверняка

4 Опытный пользователь отметит, что говорить о совместном планировании в данном контексте некорректно по двум основным причинам: порядок выполнения задач фиксирован («порядок выполнения» вычисляется программистом во время разработки микропрограммы) и каждая процедура не может сохранить свой контекст выполнения перед выходом, то есть кадр стека процедуры doOperationX() уничтожается при ее возврате. Как мы увидим через некоторое время, сопрограмма (co-routine) – это обобщение подпрограмм (subroutines) в многозадачных системах без вытеснения (non-preemptive multitasking systems).

Запуск FreeRTOS

604

потеряет некоторые из них5. Лучший способ написания blinkTask() выглядит следующим образом:

void blinkTask() {

if(HAL_GetTick() - timeKeep > 500) { HAL_GPIO_TooglePin(GPIOA, GPIO_PIN_5); timeKeep = HAL_GetTick();

}

}

Простое изменение этой процедуры гарантирует, что в большинстве ситуаций мы не потеряем поступающие по UART данные, только если UART не передает данные очень быстро.

Как видите, с помощью совместного планирования программисты несут большую ответственность за обеспечение того, чтобы их код не влиял на общую деятельность микропрограммы, создавая узкие места в производительности.

Добровольное освобождение потока выполнения – не единственное ограничение кода, рассмотренного до сих пор. Давайте подробнее разберем процедуру blinkTask(). В ней нам нужна глобальная переменная6, timeKeep, чтобы отслеживать глобальный счетчик тиков, увеличиваемый CubeHAL каждые 1 мс, и выполнять сравнение, чтобы проверить, истекли ли 500 мс. Это необходимо, потому что каждый раз при выходе из процедуры контекст выполнения (то есть кадр стека) извлекается из основного стека и уничтожается. Если мы не воспользуемся некоторыми неприятными трюками, предлагаемыми языком7, то невозможно выйти из функции без потери ее контекста.

Совместные подпрограммы (Continuation routines), сокращенно называемые сопрограм-

мами (co-routines или просто coroutines), представляют собой программные структуры, которые обобщают концепцию подпрограмм (subroutines) для не вытесняющей многозадачности, позволяя множественным точкам входа приостанавливать и возобновлять выполнение в определенных местах. Сопрограммы требуют специальной поддержки среды выполнения языка, и они традиционно предоставляются не только более высокоуровневыми языками, такими как Scheme, но и более распространенными языками, такими как Python и Perl. Говорят, что сопрограмма не возвращает (return), а уступает (yield) поток выполнения. Например, blinkTask() может быть переписана с использованием сопрограмм следующим образом:

1void blinkTask() {

2uint32_t timeKeep = HAL_GetTick();

3while (1) {

4if(HAL_GetTick() - timeKeep > 500) {

5HAL_GPIO_TooglePin(GPIOA, GPIO_PIN_5);

6timeKeep = HAL_GetTick();

7}

8yield; /* Передача управления другой процедуре, например, планировщику */

9}

10}

5 При высокой скорости передачи данных (baudrate) опрос UART, конечно, не совсем корректен, но здесь мы заинтересованы в этом.

6 Локальная и статическая переменные будут иметь то же действие, но без изменения концепции. 7 Которые включают в себя использование функций Си setjmp() и longjmp().

Запуск FreeRTOS

605

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

Операционная система с вытесняющей многозадачностью (preemptive multitasking) – это распределитель физических ресурсов, который позволяет выполнять несколько вычислительных задач8, каждая из которых со своим независимым стеком, назначая ограни-

ченный квант времени, англ. quantum time (также называемый временным интервалом,

англ. slice time) каждой задаче. Каждая задача имеет четко определенное временное окно, обычно большое, около 1 мс во встроенных системах, в течение которого она выполняет свои действия, перед тем как она будет вытеснена (preempted). Ядро ОСРВ определяет порядок выполнения задач, готовых к исполнению, с помощью алгоритма планирования – планировщика (scheduler) – алгоритма, который характеризует способ, которым ОС планирует выполнение задач.

Задача «перемещается» в/из ЦПУ с помощью операции переключения контекста (context switch). Переключение контекста выполняется ОС благодаря аппаратным функциям, которые мы рассмотрим далее, делающим «снимок» текущего состояния задачи путем сохранения внутренних регистров ЦПУ (PC, MSP, R0..R15, и т. д.) перед переключением на другую задачу, которая сможет снова «повторно использовать» ЦПУ в течение того же кванта времени (или даже меньше, если «она этого хочет»).

Рисунок 1: Как ОС планирует выполнение задач, назначая им фиксированное время квантования

На рисунке 1 показано, как работает вытеснение задачи для примера, рассмотренного ранее. Здесь мы предполагаем, что у нас есть только две задачи: одна для процедуры

blinkTask() и одна для readUART2Task(). ОС начинает планирование задачи blinkTask(),

которая может «использовать» ЦПУ в течение 1000 мкс (то есть 1 мс)9. По истечении времени ОС планирует выполнение readUART2Task(), которое теперь может занимать ЦПУ в течение того же кванта времени. По истечении этого периода ЦПУ перепланирует первую задачу и так далее.

8В этом и только в этом параграфе термин задача и поток будут использоваться без разбора.

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

Запуск FreeRTOS

606

На рисунке 2 показано, как память SRAM обычно организована операционной системой. Каждая задача представлена сегментом памяти, содержащим блок управления потоками (Thread Control Block, TCB), который является не более чем дескриптором, содержащим всю соответствующую информацию, касающуюся выполнения задачи, всего лишь «момент»10 перед тем, как она была вытеснена (указатель стека, счетчик команд, регистры ЦПУ и некоторые другие вещи), а также сам стек, то есть записи активации тех процедур, которые в данный момент вызываются в стеке потоков. Перемещаясь между несколькими потоками, благодаря операциям переключения контекста, ОС гарантирует одинаковое время выполнения для всех потоков, создавая впечатление, что действия микропрограммы выполняются параллельно.

Рисунок 2: Как память организована в несколько задач ОС

Операционные системы реального времени (ОСРВ) – это ОС, способные предложить поня-

тие многозадачности (или даже лучше, многопоточности, как показано в Примечании 1), обеспечивая при этом оклик в течение определенных временных ограничений, часто называемых крайними сроками (deadlines). Откликами в реальном времени часто считаются промежутки порядка миллисекунд, а иногда и микросекунд. Система, не указанная как работающая в режиме реального времени, обычно не может гарантировать

10 Это совсем не так, поскольку перед выполнением задачи происходит несколько других вещей. Однако подробное объяснение этих аспектов выходит за рамки данной книги. Обратитесь к книгам Джозефа Ю, если вам интересно углубиться в процесс переключения контекста в микроконтроллерах на базе Cortex-M.

Запуск FreeRTOS

607

отклик в течение какого-либо периода времени, хотя могут быть указаны фактическое или ожидаемое время отклика. Операционные системы общего назначения (такие как Linux, Windows и MacOS) не могут быть операционными системами реального времени (несмотря на то что существуют некоторые их производные версии, особенно Linux, разработанные для приложений реального времени) по двум простым причинам: разбиение на страницы (pagination) и подкачка (swapping). Первая позволяет сегментировать память задач в виде небольших порций, называемых страницами (pages), которые могут быть разбросаны в ОЗУ и наложены (aliased) из MMU, создавая иллюзию того, что процесс может управлять всем адресным пространством 4 ГБ (даже если компьютер не предоставляет такое количество SRAM). Последняя позволяет подгружать/выгружать (swap-in/swap-out) эти «неиспользуемые» страницы на внешнем (и более медленном) запоминающем устройстве (обычно на жестком диске). Эти две функции изначально недетерминированные и не позволяют ОС отвечать на запросы в короткие и исчисляемые сроки.

ОСРВ позволяет использовать первую версию функции blinkTask(), сводя к минимуму влияние цикла активного ожидания на процесс передачи через UART11. Однако, как мы увидим далее в этой главе, обычно ОСРВ также предоставляет нам инструменты, позволяющие полностью избежать циклов активного ожидания: используя программные таймеры, можно обратиться к ОС с просьбой перепланировать blinkTask() только тогда, когда заданное количество времени истекло. Кроме того, ОСРВ также предоставляет способы добровольного освобождения управления, когда мы знаем, что совершенно бесполезно ждать операцию, которая будет выполнена другой задачей (или если мы ожидаем асинхронного события).

Мы только что сказали, что ОСРВ дает возможность добровольно освободить управление к другим потокам. Но что, если одна задача не хочет освободить его? Например, первое освобождение процедуры blinkTask() может установить полный контроль над ЦПУ до 500 мс в худшем случае, что является весьма огромным временем, учитывая типовой временной интервал в 1 мс. Таким образом, у кого есть возможность выполнить переключение контекста? Невозможно «перейти» к другим программным инструкциям (переключение контекста является своего рода переходом goto к другой программной инструкции) без потери одной важной информации: значения самого счетчика команд.

Переключение контекста требует существенной помощи от аппаратной части. В Главе 7 мы увидели, что прерывания и исключения являются источником многозадачности. То, как они обрабатываются ядром Cortex-M, позволяет перейти к обработчику исключений без потери текущего контекста выполнения. Используя выделенный аппаратный таймер, обычно SysTick, ОСРВ применяет периодическое прерывание, генерируемое событием переполнения, для переключения контекста. Этот таймер сконфигурирован на переполнение (или опустошение значения в случае SysTick, который является таймером нисходящего отсчета) каждые 1 мс. ОСРВ затем перехватывает исключение и сохраняет текущий контекст выполнения в TCB, передавая управление следующей задаче в списке

11 Это не означает, что с помощью ОСРВ мы можем писать плохой код, не влияя на общую производительность. Это означает только то, что настоящий вытесняющий планировщик может гарантировать более высокую степень многозадачности, обеспечивая всем потокам одинаковый временной интервал ЦПУ. Пока что мы не будем касаться приоритетов задач, которые мы увидим позже.

Запуск FreeRTOS

608

планирования, восстанавливая ее контекст выполнения и выходя из прерывания таймера. Прерванные потоки не будут знать, что это произошло12.

В свете соображений, которые мы рассмотрели до этого момента, рисунок 1 должен быть обновлен до того, который показан на рисунке 3, где также учитывается время,

затраченное ОС при выполнении переключения контекста. Переключения контекста

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

ний контекста в секунду.

Рисунок 3: Влияние переключения контекста на планирование задач

Прежде чем мы сможем приступить к практическим действиям с ОСРВ, нам нужно объяснить только одну последнюю концепцию. Как насчет случая, когда задача хочет добровольно выйти из управления? В этом случае часто ОСРВ используют инструкцию вызова супервизора SVC (SuperVisor Call), реализованную процессорами Cortex-M, которая вызывает обработчик исключения SVC_Handler, или принудительно вызывают исключение PendSV. Объяснение того, когда они используют одно исключение, а когда другое выходит за рамки книги, и это также является проектным решением производителя ОС. Для получения дополнительной информации обратитесь к книгам Джозефа Ю13, если вы заинтересованы в углублении в данные темы.

12Однако все это не может соответствовать тому, что на самом деле делает ОСРВ. Ситуация здесь более сложная, и она связана с конкретными аппаратными архитектурами и с тем, как прерывания назначаются по приоритетам. Во время выполнения обработчика прерываний другое прерывание с более высоким приоритетом может приостановить выполнение текущего прерывания, как показано в Главе 7. Но когда это происходит, ЦПУ не может переключиться в режим потока (который является обычным режимом, когда выполняется нормальный код) путем переключения задачи без предварительного выхода из всех прерываний (которые выполняются в режиме обработчика – особом режиме, предоставляемым ядром Cortex- M во время обработки исключений). Это означает, что если выполняется IRQ SysTick, когда активен другой IRQ, обработчик исключения SysTick не может выполнить переключение контекста (то есть передать управление другой задаче, выполняющейся в режиме потока), поскольку другой код, работающий в режиме обработчика, был прерван и должен завершить свое выполнение. Обычно это решается путем переноса действующей операции переключения контекста в обработчик PendSV, который является исключением, сконфигурированным для работы с самым низким приоритетом. Тем не менее, это только один из способов реализации переключения контекста. Если вы заинтересованы в углублении в данную тему, то вам следует ознакомиться с исходным кодом или документацией вашей ОСРВ.

13http://amzn.to/1P5sZwq