Отладка многопоточных программ на базе FreeRTOS

  • Tutorial

image

Отладка многозадачных программ дело не простое, особенно если ты сталкиваешься с этим впервые. После того, как прошла радость от запуска первой задачи или первой демо программы, от бесконечно волнительного наблюдения за светодиодами, каждый из которых моргает в своей собственной задаче, наступает время, когда ты осознаешь, что довольно мало понимаешь (вообще не врубаешься) о том, что на самом деле происходит. Классика жанра: «Я выделил целых 3КБ операционной системе и запустил всего 3 задачи со стеком по 128Б, а на четвертую уже почему-то не хватает памяти» или «А сколько вообще стека я должен выделить задаче? Столько достаточно? А столько?». Многие решают данные задачи путем проб и ошибок, поэтому в этой статье я решила объединить большинство моментов, которые, в настоящее время, значительно упрощают мне жизнь и позволяют более осознанно отлаживать многопоточные программы на базе FreeRTOS.

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

В данной статье я расскажу о следующих моментах:


  1. Настройка OpenOCD для работы с FreeRTOS.
  2. Не забываем включать хуки.
  3. Статическое или динамическое выделение памяти?
  4. Сказ, о параметре configMINIMAL_STACK_SIZE.
  5. Мониторинг использования ресурсов.

Настройка OpenOCD для работы с FreeRTOS


Первое, с чем можно столкнуться при использовании FreeRTOS — это отсутствие какой-либо полезной информации в окне Debug:

image

Выглядит это максимально грустно. К счастью, OpenOCD поддерживает отладку FreeRTOS, просто его нужно правильно настроить:

  1. Добавить в проект файл FreeRTOS-openocd.c
  2. Добавить флаги линкеру (Properties > C/C++ Build > Settings > Cross ARM C++ Linker > Miscellaneous > Other linker flags):

    -Wl,--undefined=uxTopUsedPriority
  3. Добавить флаги отладчику (Run > Debugs configurations > Debugger > Config options):

    -c "$_TARGETNAME configure -rtos auto"
  4. Снять галочку Run > Debugs configurations > Startup > Set breakpoint at main.

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

image

Не забываем включать хуки


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

image

Например, на картинке выше, мы видим, что ошибка возникла во время выполнения задачи YellowLedTask. Первое, что мы делаем, это в дебаге начинаем шагать строчка за строчкой по бесконечному циклу задачи, чтобы уточнить место падения. Допустим, мы узнали, что программа ломается во время выполнения функции dummy() (кстати, есть способ сразу понимать, в какой функции мы сломались, об этом можно прочитать в этой статье). Мы начинаем проверять тело функции, нет ли там ошибки или опечатки. Проходит час, глаз начинает дергаться, а мы уверены в том, что функция написана корректно так же твердо, как мы уверены в том, что стул на котором мы сидим существует. Так в чем же дело? А дело в том, что возникшая ошибка может не иметь ничего общего с вашей функцией, а проблема заключается именно в работе ОС. И тут нам на помощь приходят хуки.

Во FreeRTOS существуют следующие хуки:

/* Hook function related definitions. */
#define configUSE_IDLE_HOOK                     0
#define configUSE_TICK_HOOK                     0
#define configCHECK_FOR_STACK_OVERFLOW          2
#define configUSE_MALLOC_FAILED_HOOK            1
#define configUSE_DAEMON_TASK_STARTUP_HOOK      0

Самыми важными, в рамках отладки программы, являются configCHECK_FOR_STACK_OVERFLOW и configUSE_MALLOC_FAILED_HOOK.

Параметр configCHECK_FOR_STACK_OVERFLOW может быть включен значением 1 или 2 в зависимости от того, какой метод детектирования переполнения стека вы хотите использовать. Подробнее об этом можно почитать здесь. Если вы включили этот хук, то вам нужно будет определить функцию
void vApplicationStackOverflowHook(TaskHandle_t xTask, signed char *pcTaskName), которая будет выполняться каждый раз, когда выделенного для задачи стека будет не хватать для ее работы, а главное вы будете видеть ее в стеке вызовов конкретной задачи. Таким образом, для решения возникшей проблемы нужно будет лишь увеличить размер стека, выделенный для задачи.

