Изучайте FreeRTOS с 0-(Планирование задач)-4

Интернет вещей

Добрый вечер всем,я 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Нет абсолютно никаких проблем с переключением контекста напрямую, как показано на рисунке:

switch

Но проблема в том, что почти немногие встроенные устройства не используют его богатую реакцию на прерывания, поэтому использовать systick для переключения контекста системы напрямую нецелесообразно, что сопряжено с большими рисками, т.к. предполагается, чтоsystickпрервал прерывание(IRQ), если переключение контекста производится немедленно, это нарушает использованиеfault Ненормально, у вас нет другого пути, кроме как перезапустить, продукт, сделанный таким образом, - фигня! ! Какое дерьмо было написано словами моего начальника! ! ! как показано на рисунке:

IRQ-switch

Тем не менее, это не работает, это не работает, что мне делать? Пожалуйста, смотрите предыдущийPendSV, это немного просветленный?PendSVидеально решить эту проблему.PendSVИсключения автоматически задерживают запросы на переключение контекста до тех пор, пока другие ISRОн будет выпущен после завершения всей обработки. Для реализации этого механизма необходимоPendSVИсключение запрограммировано на самый низкий приоритет. если OS обнаружилIRQ активен и вытеснен SysTick, он приостановитPendSVИсключение, чтобы отложить выполнение переключения контекста.

понимать? То есть до тех пор, покаPendSVПриоритет установлен на самый низкий, даже если systick прервет IRQ, он не переключит контекст немедленно, а подождет, пока IRQ будет выполнено.PendSVСлужебная подпрограмма только начинает выполняться, и внутри нее происходит переключение контекста. Процесс показан на рисунке:

pendsv-switch

Исходный код реализации переключения задач

Процесс почти понятен, давайте посмотрим, как он реализован во FreeRTOS! !

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

  1. одинsystickкурокPendSVИсключение, которое используется чаще всего.
  2. Другой — активное переключение задач и выполнение системных вызовов.Например, обычные задачи могут использовать 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»