Неизучение исходного кода операционной системы не считается изучением операционной системы.
Управление временем FreeRTOS
Тайм-менеджмент включает в себя два аспекта: системный ритм и управление задержкой задачи.
Системный ритм:
В предыдущей статье я тоже много говорил о том, что если вы хотите, чтобы система работала нормально, то тактовый ритм необходим.FreeRTOS
Ход часов обычно определяетсяSysTick
При условии, что он периодически генерирует прерывания по времени.Основой так называемого управления тактом является процедура обслуживания этого прерывания по времени.FreeRTOS
Работа ядра в тактовом ритме isr состоит в том, чтобы вызватьvTaskIncrementTick()
функция. Подробности смотрите в предыдущей статье.
Управление задержкой
FreeRTOS предоставляет две функции системной задержки:
- Функция относительной задержки
vTaskDelay()
- функция абсолютной задержки
vTaskDelayUntil()
.
Эти функции задержки не похожи на функции задержки, которые мы использовали для написания кода на голом железе.Операционная система не позволяет процессору ждать время, потому что это слишком неэффективно.
В то же время студентов, изучающих операционные системы, следует предупредить, чтобы они не использовали идею «голого железа» для изучения операционных систем.
задержка задачи
Задачу, возможно, придется отложить в двух случаях, один из которых состоит в том, что задачаvTaskDelay
илиvTaskDelayUntil
Задержка, другой случай, когда задача указывается при ожидании события (например, ожидание семафора или очереди сообщений).timeout
(то есть ждать время тайм-аута не более, если событие ожидания еще не произошло, оно не будет продолжать ожидание), в цикле каждой задачи должна быть ситуация блокировки, иначе задача с более низким приоритетом, чем задача никогда не сможет запуститься.
Разница между относительной задержкой и абсолютной задержкой
Относительная задержка: vTaskDelay():
Относительная задержка означает, что каждая задержка выполняется из функции задачи.vTaskDelay()
Старт, отсрочка окончания указанного времени
Абсолютная задержка: vTaskDelayUntil():
Абсолютная задержка относится к вызовуvTaskDelayUntil()
Задача выполняется каждые x раз. То есть цикл задачи выполняется.
Относительная задержка: vTaskDelay()
относительная задержкаvTaskDelay()
вызывается изvTaskDelay()
Эта функция начинает задерживать, но когда задача выполняется, может произойти прерывание, в результате чего время выполнения задачи становится больше, но время задержки всей задачи по-прежнему составляет 1000 тиков, что не является периодическим, просто посмотрите на следующее Код:
void vTaskA( void * pvParameters )
{
while(1)
{
// ...
// 这里为任务主体代码
// ...
/* 调用相对延时函数,阻塞1000个tick */
vTaskDelay( 1000 );
}
}
Может быть недостаточно понятно, можете посмотреть на схему.
Когда задача выполняется, если она прерывается задачей высокого уровня или прерыванием, время выполнения задачи будет больше, но задержка все равно задерживается.1000
Кусокtick
Таким образом, время всей системы путается.
Если это недостаточно ясно, взгляните на исходный код vTaskDelay().
void vTaskDelay( const TickType_t xTicksToDelay )
{
BaseType_t xAlreadyYielded = pdFALSE;
/* 延迟时间为零只会强制切换任务。 */
if( xTicksToDelay > ( TickType_t ) 0U ) (1)
{
configASSERT( uxSchedulerSuspended == 0 );
vTaskSuspendAll(); (2)
{
traceTASK_DELAY();
/*将当前任务从就绪列表中移除,并根据当前系统节拍
计数器值计算唤醒时间,然后将任务加入延时列表 */
prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
}
xAlreadyYielded = xTaskResumeAll();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 强制执行一次上下文切换 */
if( xAlreadyYielded == pdFALSE )
{
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
- (1): Если прошло время задержки
0
, может быть выполнена только задача принудительного переключения, а вызовportYIELD_WITHIN_API()
, на самом деле это макрос, что действительно работает, так этоportYIELD()
, вот его исходный код:
#define portYIELD() \
{ \
/* 设置PendSV以请求上下文切换。 */ \
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \
__dsb( portSY_FULL_READ_WRITE ); \
__isb( portSY_FULL_READ_WRITE ); \
}
- (2): приостановить текущую задачу.
Затем удалите текущую задачу из списка готовых и добавьте ее в список отложенных. это вызывающая функцияprvAddCurrentTaskToDelayedList()
завершить этот процесс. Поскольку эта функция слишком длинная, я не буду ее объяснять, если вам интересно, вы можете взглянуть, я кратко расскажу о процессе. существуетFreeRTOS
Есть такая переменная, которая используется для записиsystick
значения.
PRIVILEGED_DATA static volatile TickType_t xTickCount = ( TickType_t ) 0U;
каждый разtick
когда прерываетсяxTickCount
При добавлении единицы его значение представляет собой количество прерываний системных тиков, поэтому когда задача, добавленная в список задержки, проснется? На самом деле это очень просто, подход FreeRTOS будетxTickCount
(текущее системное время) +xTicksToDelay
(время задержки). Когда относительное время задержки истекает, он просыпается, это(xTickCount+ xTicksToDelay)
Время будет зафиксировано в блоке управления задачами.
Увидев это, нужно спросить, эта переменнаяTickType_t
тип (32-битный), он обязательно переполнится, да, переменная когда-нибудь переполнится, ноFreeRTOS
Это операционная система номер один в мире.FreeRTOS
Используются два списка задержек:
xDelayedTaskList1 和 xDelayedTaskList2
и используйте две переменные типа указателя спискаpxDelayedTaskList
иpxOverflowDelayedTaskList
Укажите на приведенный выше список задержек 1 и список задержек 2 соответственно (укажите указатель списка задержек на список задержек при создании задачи). Если ядро определяет, чтоxTickCount+xTicksToDelay
Переполнение, привязка текущей задачи к указателю спискаpxOverflowDelayedTaskList
список указывает, в противном случае он подключается к указателю спискаpxDelayedTaskList
указано в списке. Когда время истечет, отложенная задача будет удалена из списка отложенных и добавлена в список готовых.Конечно, в это время планировщик решает, может ли задача выполняться или нет.Если приоритет задачи больше чем текущей выполняемой задачи, то планировщик Устройство будет планировать задачи.
Абсолютная задержка: vTaskDelayUntil()
vTaskDelayUntil()
Параметр указывает точное значение счетчика тиков
перечислитьvTaskDelayUntil()
Есть надежда, что задача будет выполняться периодически с фиксированной периодичностью без внешнего воздействия, а интервал времени от начала предыдущего запуска до начала следующего запуска задачи будет абсолютным, а не относительным. Предположим, основная задача прервана0.3s
, но время следующего пробуждения фиксировано, поэтому он все равно будет запускаться периодически.
Посмотрите нижеvTaskDelayUntil()
метод использования, обратите внимание, что этоvTaskDelayUntil()
как использовать иvTaskDelay()
Разные:
void vTaskA( void * pvParameters )
{
/* 用于保存上次时间。调用后系统自动更新 */
static portTickType PreviousWakeTime;
/* 设置延时时间,将时间转为节拍数 */
const portTickType TimeIncrement = pdMS_TO_TICKS(1000);
/* 获取当前系统时间 */
PreviousWakeTime = xTaskGetTickCount();
while(1)
{
/* 调用绝对延时函数,任务时间间隔为1000个tick */
vTaskDelayUntil( &PreviousWakeTime,TimeIncrement );
// ...
// 这里为任务主体代码
// ...
}
}
При его использовании время задержки должно быть преобразовано в системный такт, а функция задержки должна быть вызвана перед основной частью задачи.
Задача будет вызвана первойvTaskDelayUntil()
Заставьте задачу войти в состояние блокировки, освободите ее от блокировки, когда время истечет, а затем выполните основной код, и основной код задачи будет выполнен. будет продолжать звонитьvTaskDelayUntil()
Задача переводится в состояние блокировки, а затем цикл выполняется следующим образом. Даже если задача будет прервана во время выполнения, это не повлияет на цикл выполнения задачи, а только сократит время блокировки.
Давайте взглянемvTaskDelayUntil()
Исходный код:
void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
{
TickType_t xTimeToWake;
BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE;
configASSERT( pxPreviousWakeTime );
configASSERT( ( xTimeIncrement > 0U ) );
configASSERT( uxSchedulerSuspended == 0 );
vTaskSuspendAll(); // (1)
{
/* 保存系统节拍中断次数计数器 */
const TickType_t xConstTickCount = xTickCount;
/* 生成任务要唤醒的滴答时间。*/
xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;
/* pxPreviousWakeTime中保存的是上次唤醒时间,唤醒后需要一定时间执行任务主体代码,
如果上次唤醒时间大于当前时间,说明节拍计数器溢出了 具体见图片 */
if( xConstTickCount < *pxPreviousWakeTime )
{
/* 由于此功能,滴答计数已溢出持续呼唤。 在这种情况下,我们唯一的时间实际延迟是如果唤醒时间也溢出,
唤醒时间大于滴答时间。 当这个就是这样,好像两个时间都没有溢出。*/
if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) )
{
xShouldDelay = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* 滴答时间没有溢出。 在这种情况下,如果唤醒时间溢出,
或滴答时间小于唤醒时间,我们将延迟。*/
if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) )
{
xShouldDelay = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/* 更新唤醒时间,为下一次调用本函数做准备. */
*pxPreviousWakeTime = xTimeToWake;
if( xShouldDelay != pdFALSE )
{
traceTASK_DELAY_UNTIL( xTimeToWake );
/* prvAddCurrentTaskToDelayedList()需要块时间,而不是唤醒时间,因此减去当前的滴答计数。 */
prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
xAlreadyYielded = xTaskResumeAll();
/* 如果xTaskResumeAll尚未执行重新安排,我们可能会让自己入睡。*/
if( xAlreadyYielded == pdFALSE )
{
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
и функция относительной задержкиvTaskDelay
Отличие, эта функция добавляет параметрpxPreviousWakeTime
Используется для указания на переменную, которая содержит последний раз, когда задача была разблокирована, после чего функцияvTaskDelayUntil()
Эта переменная автоматически обновляется внутри. из-за переменнойxTickCount
Он может переполняться, поэтому программа должна обнаруживать различные условия переполнения и следить за тем, чтобы период задержки был не меньше времени выполнения основного кода задачи.
Прежде чем задачу можно будет добавить в список отложенных, возможны следующие три ситуации.
Запомните значение этих слов:
-
xTimeIncrement
: время цикла задачи -
pxPreviousWakeTime
: момент времени последнего пробуждения -
xTimeToWake
: системный момент времени следующего пробуждения. -
xConstTickCount
: Время задержки входа
- Третий случай: обычный случай без переполнения.
Принимая время за горизонтальную ось, момент времени последнего пробуждения меньше момента времени следующего пробуждения, что является нормальной ситуацией.
- Второй случай: счетчик времени пробуждения (
xTimeToWake
) состояние переполнения.
То есть в кодеif( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) )
- Первый случай: время пробуждения (
xTimeToWake
) и момент времени задержки на вход (xConstTickCount
) являются условиями переполнения.
То есть в кодеif( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) )
Из рисунка видно, что независимо от того, есть переполнение или нет, требуется, чтобы основной код текущей задачи был выполнен до следующей задачи пробуждения. То есть время выполнения задачи не должно превышать время задержки.10ms
выполнить один раз20ms
задание на время. После того, как рассчитанное время пробуждения станет допустимым, текущая задача будет добавлена в список отложенных, также есть два списка отложенных. Каждый раз, когда системный такт прерывается, функция обслуживания прерываний проверяет два списка отложенных задач, чтобы узнать, не истек ли срок отложенной задачи.Если время истекло, задача будет удалена из списка отложенных и снова добавлена в список готовых. Если новая задача, добавленная в список готовых, имеет более высокий приоритет, чем текущая задача, сработает переключение контекста.
Суммировать
Если вызов задачи относительно отложен, ее цикл выполнения совершенно неизмерим.Если приоритет задачи не самый высокий, ошибка будет больше, как и задача, которая должна быть5ms
Если используется относительная задержка в 1 мс, она, вероятно, будет прервана задачей с более высоким приоритетом при выполнении задачи, в результате чего5ms
Однако то, может ли задача выполняться или нет, зависит от приоритета задачи.Если приоритет самый высокий, цикл задачи по-прежнему остается относительно высоким.vTaskDelay
Например, если вы хотите выполнять задачу более точно и периодически, вы можете использовать функцию системного битаvApplicationTickHook()
, это вtick
Вызывается функция обслуживания прерывания, поэтому код в этой функции должен быть лаконичным, а условия блокировки не допускаются.
Подписывайтесь на меня
Добро пожаловать в публичный аккаунт «Развития Интернета вещей IoT»