vApplicationStackOverflowHook
void vApplicationStackOverflowHook(TaskHandle_t xTask, char* pcTaskName)
{
    rtos::CriticalSection::Enter();
    {
        while (true)
        {
            portNOP();
        }
    }
    rtos::CriticalSection::Exit();
}

Парамерт configUSE_MALLOC_FAILED_HOOK включается 1, как и большинство конфигурируемых параметров FreeRTOS. Если вы включили этот хук, то вам нужно будет определить функцию void vApplicationMallocFailedHook(). Эта функция будет вызвана тогда, когда свободного места в куче, выделенной для FreeRTOS, окажется недостаточно, для размещения очередной сущности. И, опять же, главное, что мы будем видеть все это в стеке вызовов. Следовательно, все что нам нужно будет сделать для решения данной проблемы — это увеличить размер кучи, выделенной для FreeRTOS.

vApplicationallocFailedHook
void vApplicationMallocFailedHook()
{
    rtos::CriticalSection::Enter();
    {
        while (true)
        {
            portNOP();
        }
    }
    rtos::CriticalSection::Exit();
}

Теперь, если мы запустим нашу программу еще раз, то при ее падении в hard_fault_handler() мы увидем причину этого падения в окне Debug:

image

Кстати, если вы когда-либо находили интересное применение configUSE_IDLE_HOOK, configUSE_TICK_HOOK или configUSE_DAEMON_TASK_STARTUP_HOOK, то было бы очень интересно почитать об этом в комментариях)

Статическое или динамическое выделение памяти?


Итак, мы разобрались с тем, как следить за переполнением стека и кучи во FreeRTOS, а теперь пришло время поговорить о вечном — о памяти.

В этом параграфе мы рассмотрим следующие параметры FreeRTOS:

/* Memory allocation related definitions. */
#define configSUPPORT_STATIC_ALLOCATION         0
#define configSUPPORT_DYNAMIC_ALLOCATION        1
#define configTOTAL_HEAP_SIZE                   100000
#define configAPPLICATION_ALLOCATED_HEAP        0

Во FreeRTOS память для создания задач, семафоров, таймеров и других объектов RTOS может выделяться как статически (configSUPPORT_STATIC_ALLOCATION), так и динамически (configSUPPORT_DYNAMIC_ALLOCATION). Если вы включаете динамическое выделение памяти, то вам необходимо также указать размер кучи, который может использовать RTOS (configTOTAL_HEAP_SIZE). Кроме того, еслы вы хотите, чтобы куча располагалась в каком-то определенном месте, а не автоматически расположена в памяти линкером, то вам необходимо включить параметр configAPPLICATION_ALLOCATED_HEAP и определить массив uint8_t ucHeap[configTOTAL_HEAP_SIZE]. И не забывайте, что для динамического выделения памяти, в папку с файлами FreeRTOS нужно добавить файл heap_1.c, heap_2.c, heap_3.c, heap_4.c или heap_5.c в зависимости от того, какой вариант менеджера памяти вам больше подходит.

Для того, чтобы оценить, сколько памяти вы можете отдать куче FreeRTOS, после сборки проекта нужно посмотреть на размер секции .bss. Она отображает размер RAM необходимый для хранения всех статических переменных. Например, у меня контроллер с оперативной памятью на 128КБ, я отдала FreeRTOS 50КБ и после сборки проекта секция .bss занимает 62304Б. Это значит, что у меня в проекте статических переменных на 12304 байт + 50000 байт статически выделено для кучи ОС. Нужно помнить, что парочку килобайт нужно захабарить для стека main() и в итоге мы получаем, что кучу FreeRTOS можно еще увеличить на (128000 — 62304 — 2000) байта.

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

