FreeRTOS: введение


    Здравствуйте. В короткой серии постов я постараюсь описать возможности, и подходы работы с одной из наиболее популярной и развивающейся РТОС для микроконтроллеров – FreeRTOS. Я предпологаю базовое знакомство читателя с теорией многозадачности, о которой можно почитать в одном из соседних постов на Хабре или ещё где-то.
    Ссылки на остальные части:
    FreeRTOS: межпроцессное взаимодействие.
    FreeRTOS: мьютексы и критические секции.

    Зачем все это? Или введение в многозадачные системы, от создателей FreeRTOS.

    Традиционно существует 2 версии многозадачности:
    • «Мягкого» реального времени(soft real time)
    • «Жесткого» реального времени(hard real time)

    К ОСРВ мягкого типа можно отнести наши с Вами компьютеры т.е. пользователь должен видеть, что, например, нажав кнопку с символом, он видит введенный символ, а если же он нажал кнопку, и спустя время не увидел реакции, то ОС будет считать задачу «не отвечающей»( по аналогии с Windows — «Программа не отвечает»), но ОС остается пригодной для использования. Таким образом, ОСРВ мягкого времени просто определяет предполагаемое время ответа, и если оно истекло, то ОС относит таск к не отвечающим.

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

    А все таки зачем?

    Если у Вас есть устройство с нетривиальной логикой синхронизации обмена данными между набором сенсоров, если Вам действительно нужно гарантировать время отклика, и наконец-то если Вы думаете, что система может разростись, но не знаете насколько, то РТОС Ваш выбор.

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

    Работа с тасками(или задачами, процессами).

    Для начала приведу несколько определений, для того чтобы внести ясность в дальнейшие рассуждения:

    "Операционные системы реального времени (ОСРВ(RTOS)) предназначены для обеспечения интерфейса к ресурсам критических по времени систем реального времени. Основной задачей в таких системах является своевременность (timeliness) выполнения обработки данных".
    "FreeRTOS — многозадачная операционная система реального времени (ОСРВ) для встраиваемых систем. Портирована на несколько микропроцессорных архитектур.
    От хабраюзера andrewsh, по поводу лицензии: разрешено не публиковать текст приложения, которое использует FreeRTOS, несмотря на то, что OS линкуется с ним. Исходники самой же RTOS должны всегда прикладываться, изменения, внесённые в неё — тоже.".


    FreeRTOS написана на Си с небольшим количеством ассемблерного кода(логика переключения контекста) и ее ядро представлено всего 3-мя C файлами. Более подробно о поддерживаемых платформах можно почитать на официальном сайте.

    Перейдем к делу.
    Любой таск представляет собой Си функцию со следующим прототипом:

    void vTask( void *pvParametres );

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

    Тело таска не должно содержать явных return; конструкций, и в случае если таск больше не нужен, его можно удалить с помощью вызова API функции. Следующий листинг, демонстрирует типичный скелет таска:
    void vTask( void *pvParametres) {
        /* Данный фрагмент кода будет вызван один раз, перед запуском таска.
           Каждый созданный таск будет иметь свою копию someVar.
           Кроме объявления переменных, сюда можно поместить некоторый инициализационный код.    
        */
        int someVar;
    
        // Так как каждый таск - это по сути бесконечный цикл, то именно здесь начинается тело таска.
        for( ;; ) {
            // Тело таска
        }
        
        // Так как при нормальном поведении мы не должны выходить из тела таска, то в случае если это все таки произошло, мы удаляем таск.
        // Функция vTaskDelete принимает в качестве аргумента хэндл таска, который стоит удалить.
        // Вызов внутри тела таска с параметром NULL,удаляет текущий таск
        
        vTaskDelete( NULL );
    }

    Для создания таска, и добавления ее в планировщик используется специальная API функция со следующим прототипом:
    portBASE_TYPE xTaskCreate( pdTASK_CODE pvTaskCode,  
                               const signed portCHAR * const pcName,  
                               unsigned portSHORT usStackDepth,  
                               void *pvParameters,  
                               unsigned portBASE_TYPE uxPriority,  
                               xTaskHandle *pxCreatedTask  
                             );


    pvTaskCode – так как таск – это просто Си функция, то первым параметром идет ее значение.

    pcName – имя таска. По сути это нигде не используется, и полезно только при отладке с соответствующими плагинами для IDE.

    usStackDepth – так как каждый таск – это мини подпрограмма, со своим стэком, то данный параметр отвечает за его глубину. При скачивании RTOS и разворачивания системы для своей платформы, вы получаете файл FreeRTOSConfig.h настройкой которого можно конфигурировать поведение самой ОС. В данном файле также объявлена константная величина configMINIMAL_STACK_SIZE, которую и стоит передавать в качестве usStackDepth с соответствующим множителем, если это необходимо.

    pvParameters – при создании, каждый таск может принимать некоторые параметры, значения, или ещё что-то что может понадобиться внутри тела самого таска. С точки зрения инкапсуляции, этот подход наиболее безопасный, и в качестве pvParameters стоит передавать, например, некоторую структуру, или NULL, если ничего передавать не нужно.

    uxPriority – каждый таск имеет свой собственный приоритет, от 0(min) до (configMAX_PRIORITIES – 1). Так как, по сути, нет верхнего предела для данного значения, то рекомендуется использовать как можно меньше значений, чтобы не было дополнительно расхода RAM на данную логику.

    pxCreatedTask — хэндл созданного таска. При создании таска, опционально можно передать указатель на хэндл будующего таска, для последующего управления работой самого таска. Например, для удаления определенного таска.

    Данная функция возвращает pdTRUE, в случае успешного создания таска, или errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY, в случае если размер стэка был указан слишком большим, т.е. недостаточно размера хипа для хранения стэка таска, и самого таска.

    На следующем листинге я привел, короткий пример законченной программы, которая создает 2 таска, каждый из которых мигает светодиодом:
    void vGreenBlinkTask( void *pvParametrs ) {
    	for( ;; ) {
    		P8OUT ^= BIT7;
    		
    		// Выполнить задержку в 700 FreeRTOS тиков. Величина одного тика задана в FreeRTOSConfig.h и как правило составляет 1мс.
    		vTaskDelay( 700 );
    	}
    }
    
    void vRedBlinkTask( void *pvParametrs ) {
    	for( ;; ) {
    		P8OUT ^= BIT6;
    		
    		// Выполнить задержку в 1000 FreeRTOS тиков. Величина одного тика задана в FreeRTOSConfig.h и как правило составляет 1мс.
    		vTaskDelay( 1000 );
    	}
    }
    
    void main(void) {
    	// Инициализация микроконтроллера. Данный код будет у каждого свой.
    	vInitSystem();
    
    	// Создание тасков. Я не включил код проверки ошибок, но не стоит забывать об этом!
    	xTaskCreate( &vGreenBlinkTask,
    	             (signed char *)"GreenBlink",
    	             configMINIMAL_STACK_SIZE, 
    	             NULL,
    	             1,
    	             NULL );
    	xTaskCreate( &vRedBlinkTask, 
    	             (signed char *)"RedBlink",
    	             configMINIMAL_STACK_SIZE,
    	             NULL,
    	             1,
    	             NULL );
    
    	// Запуск планировщика т.е начало работы тасков.
    	vTaskStartScheduler();
    	
    	// Сюда стоит поместить код обработки ошибок, в случае если планировщик не заработал. 
    	// Для примера я использую просто бесконечный цикл.
    	for( ;; ) { }
    }


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

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 30

      +1
      Отличная статья, интересно будет прочитать про работу с прерываниями.
        0
        С прерываниями там все очень умно и шоколадно устроено. Можно гибко настроить, каким прерываниям можно вытеснять код ядра, а каким — нельзя. При выходе из прерывания можно принудительно переключить контекст, то есть вернуться не в ту задачу, в которая была прервана прерыванием, а в ту, у которой приоритет выше. Например, пришел в UART символ, на него сработало прерывание, и при выходе из прерывания можно сразу принудительно переключить контекст на задачу, которая обрабатывает прием. В общем, FreeRTOS сделана с заботой о нас, о программистах. Просто бери готовенькое на блюдечке.
          0
          Как-то так:
          image

          Константы configKERNEL_INTERRUPT_PRIORITY и configMAX_SYSCALL_INTERRUPT_PRIORITY также настраиваются в файле FreeRTOSConfig.h. Все свежие релизы FreeRTOS поддерживают вложенность и аппаратные приоритеты прерываний (на тех архитектурах, которые предусматривают настройку приоритета аппаратных прерываний).
          0
          ИНтересно: какой там планировщик?
            0
            Насколько помню, там планировщик с несколькими очередями, по очереди на каждый приоритет. Количество приоритетов настраивается в конфигурационном файле (это константа). И вроде бы обычное приоритетное планирование. Смею предположить, что O(1). Но гарантий дать не могу.

            FreeRTOS помимо классического псевдо-параллелизма с жирным стеком для каждой задачи позволяет работать с сопрограммами, которые исполняются кооперативно. Причем сопрограммы в данном случае полноценные, в отличии от имитации их за счет switch-case, как у Эдама Данкелса в Contiki.
              0
              Шедулер (планировщик) отвечает за принятие решения о переключении контекста выполнения (т. е. какой задаче передать ресурс процессора). Решение это принимается на основе приоритета задач, а также на основе подсчета времени, которое задача провела в состоянии ожидания (чем дольше задача бездействовала, тем больше у неё прав на запуск). Приоритет назначается при создании задачи (uxPriority, упомянутый в статье), и может быт изменен или считан в ходе выполнения программы через специальные функции API FreeRTOS (vTaskPrioritySet(), uxTaskPriorityGet()).

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

              Задачи, которые имеют равный приоритет, имеют одинаковые права на процессор, и переключаются на выполнение по очереди. Задачи, которые имеют более высокий приоритет, вытесняют (preempt) все задачи, которые имеют приоритет меньше. Высокоприоритетные задачи могут принудительно отдать свое процессорное время (вызовом taskYIELD(), если они завершили свою нужную работу), или заблокироваться в ожидании специального события, позволяя тем самым менее приоритетным задачам возможность запуститься. Таким образом во FreeRTOS для переключения задач используется алгоритм приоритетного (с фиксацией приоритета) планирования запуска задач с вытеснением, вытесняющая многозадачность (Fixed Priority Preemptive Scheduling). Однако имеется возможность применить и другой алгоритм переключения задач — кооперативная многозадачность (Co-operative Scheduling). У каждого алгоритма свои достоинства и недостатки, и соответственно разная область применения.

              Для обмена данными задача-задача, задача-прерывание, прерывание-задача во FreeRTOS имеются специально предусмотренные объекты — очереди. При обмене данными или событиями предусмотрена синхронизация высокоприоритетной задачи (или прерывания) и менее приоритетной задачи. Синхронизация происходит с помощью блокировки на очереди или семафоре, при этом можно задать время блокировки (таймаута процесса передачи). Для атомарных операций есть возможность создавать критические секции кода.
                0
                Эмс… Насколько мне известно, эта же схема с учётом времени ожидания — это не realtime технология, это sharedtime. Как при таком планировании обеспечиваются гарантии времени при реакции на события?
                  0
                  При задании времени ожидания, можно указать, не ждать фиксированное время, а просто находится в ожидании т.е как только пришел сигнал, сразу разблокироваться.
                    0
                    FreeRTOS- это именно system for hard real time, то есть система, СПЕЦИАЛЬНО СПРОЕКТИРОВАННАЯ для того, чтобы обеспечить поддержку ЖЕСТКО ГАРАНТИРОВАННОГО времени отклика на внешние события. Время ожидания в ней действительно учитывается — при принятии решения шедулером о переключении задач, имеющий одинаковый относительно друг друга приоритет. Ни более ни менее. Гарантия времени реакции на события достигается настройкой приоритетов задач и прерываний.
                +3
                Для меня лично будет очень интересно увидеть статью про работу FreeRTOS+LwIP при активной передаче UDP данных.
                  0
                  На каком процессоре использовался живьём? Как с гарантией latency? Какие характеристики шедулера?
                  Это самое интересное :)
                    0
                    Использовал на MSP430F5438, с latency в документах по этой РТОС ничего не видел, по другой пишут, что порядка 40мс переключение контекста, здесь думаю примерно также. Глубоко внутрь планировщика не копал т.к. нужно было просто применять, и знаю только, что есть возможность работать в режиме preemptive(мой случай) и cooperative.
                      0
                      > 40мс переключение контекста
                      Не слишком ли это много для RTOS? Может мкс (микросекунд)?
                      Кто нибудь тестил low-latency ядро линукс по этому же параметру?
                        0
                        Это время конфигурируется константой времени компиляции configTICK_RATE_HZ в файле FreeRTOSConfig.h, которая представляет из себя частоту в Герцах. Например, если configTICK_RATE_HZ установлена в 100 (Гц), то длительность слайса (тика времени, через которые происходит переключение контекста) составит 10 мс.

                        Во FreeRTOS переключение контекста выполнения реально может происходить намного чаще, чем каждый слайс времени — при окончании аппаратного прерывания, при добровольной передаче контекста (вызовом taskYIELD()), при наступлении событий.
                          0
                          > каждый слайс времени
                          Это эквивалентно обновлению TickCount в Windows (16 мс минимум)? Если да, то как я понимаю это не совсем «latency» в контексте разговора про отзывчивость ядра и в контексте low-latency- и rt- ядер линукс. Как я понимаю TickCount для апишной функции винды обновляется редко, но контекст между процессами переключается чаще.
                          Я путаюсь?
                            0
                            добавлю ещё один момент, FreeRTOS очень не любит циклы, пусть даже и не бесконечные, но которые исполнются дого, напримр есть инфа, которую нужно обработать, ну и вы включаете какой-то алгоритм в цикл while(условие):
                            void TASK_YYY( void *pvParameters)
                            {
                            	while( 1 ){
                            		while( value != xxx ){
                            			// алгоритм
                            		}
                            	}
                            }
                            

                            В таких местах могут быть зависоны, по крайней мере на IAR STM32 бывали, и нужно делать так:
                            void TASK_YYY( void *pvParameters)
                            {
                            	while( 1 ){
                            		while( value != xxx ){
                            			// алгоритм
                            			taskYIELD();
                            		}
                            	}
                            }
                            

                            Т.е. после каждого перегона алгоритма нужно сделать переключение задачи, а то могут быть проблемы и вообще нужно избегать таких вот «ожидающих» состояний, например когда ожидаются данные с периферии низко скоростного интерфейса
                      +1
                      А мне вот отечественный проект один нравится, имя которому Embox. Это, конечно, решение не для самых «бедных» архитектур вроде микроконтроллеров нижнего ценового сегмента, но система заслуживает своего внимания. Код очень качественный, можно легко разобраться с любым компонентом данной ОС. Архитектура модульная, и, в принципе, не возбраняется собрать из этой мозаики что-то свое, подходящее только вам. Здесь в наличии несколько планировщиков, несколько менеджеров памяти на разные случаи жизни, потоки и псевдопроцессы, таймеры и механизмы обработки прерываний. Помимо всего сверху навешена система драйверов, файловая система, стандартная библиотека и набор приложений. Естественно, немного разобравшись, ото всего этого изобилия можно отказаться, получив вполне миниатюрную систему для вашей железки. Врать не буду, нигде не использовал, но активно игрался с x86 версией под эмулятором.
                        0
                        На данный момент с этим работать пересатал, как вернусь обязательно попробую. Я из отечественного немного работал с scmRTOS, которая написана на C++, конечно сверхвозможностей, как у Вашей ОС там нет, но как пример, что можно писать и на C++ пойдет, а также, она тоже очень небольшая, т.е. можно и внутри поковыряться.
                          0
                          Вы немного не поняли, это не моя ОС. А что до отечественных, тут поле довольно широкое. Не все, конечно, заслуживает внимания. Слышал еще про iRTOS; не помню, что, но что-то меня не впечатлило в ней. Может за последнее время что-то изменилось.
                          0
                          У Embox что-то мало поддерживаемых платформ. У FreeRTOS платформ намного больше (только под GCC я насчитал аж 19 наименований). Кроме того, есть коммерческие форки — OpenRTOS и SafeRTOS. Поэтому для разработчика FreeRTOS гораздо привлекательнее. Можно сначала потренироваться на кошечках (FreeRTOS), а потом при необходимости прикупить тяжелую артиллерию (OpenRTOS и SafeRTOS).
                            0
                            Приятно слышать это как одному из разработчиков. :)

                            Будет ли сообществу интересен материал, подобный этому, но про Embox?
                              +1
                              Конечно будет, к тому же, можно сделать один простой ход, который позволит вам собрать еще большую аудиторию.

                              Уж очень много человек одержимы идеей влиться в OSDEV-сцену, и среди пользователей хабрахабра таких также не мало. Часть одержимых идеей пытается прыгнуть чуть выше и пишет для таких же как они сами уроки. В итоге и появляются очередные протуберанцы о работе с защищенным режимом на ассемблере вместо основной темы — разработки ядра операционной системы.

                              У вас в багаже вполне переносимый код, достаточное множество реализованных алгоритмов, документация, хоть и изобилующая опечатками, страдающая орфографией, но все же вполне качественная. Вы могли бы показать сообществу, как реально устроена операционная система, с примерами кода. Пожевать, чтобы все могли это проглотить. Ну например, взять менеджер физической памяти и рассказать, как он работает, что такое buddy-алгоритм, чем хорош, или переходя уже к более высокому уровню — поведать о механизме slab. Рассказать про виртуальную память на примере своей ОС (хотя насколько помню тут у вас не все еще есть), про виртуальную файловую систему, драйверы, планирование, загрузку ELF. При достаточном качестве материалов они бы были мегапопулярны.

                              Также, статьи об использовании ОС в своих проектах могут быть очень интересными. Можно преподносить их подобно этому материалу про FreeRTOS. А можно комбинировать рассмотрение внутренностей ОС вместе с применением ее на практике. Например, рассказываете как сделать в вашей ОС что-то конкретное и добавляете немного информации о том, как же оно устроено. Чтобы у людей не складывалось ощущение черного ящика.
                            +1
                            Спасибо за статью, материал-то интересный! Хабрахабру нужен большой пинок под зад, ну или серия пинков, которая откроет дорогу новым качественным материалам.
                              +3
                              Круто, еще бы хотелось увидеть примеры, когда нужна РТОС. Ну т.е. ситуация, когда я со всей неотвратимостью должен понять — да тут без РТОС не обойтись.

                              Ну и про стек я лично ничего не знаю — если это не совсем тривиальная вещь, то опиши ПЛЗ — зачем каждой таске свой стек и т.д.
                                0
                                Пример из жизни: было устройство 1 МК, 5 сенсоров, с разными протоколами общения, разной частотой пересылки данных. Привычный подход с использованием while(1){} приносит те еще проблемы по синхронизации приема-обработки-передачи данных, а также по сопровождению и написанию самого кода.

                                Надуманный пример: на начальном этапе проектирования, заранее неизвестно, о будущем масштабе системы и хотелось бы предусмотреть ее будущий рост, вот РТОС, как раз позволяет сделать это достаточно прозрачно.

                                А применять ее не стоит в самых тривиальных случаях, например, есть простая логика: контроллер-сенсор, которую можно решить привычным способом, так как KISS никто не отменял :-).

                                Стэк по сути область памяти выделенная из кучи для каждого таска т.к каджый таск — мини подпрограмма, то соответственно имеет собственную область памяти.
                                  0
                                  Стек — вполне тривиальная вещь с точки зрения механизма, но польза от этого простого механизма — огромная.

                                  Если говорить, не касаясь многозадачности:
                                  1. При вызове подпрограмм в стек кладутся их формальные параметры, а также адрес возврата из подпрограммы. За счет этого подхода мы можем вызывать функции и всегда возвращаться туда, откуда мы их вызывали;
                                  2. Также, на стеке выделяется память для локальных объектов функции;
                                  3. Стек используется для временного сохранения данных из регистров процессора, потому что регистров обычно — не много.

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

                                  Есть подходы, когда регистровый контекст сохраняется вручную, без использования аналогов инструкций push и pop, но это встречается довольно редко. Тем более, многие архитектуры умеют делать это (сохранение части регистрового контекста в стеке на момент обработки прерывания) почти на автомате.
                                  +2
                                  Автору: статья нужная и интересная. Неплохо было бы вначале немного написать — "зачем все это надо?". Я имею в виду — почему появилась FreeRTOS, чем она отличается от обычных операционных систем и от обычной программы, которую пишет программист? Что она вообще дает программисту? В оригинальном руководстве «Using The FreeRTOS Real Time Kernel» Ричарда Барри этот вопрос рассматривается очень хорошо.
                                    0
                                    Добавил краткое введение.
                                    0
                                    Прошу исправить абзац насчёт лицензии. Разрешено не публиковать текст приложения, которое использует FreeRTOS, несмотря на то, что OS линкуется с ним. Исходники самой же RTOS должны всегда прикладываться, изменения, внесённые в неё — тоже.
                                      0
                                      The exception permits the source code of applications that use FreeRTOS solely through the API published on this website to remain closed source, thus permitting the use of FreeRTOS in commercial applications without necessitating that the whole application be open sourced. The exception can only be used if you wish to combine FreeRTOS with a proprietary product and you comply with the terms stated in the exception itself.

                                      As a special exception, the copyright holder of FreeRTOS gives you permission to link FreeRTOS with independent modules that communicate with FreeRTOS solely through the FreeRTOS API interface, regardless of the license terms of these independent modules, and to copy and distribute the resulting combined work under terms of your choice, provided that

                                      1. Every copy of the combined work is accompanied by a written statement that details to the recipient the version of FreeRTOS used and an offer by yourself to provide the FreeRTOS source code (including any modifications you may have made) should the recipient request it.
                                      2. The combined work is not itself an RTOS, scheduler, kernel or related product.
                                      3. The independent modules add significant and primary functionality to FreeRTOS and do not merely extend the existing functionality already present in FreeRTOS.


                                      Only users with full accounts can post comments. Log in, please.