FreeRTOS – определяем среднюю загрузку ядра микропроцессора (CPU utilization)



В одном из проектов на базе FreeRTOS появилась необходимость определить: на сколько, в среднем, загружено ядро МП. Конечно, для систем реального времени более критичным является показатель пиковой загрузки, но знать «среднюю температуру по больнице» иногда тоже полезно.



FreeRTOS имеет довольно удобный механизм — CPU idle hook. На практике — это просто функция vApplicationIdleHook(), которая вызывается (в бесконечном цикле) из потока prvIdleTask, управление которому передается только в том случае, если все остальные потоки находятся в состоянии ожидания какого либо события (ожидание семафора, мютекса и т. д.). Упрошено, можно сказать, что prvIdleTask это поток с наименьшим приоритетом.

И так, для того, чтобы определить время «простоя» ядра необходимо вычислить — сколько, в процентном отношении, ресурсов CPU «досталось» prvIdleTask (измерить частоту вызова vApplicationIdleHook()). Чем больше ресурсов досталось prvIdleTask — тем менее загружено ядро другими потоками.

Как определить «производительность» prvIdleTask в определенный момент времени? Я решил «озадачить» данный поток банальным приращением переменной — счетчика. Раз в секунду состояние счетчика анализируется, на основании его значения вычисляется средняя загрузка ядра за прошедшую секунду, после чего счетчик сбрасывается и все повторяется сначала.

Для того чтобы из полученного значения счетчика вычислить процент простоя CPU необходима «калибровка» — нужно узнать до какого значения prvIdleTask «накрутит» за одну секунду счетчик, если данный поток будет выполнятся в монопольном режиме. Для этого, при старте устройства, все основные потоки стартуют с задержкой в пару секунд (в моем случе это не критично). Эти пару секунд все ресурсы ОС отдает prvIdleTask и вычисленное значение счетчика будет максимальным – соответствовать 100% CPU idle. Далее думаю все понятно — если при монопольном выполнении prvIdleTask за секунду наш счетчик достиг 1000, а при работе всех потоков prvIdleTask «досчитал» до 500 — значит средняя загрузка составляет 50% и т. д.

Описание получилось немного путанным, в коде все намного проще:



//---------------------------------
volatile static BYTE CPU_IDLE = 0;

//---------------------------------
BYTE GetCPU_IDLE(void) {
	return CPU_IDLE;
}

//----------------------------------
void vApplicationIdleHook( void ) {	//это и есть поток с минимальным приоритетом
	static portTickType LastTick; 
	static DWORD count; 		//наш трудяга счетчик
	static DWORD max_count ; 	        //максимальное значение счетчика, вычисляется при калибровке и соответствует 100% CPU idle

	count++;                                                  //приращение счетчика

	if(_GET_DIFF(xTaskGetTickCount(), LastTick ) > 1000) 	{ //если прошло 1000 тиков (1 сек для моей платфрмы)
		LastTick = xTaskGetTickCount();
		if(count > max_count) max_count = count;          //это калибровка
		CPU_IDLE = 100 * count / max_count;               //вычисляем текущую загрузку
		count = 0;                                        //обнуляем счетчик
	}
}


UPD: спасибо 6502 за обнаруженную ошибку.
  • +2
  • 21 октября 2011, 14:56
  • e_mc2

Комментарии (26)

RSS свернуть / развернуть
Мне кажется код:

if(_GET_DIFF(xTaskGetTickCount() - LastTick ) > 1000)   
{ 
   //если прошло 1000 тиков (1 сек для моей платфрмы)
}


Не совсем правильный, поскольку в мануале FreeRtos на TaskGetTickCount сказано:

The count of ticks since vTaskStartScheduler was called.

То есть если какие то задачи будут возвращать управление раньше чем истечет квант времени, то и диспетчер будет вызывается чаще, соответственно в секунду будет не ровно 1000 тиков. Поэтому точность может колебаться в некоторых пределах.
0
  • avatar
  • 0xe0f
  • 21 октября 2011, 15:56