Что касается моего мнения, то на данном этапе развития я не вижу смысла в использовании статического выделения памяти, поэтому в приведенном выше конфиге статическое выделение памяти выключено. И вот почему:

  1. Зачем самостоятельно выделять буфер для стека и структуру StaticTask_t, если операционная система поддерживает целых 5 различных менеджерей памяти на любой вкус и цвет, которые сами разберутся где, что и как создать, да еще и сообщат, если у них что-то не получилось? В частности, для большинства программ под микроконтроллеры более чем полностью подходит heap_1.c
  2. Вам может понадобиться какая-нибудь сторонняя библиотека, написанная очень оптимально и емко, но использующая внутри себя malloc(), calloc() или new[](). И что же делать? Отказаться от нее в пользу менее оптимальной (это еще если выбор есть)? А можно просто использовать динамическое выделение памяти с heap_2.c или heap_4.c. Единственное, что вам нужно будет сделать — это переопределить соответствующие функции, чтобы выделение памяти происходило средствами FreeRTOS в предоставленной ей куче:

    code snippet
    void* malloc(size_t size) {
        return pvPortMalloc(size);
    }
    
    void* calloc(size_t num, size_t size) {
        return pvPortMalloc(num * size);
    }
    
    void free(void* ptr) {
        return vPortFree(ptr);
    }
    
    void* operator new(size_t sz) {
        return pvPortMalloc(sz);
    }
    
    void* operator new[](size_t sz) {
        return pvPortMalloc(sz);
    }
    
    void operator delete(void* p) {
        vPortFree(p);
    }
    
    void operator delete[](void* p) {
        vPortFree(p);
    }
    

В своих проектах я использую только динамическое выделение памяти с heap_4.c, отдавая под кучу ОС максимально возможный объем памяти, и всегда переопределяю функции malloc(), calloc(), new() и т. д. вне зависимости от того, используются они в настоящий момент или нет.

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

Сказ, о параметре configMINIMAL_STACK_SIZE


Значение параметра configMINIMAL_STACK_SIZE исчисляется НЕ в байтах, а в словах! Причем размер слова меняется от одного порта ОС к другому и он определен в файле portmacro.h дефайном portSTACK_TYPE. Например, в моем случае, размер слова составляет 4 байта. Таким образом, то, что параметр configMINIMAL_STACK_SIZE в моей конфигурации равен 128 означает, что минимальный размер стека для задачи равен 512 байт.

У меня все.

Мониторинг использования ресурсов


Как было бы замечательно иметь ответы на такие вопросы, как:

  • Адекватно ли я выбрал размер стека для задачи? Не слишком ли много? А может слишком мало?
  • А сколько процессорного времени требуется на исполнение моей задачи?
  • А сколько реально кучи, выделенной для ОС, используется? Программа уже на пределе или еще есть, где развернуться?

В данном параграфе я приведу пример того, как можно реализовать простенький мониторинг ресурсов, который поможет получить однозначные ответы на все выше поставленные вопросы.

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

#define configGENERATE_RUN_TIME_STATS           0
#define configUSE_TRACE_FACILITY                0
#define configUSE_STATS_FORMATTING_FUNCTIONS    0

О значении каждого параметра я расскажу чуть дальше, а для начала нам нужно создать задачу, назовем ее MonitorTask, бесконечный цикл которой будет собирать статистику с интервалом config::MonitorTask::SLEEP_TIME_MS и отправлять ее в терминал.

После того, как задача создана, нам нужно установить параметр configUSE_TRACE_FACILITY в 1, после чего нам станет доступна функция:

UBaseType_t uxTaskGetSystemState(TaskStatus_t* const pxTaskStatusArray, const UBaseType_t uxArraySize, uint32_t* const pulTotalRunTime)

Параметр pxTaskStatusArray должен иметь размер sizeof(TaskStatus_t) * uxTaskGetNumberOfTasks(), т.е. он должен быть достаточно большим, чтобы вместить в себя информацию обо всех существующих задачах.

