Добрый вечер всем,я Jiejie.Я был очень занят в последнее время.Давно не обновлялся.В эти выходные буду блевать кровью и обновлять!
предисловие
FreeRTOS
Это ядро реального времени, задача — наименьшая единица выполнения программы, а также основная единица обработки планировщика.FreeRTOS
, управления задачами нельзя избежать.Когда запущено несколько задач, переключение задач особенно важно. Эффективность переключения задач будет определять стабильность и эффективность системы.
FreeRTOS
Для чего нужен переключатель задач?rtos
Реальность такова, что задача в состоянии выполнения с наивысшим приоритетом всегда выполняется, и как те задачи, которые были в состоянии готовности, переходят в состояние выполнения, чтобы заставить их работать?FreeRTOS
Что нужно сделать для переключения задач, так это найти задачу в состоянии готовности с наивысшим приоритетом и дать ей право использовать ЦП, чтобы она могла перейти из состояния готовности в состояние выполнения. вся система. Производительность будет хорошей, и отклик будет хорошим, без блокировки программы.
Чтобы знать, как реализовать переключение задач, вы должны знать механизм переключения задач.cpu(mcu)
, способ срабатывания может быть другим, и сейчас мы возьмем Cortex-M3 в качестве примера, чтобы поговорить о переключении задач. Для того, чтобы всем была понятна эта статья, я буду кидать и цитировать нефрита,引用《Cortex-M3权威指南-中文版》的部分语句(如涉及侵权,请联系杰杰删除)
SVC и PendSV
SVC (системный сервисный вызов, также называемый системным вызовом) иPendSV
(Pended System Call
, приостановка системных вызовов), которые в основном используются при разработке программного обеспечения поверх операционной системы.SVC
Используется для генерации запросов на вызов системных функций. Например, операционная система не позволяет пользовательской программе напрямую обращаться к оборудованию, но косвенно обращается к оборудованию, предоставляя некоторые системные сервисные функции, а пользовательская программа использует SVC для выдачи запроса на вызов системных сервисных функций. Поэтому, когда пользовательская программа хочет управлять конкретным оборудованием, она генерируетSVC
исключение, то операционная система предоставляетSVC
Выполняется подпрограмма службы исключений, которая затем вызывает соответствующую функцию операционной системы, которая завершает выполнение службы, запрошенной программой пользователя.
Другим связанным исключением является PendSV
(приостанавливаемый системный вызов), это иSVC
Использование в синергии. с одной стороны,SVC
На исключение нужно реагировать немедленно (если приоритет не выше, чем обрабатываемый в данный момент, или другие причины не позволяют ответить немедленно, петиция становится хард-виной — Прим. переводчика), приложение выполняетсяSVC
Всякий раз, когда вы хотите, чтобы на желаемый запрос ответили немедленно. PendSV, с другой стороны, отличается тем, что его можно приостановить как обычное прерывание (в отличие отSVC
посетит). ОС может использовать его, чтобы «приостановить выполнение» исключения — не выполнять действие, пока не будут завершены другие важные задачи. приостановленныйPendSV
Метод: вручнуюNVIC
из PendSV
Запишите 1 в регистр ожидания. После приостановки, если приоритет недостаточно высок, выполнение будет отложено.
Если возникающее исключение не может быть отреагировано немедленно, оно называется «ожидающим». Тем не менее, несколько исключений из-за неисправности не могут быть приостановлены. Исключение может быть приостановлено, так как система в настоящее время выполняет служебную процедуру для исключения с более высоким приоритетом, или исключение отключено из-за установки соответствующего бита маскирования. Для каждого источника исключений, в случае его приостановки, будет соответствующий «реестр состояния приостановки» для сохранения его запроса на исключение до тех пор, пока исключение не будет выполнено, что полностью отличается от традиционного ARM. Раньше это было устройство, которое генерировало прерывание, удерживающее сигнал запроса. Теперь появление регистра состояния ожидания NVIC решает эту проблему, даже если устройство выпустило сигнал запроса позже, предыдущий запрос на прерывание не будет пропущен.
Инженерный анализ переключения системных задач
Задачи, которые обычно выполняются в системе (при условии отсутствия внешних прерываний)IRQ
),использоватьSystick
Нет абсолютно никаких проблем с переключением контекста напрямую, как показано на рисунке:
Но проблема в том, что почти немногие встроенные устройства не используют его богатую реакцию на прерывания, поэтому использовать systick для переключения контекста системы напрямую нецелесообразно, что сопряжено с большими рисками, т.к. предполагается, чтоsystick
прервал прерывание(IRQ
), если переключение контекста производится немедленно, это нарушает использованиеfault
Ненормально, у вас нет другого пути, кроме как перезапустить, продукт, сделанный таким образом, - фигня! ! Какое дерьмо было написано словами моего начальника! ! ! как показано на рисунке:
Тем не менее, это не работает, это не работает, что мне делать? Пожалуйста, смотрите предыдущийPendSV
, это немного просветленный?PendSV
идеально решить эту проблему.PendSV
Исключения автоматически задерживают запросы на переключение контекста до тех пор, пока другие ISR
Он будет выпущен после завершения всей обработки. Для реализации этого механизма необходимоPendSV
Исключение запрограммировано на самый низкий приоритет. если OS
обнаружилIRQ
активен и вытеснен SysTick, он приостановитPendSV
Исключение, чтобы отложить выполнение переключения контекста.
понимать? То есть до тех пор, покаPendSV
Приоритет установлен на самый низкий, даже если systick прервет IRQ, он не переключит контекст немедленно, а подождет, пока IRQ будет выполнено.PendSV
Служебная подпрограмма только начинает выполняться, и внутри нее происходит переключение контекста. Процесс показан на рисунке:
Исходный код реализации переключения задач
Процесс почти понятен, давайте посмотрим, как он реализован во FreeRTOS! !
FreeRTOS имеет два способа запуска переключения задач:
- один
systick
курокPendSV
Исключение, которое используется чаще всего. - Другой — активное переключение задач и выполнение системных вызовов.Например, обычные задачи могут использовать taskYIELD() для принудительного переключения задач, что используется в подпрограммах обслуживания прерываний.
portYIELD_FROM_ISR()
Принудительное переключение задач.
Первое
Давайте поговорим о первом, прямо здесьsystick
прерывание вызоваxPortSysTickHandler()
;
Вот исходный код:
void xPortSysTickHandler( void )
{
vPortRaiseBASEPRI();
{
/* Increment the RTOS tick. */
if( xTaskIncrementTick() != pdFALSE )
{
/* A context switch is required. Context switching is performed in
the PendSV interrupt. Pend the PendSV interrupt. */
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
}
}
vPortClearBASEPRIFromISR();
}
Процесс его выполнения такой, все прерывания маскируются, потому что SysTick работает с самым низким приоритетом прерывания, поэтому все прерывания должны быть замаскированы при выполнении этого прерывания. vPortRaiseBASEPRI(); должен экранировать все прерывания. И нет необходимости сохранять значение этого прерывания, потому что приоритет прерывания systick известен, и все прерывания можно восстановить сразу после выполнения.
существуетxTaskIncrementTick()
Центральная встречаtick
Значение счетчика самодобавляется, а затем проверяется, есть ли задача с наивысшим приоритетом в состоянии готовности, если есть, возвращается ненулевое значение, а затем указывает, что требуется переключение задачи вместо немедленной задачи Здесь следует отметить, что это только к регистру состояния прерывания.bit28
немного написать1
, просто поставьPendSV
зависает, если не болееPendSV
прерывания с более высоким приоритетом, он войдетPendSV
Функция обслуживания прерываний выполняет переключение задач.
#define portNVIC_PENDSVSET_BIT ( 1UL << 28UL )
Затем демаскируйте все прерывания.
vPortClearBASEPRIFromISR();
секунда
Другой способ — активно переключать задачи, используя taskYIELD() или portYIELD.FROMISR(), в конечном итоге выполнит следующий код:
#define portYIELD() \
{ \
/* Set a PendSV to request a context switch. */ \
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \
__dsb( portSY_FULL_READ_WRITE ); \
__isb( portSY_FULL_READ_WRITE ); \
}
ЭтотportYIELD()
По сути, это определение макроса. То же самое с регистром статуса прерыванияbit28位写入1
,будетPendSV
Приостановите, затем подождите, пока задача переключится.
Исходный код переключения конкретной задачи
Я говорил о том, как выполнять переключение задач, кажется, я не видел исходный код переключения задач.Эй, давайте посмотрим на истинное лицо переключения задач! !
__asm void xPortPendSVHandler(void)
{
extern uxCriticalNesting;
extern pxCurrentTCB;
extern vTaskSwitchContext;
PRESERVE8
mrs r0, psp
isb
ldr r3, =pxCurrentTCB /* Get the location of the current TCB. */
ldr r2, [r3]
stmdb r0!, {r4-r11} /* Save the remaining registers. */
str r0, [r2] /* Save the new top of stack into the first member of the TCB. */
stmdb sp!, {r3, r14}
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
msr basepri, r0
dsb
isb
bl vTaskSwitchContext
mov r0, #0
msr basepri, r0
ldmia sp!, {r3, r14}
ldr r1, [r3]
ldr r0, [r1] /* The first item in pxCurrentTCB is the task top of stack. */
ldmia r0!, {r4-r11} /* Pop the registers and the critical nesting count. */
msr psp, r0
isb
bx r14
nop
}
Дело не в том, что я не хочу ее читать, а в том, что у меня поднимается голова, когда я вижу компиляцию. Я также просматриваю исходный код в эти дни, и он действительно большой.
Просто найдите основные функции и посмотрите. Независимо от того, сколько их, вы можете изучить код прерывания, если вам интересно. Если вы не понимаете, вы можете спросить меня. Это также хороший выбор для совместного изучения.
Вот основные моменты:
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
msr basepri, r0
Эти две строки кода отключают прерывания. Вы должны работать, когда отключаете прерывание, хе-хе~
bl vTaskSwitchContext
BL — это инструкция по прыжку, я еще немного в этом разбираюсь.
функция вызоваvTaskSwitchContext()
, ища новую задачу для запуска, сделав переменнуюpxCurrentTCB
Укажите на новую задачу, чтобы добиться переключения задач, затем откройте прерывание и выйдите.
Найти следующую задачу для запуска
Вы чувствуете, что это не имеет большого значения, если вы чувствуете себя так, вы, возможно, не узнали дома, поторопитесь и посмотритеFreeRTOS
исходный код, вconfig.h
Есть ли в файле конфигурации задача под названием «Поиск оборудования» для запуска следующей?configUSE_PORT_OPTIMISED_TASK_SELECTION
, это вFreeRTOS
Он называется специальным методом в , который фактически является аппаратным поиском, но не каждый однокристальный микрокомпьютер его поддерживает.Если он не поддерживается, то можно выбрать только программный метод поиска, который является так называемым общим методом. Я не буду много говорить об общем методе, потому что я используюSTM32
,он支持硬件方法
Да, это более эффективно, поэтому мне не нужно изучать его программный метод. Если вам интересно, вы можете изучить исходный код. Если вы не понимаете, вы можете задать мне вопросы. Исходный код выглядит следующим образом. :
#define taskSELECT_HIGHEST_PRIORITY_TASK() \
{ \
UBaseType_t uxTopPriority = uxTopReadyPriority; \
\
/* Find the highest priority queue that contains ready tasks. */ \
while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) ) \
{ \
configASSERT( uxTopPriority ); \
--uxTopPriority; \
} \
\
/* listGET_OWNER_OF_NEXT_ENTRY indexes through the list, so the tasks of \
the same priority get an equal share of the processor time. */ \
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
uxTopReadyPriority = uxTopPriority; \
} /* taskSELECT_HIGHEST_PRIORITY_TASK */
Исходный код аппаратного метода выглядит следующим образом:
#define taskSELECT_HIGHEST_PRIORITY_TASK() \
{ \
UBaseType_t uxTopPriority; \
\
/* Find the highest priority list that contains ready tasks. */ \
portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority ); \
configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 ); \
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
} /* taskSELECT_HIGHEST_PRIORITY_TASK() */
Этот метод заключается в использовании инструкции вычисления начального нуля CLZ, предоставляемой аппаратным обеспечением, а конкретный макрос определяется как:
#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )
статическая переменнаяuxTopReadyPriority
Содержит информацию о наивысшем приоритете задач в состоянии готовности, т.к.FreeRTOS
Состояние выполнения всегда имеет наивысший приоритет, а следующее состояние готовности с наивысшим приоритетом должно выполняться при следующем переключении задач.uxTopReadyPriority
Используйте каждый бит, чтобы указать, находится ли задача в состоянии готовности, например переменнаяuxTopReadyPriority
изbit0为1
, значит есть задача с приоритетом 0 в состоянии готовности,bit6为1
Это означает, что в состоянии готовности находится задача с приоритетом 6. И с тех порbit0
приоритет выше, чемbit6
, то следующая задача — это задача с бит0 для выполнения (чем ниже массив, тем выше приоритет). Поскольку 32-битные целые числа имеют не более32
бит, поэтому использование этого специального метода ограничивает максимальное количество доступных приоритетов до32
, приоритет0~31
. Получите следующую задачу в состоянии готовности с наивысшим приоритетом, вызовитеlistGET_OWNER_OF_NEXT_ENTRY
чтобы получить элемент списка следующей задачи, а затем назначить блок управления задачей TCB элемента списка наpxCurrentTCB
, то мы запускаем следующую задачу.
На данный момент переключение задач завершено.
END
Подписывайтесь на меня
Добро пожаловать в публичный аккаунт «Развития Интернета вещей IoT»