Не совсем Вас понял. Функция vTaskStartScheduler () — это «запуск» шедулера, данная функция вызывается один раз, а не каждый раз после переключения таска. Фукция TaskGetTickCount возращает количество тиков после запуска шедулера. Длительность «тика» фиксирована.
0
Да, действительно неправильно перевел. Но все таки, если задачи будет вызывать taskYIELD() до окончания своего тика, то будет ли увеличиваться счетчик тиков?
0
Будет. Насколько я помню, счетчик тиков привязан к таймеру. Хотя, меня в данном случае особо точность не волновала, нужны было ориентировочные значения. Все равно это усредненная загрузка.
0
Я похожую задачу делал, но аппаратным способом. Каждый процесс поднимал 1 в порту при входе, а при выходе сбрасывал. В результате на выходе порта получалось подобие ШИМа. Его заводил на стрелочный вольтметр и ловил загрузку по показаниями стрелки :)
0
А можно тогда еще на выходе RC цепочку повесить и завести на аналоговый вход — будет вообще супер! :D
0
Прикольное решение! Это подход настоящего эмбеддера :)
0
Угу, просто похожая ситуация была — старый контроллер на 51 ядре, без ЦАП, но надо выдавать точное аналоговое напряжение. Сделал ШИМ с фильтром, и выход фильтра завел на аналоговый вход, как обратную связь, так и компенсировал дрейф элементов фильтра)
0
Это как
точное аналоговое напряжение
? Про цифровое я не знаю(хотя бывает разное, мож проспал=) Или я чего-то я совсем понял??? И где в подобных контроллерах был ЦАП? Можно подробнее про
дрейф элементов фильтра
?
0
И где в подобных контроллерах был ЦАП?
Вот именно. ЦАП'а нет, а напряжение генерировать как-то надо. Вот он и сделал ШИМ, на ее выход ФНЧ, чтобы выделить постоянную составляющую. Ну и полученное напряжение на АЦП, чтобы следить за его значением. Благодаря обратной связи через АЦП можно выходное напряжение поддерживать, не заморачиваясь на потери в фильтре и все такое.
0
От именно! :) Про «аналоговое напряжение» само как то написалось)
0
Вопрос. Если max_count окажется меньше в тиках, чем выполняется некоторая задача? Что будет? Вдруг процесс занял болеше времени чем в эталонном замере=(
0
Наверное, Вы не совсем поняли идею. max_count – это не время в тиках выполнения какой-то задачи. max_count – это значения счетчика, до которого успеет «досчитать» (count++;) наш поток, если он будет выполнятся монопольно.
Считайте, что внутри потока vApplicationIdleHook следующий код:


DWORD count = 0
for(;;){
   count++;
}


Предположим, что данный код выполнялся с течение 1 сек. Дальше мы смотрим – чему равен count. Чем больше ресурсов CPU OC, в течении этой секунды, отдала потоку vApplicationIdleHook тем больше раз выполнился count++; и тем больше будет значение count. А max_count – это значение счетчика для случая, если все ресурсы CPU ОС отдаст потоку vApplicationIdleHook. max_count мы принимаем за 100% idle и исходя из этого рассчитываем текущую загрузку (в процентах) CPU_IDLE = 100 * count / max_count;
0
Может я не правильно выразился, но если в
CPU_IDLE = 100 * count / max_count;
max_count < count? Т.е. мы max_count тоже ведь замеряли за некоторое время, пусть даже «час»!!! А в моей системе может процесс и два часа никому процессор не отдаст и насчитает count поболе=) Тогда в таком подходе выйдет косяко
0
Смотрим предыдущую строку программы

if(count > max_count) max_count = count;

count никогда не будет больше чем max_count.
0
А в моей системе может процесс и два часа никому процессор не отдаст
Я специально указал, что «при старте устройства, все основные потоки стартуют с задержкой в пару секунд (в моем случе это не критично)» Это сделано для того, чтобы откалибровать max_count.
0
теперь согласен=) Просто код я просматривал…
0
Нехорошо пустые циклы гонять, в таких случаях надо переходить в режимы
меньшего энергопотребления. Можно будет по току определять :)
0
К сожалению, в данном случае не получится. Для этого нужно переписывать диспетчер RTOS. «Усыпить» чип в vApplicationIdleHook не проблема, другое дело, что диспетчер в любой момент времени может передать управление другой задаче – о том, что чип нужно перед этим «разбудить» диспетчер не знает. Боле того, ток потребляемый МП зависит не только от нагрузки на CPU но и от «состояния периферии» — если через один из выводов МП течет ток – это отразиться на общем энергопотреблении, но это никак не зависит от загрузки CPU
0
Бесконечный цикл внутри vApplicationIdleHook? Это что-то новое. Видимо это беда многих начинающих пользователей FreeRTOS, документация-то платная. Но есть-же весьма годные статьи, даже на русском, гугл знает.

vApplicationIdleHook — это не поток, это функция которая вызывается из Idle Task в бесконечном цикле. Поэтому бесконечный цикл внутри vApplicationIdleHook не нужен. Более того, если Вы используете vTaskDelete(), то это может привести к тому что не будут выполнены операции по очистки ресурсов ядра после удаления задач.
0
  • avatar
  • 6502
  • 25 декабря 2012, 21:08
Вы правы, vApplicationIdleHook вызывается в бесконечном цикле внутри prvIdleTask. Я об этом не знал. Дело не в отсутствии документации – это чисто моя ошибка, я не вник в логику работы vApplicationIdleHook.

Как появится свободное время – поправлю статью.
0
Спасибо, за то, что указали не ошибку.
0
указали не ошибку.

Упс. Спасибо, что указали на ошибку.
0
Касательно разгрузки ядра управлением потоками:

Есть данные от датчиков, которые пакетами через каналы сообщений хочу передать к обработчику через очередь. Но данных может избыточно много и загруженному обработчику по освобождении будут нужны свежие данные. Первая мысль про аналог кольцевой многопоточной очереди. К сожлению ничего похожего не нашел во FreeTROS. Имеется ли опыт реализции в этой оси такого, плюсы и минусы использовния с т.з. данной архитектуре такого кольца?
0
  • avatar
  • valio
  • 12 февраля 2013, 03:07
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.