Кстати, о структуре TaskStatus_t. Какую же информацию относительно каждой задачи мы можем получить? А вот такую:

TaskStatus_t
typedef struct xTASK_STATUS
{
/* The handle of the task to which the rest of the information in the
structure relates. */
TaskHandle_t xHandle;

/* A pointer to the task's name. This value will be invalid if the task was
deleted since the structure was populated! */
const signed char *pcTaskName;

/* A number unique to the task. */
UBaseType_t xTaskNumber;

/* The state in which the task existed when the structure was populated. */
eTaskState eCurrentState;

/* The priority at which the task was running (may be inherited) when the
structure was populated. */
UBaseType_t uxCurrentPriority;

/* The priority to which the task will return if the task's current priority
has been inherited to avoid unbounded priority inversion when obtaining a
mutex. Only valid if configUSE_MUTEXES is defined as 1 in
FreeRTOSConfig.h. */
UBaseType_t uxBasePriority;

/* The total run time allocated to the task so far, as defined by the run
time stats clock. Only valid when configGENERATE_RUN_TIME_STATS is
defined as 1 in FreeRTOSConfig.h. */
unsigned long ulRunTimeCounter;

/* Points to the lowest address of the task's stack area. */
StackType_t *pxStackBase;

/* The minimum amount of stack space that has remained for the task since
the task was created. The closer this value is to zero the closer the task
has come to overflowing its stack. */
unsigned short usStackHighWaterMark;
} TaskStatus_t;

Итак, теперь мы готовы к тому, чтобы описать бесконечный цикл задачи MonitorTask. Он может выглядеть, например, так:

MonitorTask function
TickType_t delay = rtos::Ticks::MsToTicks(config::MonitorTask::SLEEP_TIME_MS);

while(true)
{
  UBaseType_t task_count = uxTaskGetNumberOfTasks();

  if (task_count <= config::MonitorTask::MAX_TASKS_MONITOR)
  {
    unsigned long _total_runtime;
    TaskStatus_t _buffer[config::MonitorTask::MAX_TASKS_MONITOR];

    task_count = uxTaskGetSystemState(_buffer, task_count, &_total_runtime);

    for (int task = 0; task < task_count; task++)
    {
      _logger.add_str(DEBG, "[DEBG] %20s: %c, %u, %6u, %u", 
                            _buffer[task].pcTaskName,
                            _task_state_to_char(_buffer[task].eCurrentState),
                            _buffer[task].uxCurrentPriority,
                            _buffer[task].usStackHighWaterMark,
                            _buffer[task].ulRunTimeCounter);
    }

    _logger.add_str(DEBG, "[DEBG] Current Heap Free Size: %u",
                          xPortGetFreeHeapSize());

    _logger.add_str(DEBG, "[DEBG] Minimal Heap Free Size: %u",
                          xPortGetMinimumEverFreeHeapSize());
						                     
    _logger.add_str(DEBG, "[DEBG] Total RunTime:  %u ms", _total_runtime);

    _logger.add_str(DEBG, "[DEBG] System Uptime:  %u ms\r\n",
				      xTaskGetTickCount() * portTICK_PERIOD_MS);
  }

  rtos::Thread::Delay(delay);
}

Допустим, что в моей программе помимо MonitorTask, есть еще несколько задач с вот такими параметрами, где configMINIMAL_STACK_SIZE = 128:

TasksConfig.h
static constexpr uint32_t MIN_TASK_STACK_SIZE 	= configMINIMAL_STACK_SIZE;
static constexpr uint32_t MIN_TASK_PRIORITY   	= 1;
static constexpr uint32_t MAX_TASK_PRIORITY   	= configMAX_PRIORITIES;

struct LoggerTask {
    static constexpr uint32_t STACK_SIZE        = MIN_TASK_STACK_SIZE * 2;
    static constexpr const char NAME[]          = "Logger Task";
    static constexpr uint32_t PRIORITY          = MIN_TASK_PRIORITY;
    static constexpr uint32_t SLEEP_TIME_MS     = 100;
};

struct MonitorTask {
	static constexpr uint32_t STACK_SIZE 		= MIN_TASK_STACK_SIZE * 3;
	static constexpr const char NAME[]   		= "Monitor Task";
	static constexpr uint32_t PRIORITY   		= MIN_TASK_PRIORITY;
	static constexpr uint32_t SLEEP_TIME_MS 	= 1000;
	static constexpr uint32_t MAX_TASKS_MONITOR     = 10;
};

struct GreenLedTask {
	static constexpr uint32_t STACK_SIZE 		= MIN_TASK_STACK_SIZE * 2;
	static constexpr const char NAME[]   		= "Green Led Task";
	static constexpr uint32_t PRIORITY   		= MIN_TASK_PRIORITY;
	static constexpr uint32_t SLEEP_TIME_MS         = 1000;
};

struct RedLedTask {
	static constexpr uint32_t STACK_SIZE 		= MIN_TASK_STACK_SIZE * 2;
	static constexpr const char NAME[]   		= "Red Led Task";
	static constexpr uint32_t PRIORITY   		= MIN_TASK_PRIORITY;
	static constexpr uint32_t SLEEP_TIME_MS 	= 1000;
};

struct YellowLedTask {
	static constexpr uint32_t STACK_SIZE 		= MIN_TASK_STACK_SIZE * 2;
	static constexpr const char NAME[]   		= "Yellow Led Task";
	static constexpr uint32_t PRIORITY   		= MIN_TASK_PRIORITY;
	static constexpr uint32_t SLEEP_TIME_MS 	= 1000;
};

Тогда, после запуска программы я увижу в терминале следующую информацию:

image

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

  • Мы видим имена всех существующих задач. Помимо задач, описанных в файле TaskConfig.h мы также видим задачу IDLE, которая создается автоматически, когда стартует планировщик RTOS (о ее предназначении написано здесь).
  • Мы видим состояние каждой задачи, где B = Blocked, R = Ready, S = Suspended, D = Deleted.
  • Мы видим приоритет каждой задачи.
  • Мы видим минимальный размер свободного места на стеке, с момента создания задачи. И тут для нас становится очевидным, что для работы большинства задач, мы выделили слишком много стека. Например, для задачи LoggerTask был выделен стек в 256 слов, а реально она использует только 40. Таким образом, стека в 64 слова вполне достаточно для функционирования задачи. Вот вам и начало оптимизации.
  • Мы видим текущий и минимальный (с момета старта планировщика) размер свободного места в куче. В нашем простом примере, эти значения равны, но в более сложных программах эти две переменные, разумеется, отличаются. Таким образом, мы понимаем, что из 100КБ отданных FreeRTOS, она использует меньше 10КБ, следовательно в наших руках более 90КБ свободной памяти.
  • И, наконец, мы видим количество времени, прошедшее с момента старта планировщика в миллисекундах.

Применив полученные знания к файлу TasksConfig.h и понизив значение параметра configMINIMAL_STACK_SIZE со 128 до 64, мы получаем следующую картину:

image

Супер! Теперь у каждой задачи есть оптимальный запас свободного места на стеке: не слишком большой, и не слишком маленький. Кроме того, мы высвободили почти 3КБ памяти.

А теперь пришло время поговорить о том, чего мы в полученном логе пока не видим. Мы не видим того, сколько процессорного времени использует каждая задача, т.е. сколько времени, задача находилась в состоянии Running. Чтобы это узнать, нам необходимо установить параметр configGENERATE_RUN_TIME_STATS в 1 и дополнить файл FreeRTOSConfig.h следующими определениями:

#if configGENERATE_RUN_TIME_STATS == 1

void vConfigureTimerForRunTimeStats(void);
unsigned long vGetTimerForRunTimeStats(void);

#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()    vConfigureTimerForRunTimeStats()
#define portGET_RUN_TIME_COUNTER_VALUE()            vGetTimerForRunTimeStats()

#endif

Теперь нам нужно завести внешний таймер, отсчитающий время (желательно в микросекундах, потому что на выполнение некоторых задач может требоваться времени меньше миллисекунды, а мы хотим все-таки знать обо всем). Дополним файл MonitorTask.h объявлением двух статических функций:

static void config_timer();
static unsigned long get_counter_value();

В файле MonitorTask.cpp напишем их реализацию:

void MonitorTask::config_timer()
{
  _timer->disable_counter();
  _timer->set_counter_direction(cm3cpp::tim::Timer::CounterDirection::UP);
  _timer->set_alignment(cm3cpp::tim::Timer::Alignment::EDGE);
  _timer->set_clock_division(cm3cpp::tim::Timer::ClockDivision::TIMER_CLOCK_MUL_1);
  _timer->set_prescaler_value(hw::config::MONITOR_TIMER_PRESQ);
  _timer->set_autoreload_value(hw::config::MONITOR_AUTORELOAD);
  _timer->enable_counter();
  _timer->set_counter_value(0);
}

unsigned long MonitorTask::get_counter_value()
{
  static unsigned long _counter = 0;
	
  _counter += _timer->get_counter_value();
  _timer->set_counter_value(0);
  return (_counter);
}

А в файле main.cpp напишем реализацию функций vConfigureTimerForRunTimeStats() и vGetTimerForRunTimeStats(), которые мы объявили в FreeRTOSConfig.h:

#if configGENERATE_RUN_TIME_STATS == 1

void vConfigureTimerForRunTimeStats(void)
{
    tasks::MonitorTask::config_timer();
}

unsigned long vGetTimerForRunTimeStats(void)
{
    return (tasks::MonitorTask::get_counter_value());
}

#endif

Теперь, после запуска программы наш лог станет вот таким:

image

Сравнивая значения Total RunTime и System Uptime, мы можем заключить, что лишь треть времени наша программа занята исполнением задач, причем 98% времени тратится на IDLE, а 2% на все остальные задачи. Чем же наша программа занимается оставшиеся две трети времени? Это время тратится на работу планировщика и переключение между всеми задачами. Печально, но факт. Разумеется, есть способы оптимизировать это время, но это уже тема для следующей статьи.

Что касается параметра configUSE_STATS_FORMATTING_FUNCTIONS, то он является очень второстепенным, чаще всего он используется в различных демо программах, предоставляемых разработчиками FreeRTOS. Его суть заключается в том, что он включает две функции:

void vTaskList(char* pcWriteBuffer);
void vTaskGetRunTimeStats(char* pcWriteBuffer);

Обе эти функции НЕ являются частью FreeRTOS. Внутри себя, они вызывают ту же самую функцию uxTaskGetSystemState, которой мы пользовались выше, и складывают в pcWriteBuffer уже отформатированные данные. Сами разработчики не рекомендуют использовать эти функции (но, разумеется, и не запрещают), укзывая на то, что их задача скорее демонстрационная, а вметно них пользоваться функцией uxTaskGetSystemState напрямую, как мы и сделали.

На этом все. Как всегда надеюсь, что эта статья была полезной и информативной)

Для сборки и отладки демо проекта, описанного в статье, использовалась связка Eclipse + GNU MCU Eclipse (formerly GNU ARM Eclipse) + OpenOCD.

Блог компании Третий Пин

Похожие публикации

Средняя зарплата в IT

120 000 ₽/мес.
Средняя зарплата по всем IT-специализациям на основании 3 391 анкеты, за 1-ое пол. 2021 года Узнать свою зарплату
Реклама
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее

Комментарии 17

    0
    Ооочень интересно и познавательно, спасибо. FreeRTOS у меня только в планах, но самых ближайших.
      0
      Должен сказать, что так как FreeRTOS для систем реального времени (как-бы), а такие приложения работают в основном во встроенных системах, а там самое важное — надежность, то главнейшим требованием реальных разработок стоит — никакого динамического выделения памяти вообще (!). И в самом деле, если ваша система планируется для контроля ядерного реактора в течении 10 лет или особо ответственного объекта, то часто единственный способ это сделать — построить конечный автомат и отключить всё ненадежное, в том числе, естественно, малейшие манипуляции с памятью и FreeRTOS. Кстати, недаром в любой системе реального времени есть опция статического распределения памяти — это вообще-то, вытекает из требований (DO-187*). Также и недопустимо динамическое создание задач… На практике совсем простые приложения (типа учебных) и некритические могут, конечно, быть написаны с управлением памятью. Но любая система реального времени — это проблемы таймингов, синхронизации и так далее — не каждый разработчик рискнет добавлять к этому еще и управление памятью — то есть вам в данный момент надо срочно 100 байт, вы вызываете выделение памяти — а вам отказывают и АЭС взрывается система переходит в отказ только из-за этого — такого риска нам не надо!
        0
        Для каждой задачи — свой инструмент. Я не буду есть суп вилкой — это странно и не эффективно) Если мне нужно разработать систему, где время отклика исчисляется десятками, а то и единицами микросекунд (в моей практике это, как правило, всякие военные системы управления), то я изначально не буду использовать FreeRTOS, потому что она не в силах обеспечить такое быстродействие системы. А вот для систем, где время отклика не является критичным, но есть требование параллельного выполнения ряда задач, то тут FreeRTOS как нельзя кстати. Из последнего: устройство должно одновременно общаться с сервером по FTP, с подключенным устройством по Bluetooth, обновлять текущее состояние на дисплее, писать лог, снимать кучу отчетов, а по запросу еще и делать снимки, при этом работая в режиме 24/7. А в проекте Пастильда нам нужно было строить чертово xml дерево, и расшифровывать десятки килобайт данных на контроллере) Вот для таких задач FreeRTOS необходима, как и хороший менеджер памяти. В целом, в embedded практически всегда вся память выделяется либо статически на этапе компиляции, либо динамически, но только во время старта программы. От этого никуда не деться, специфика такая. Однако иногда очень приятно иметь возможность кое-что и динамически в runtime выделить, без коллапса системы при этом. Тут просто нужно понимать как работает ОС, следить за использованием ресурсов и тогда ничего критического не произойдет.
          0

          Действительно зависит от области где применяется код.
          В автомобилестроении мы используем MISRA стандарт кода который прямо запрещает динамическую память.
          Но правила NASA позволяют выделять память в коде инициализации (но не трогать ничего в runtime)

          0
          Налицо конфликт эмбедчиков и РТшников.

          Но правды нет, и победили ПЛК с абсолютной детерминированностью и возможностями модификаций онлайн.

          Хотя сегмент дешевых решений (а кому он нужен) может оспариваться еще 300 лет
            0
            Статья хорошая, но уже неактуальная, лет 5 назад зашла бы статья, но сейчас, есть хорошие инструменты такие как tracealyzer от percepio, где столько возможностей… что на примере данной отладки многопоточности, все это кажется древностью )
              0
              Tracealyzer и Segger SystemView штуки, безусловно, очень мощные, полезные и ими нужно пользоваться.

              Но, во-первых, эта статья, как я указала в самом начале, ориентирована в большей степени на тех людей, которые только начали свое знакомство с FreeRTOS, а на первых парах, для формирования понимания, того, что описано в статье, на мой взгляд, вполне достаточно.

              Во-вторых, для того чтобы использовать эти утилиты, необходим J-LINK. И да, можно сделать J-LINK из ST-LINK, но с этим нужно повозиться (возможно даже не один день), а потом еще потратить время на то, чтобы овладеть всеми фичами этих программ. А в статье описан максимально простой трейсинг, реализуемый средствами самой ОС.

              В-третьих, эти утилиты работают только тогда, когда ты отлаживаешь программу «на столе», сидя на своем уютном рабочем месте и с подключенным отладчиком. Но если программа очень комплексная, то часто ты физически не способен в лабораторных условиях нагрузить ее так сильно, как она может быть нагружена в реальных условиях. Поэтому на этапе первых испытаний устройства в полевых условиях, очень полезно весь runtime трейсинг писать в лог, чтобы если что-то сломалось, ты мог понять почему (если поломка была связана с работой ОС).

              Вот. Так что думаю, что для многих читателей эта статья все еще может быть актуальной)
                0
                все такие опубликовали мой комент) и подготовились), правда в том, что люди которые дойдут до красивой отладки изучать почти весь freetos, я думаю многие для это используют статьи Курниц А. для изучения freertos, там отладка одна из последних статей, еще в защищу Tracealyzer уже давно для него не нужен только st-link и j-link, вот ссылка percepio.com/docs, и вот поддерживаемые интерфейсы через которые можно получать всю информацию для freertos:
                File/
                Jlink_RTT/
                TCPIP/
                TCPIP_Win32/
                USB_CDC/
                так же Tracealyzer поддерживает не только freertos, а так же embos, threadxm vxworks и др., поэтому инструмент перспективнее, портирование так же много времени не занимает, подключение легче, чем корректная настройка config для freertos :) и я уверен, что emmbedder в будущем перейдет на новую rtos, и что ему делать тогда? лучше способ это Tracealyzer :) посмотрите насколько он прекрасен, а получить можно его бесплатно)
                youtu.be/mt0CSvLI5Ho
                  0
                  Подскажите способ, как его получить бесплатно и легально, если ты не студент?
                    0
                    Просто заходите на сайт, при скачивании ставите галочку ознакомится, вам присылают на почту ключ и можно работать )
              0
              Вот еще отличный инструмент от NXP для просмотра состояний тасков:
              mcuoneclipse.com/2017/03/18/better-freertos-debugging-in-eclipse
                0
                Очень хорошая статья. И своевременная. Cтою перед выбором: FreeRTOS vs TI-RTOS.
                  0
                  Я делала небольшой проект под CC1310 с использованием TI-RTOS. Хочу сказать, что если человек никогда не использовал ОС в проектах, то я бы порекомендовала начать с FreeRTOS, т.к. она очень простая и прозрачная. С другой стороны, если проект под техасовские чипы, то я бы использовала все-таки TI-RTOS, просто потому, что техас предоставляет огромное количество примеров, демо проектов, все задокументировано более чем полностью… Тут главное не потеряться во всем этом изобилии и прогитовиться к тому, что читать придется очень много)
                    0
                    Анастасия, спасибо за ценные рекомендации (жаль, что моя карма пока не позволяет повысить Вашу).

                    У меня как раз та самая «двухсторонняя» ситуация. И ОС пока использовать не доводилось, и чип от TI. Как бы Вы поступили на моём месте?

                    К англоязычным мануалам в тысячу(и) страниц давно привык, не пугает.
                      0

                      Вообще, я бы всё же для начала freertos пощупал.
                      Начнете сразу с ti rtos/sysbios, совсем взгрустнете…
                      С другой стороны, в sdk у ti существует значительная поддержка драйверов.

                  0
                  Спасибо, очень интересно, но мне кажется calloc из code-snippetа нарушает стандарт C. По крайней мере о том что pvPortMalloc из heap4_c. обнуляет данные из документации и кода понять не удалось. Или предполагается что это всё работает, когда задефайнен configAPPLICATION_ALLOCATED_HEAP?
                    0
                    Вообще, я бы всё же для начала freertos пощупал.
                    Начнете сразу с ti rtos/sysbios, совсем взгрустнете…
                    С другой стороны, в sdk у ti существует значительная поддержка драйверов.

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

                    Самое читаемое