FreeRTOS ---- scheduler

FreeRTOS ---- scheduler

Start the process scheduler analysis

Once you've created the task, calls vTaskStartScheduler () function to start the task scheduler;

void vTaskStartScheduler( void )
{
/* 部分代码如下: */
BaseType_t xReturn;
    xReturn = xTaskCreate(  prvIdleTask,
                          configIDLE_TASK_NAME,
                          configMINIMAL_STACK_SIZE,
                          ( void * ) NULL,
                          portPRIVILEGE_BIT, 
                          &xIdleTaskHandle ); 

    #if ( configUSE_TIMERS == 1 )
    {
        if( xReturn == pdPASS )
        {
            xReturn = xTimerCreateTimerTask();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    #endif /* configUSE_TIMERS */

    if( xReturn == pdPASS )
    {
        #ifdef FREERTOS_TASKS_C_ADDITIONS_INIT
        {
            freertos_tasks_c_additions_init();
        }
        #endif
        
        portDISABLE_INTERRUPTS();

        #if ( configUSE_NEWLIB_REENTRANT == 1 )
        {
            _impure_ptr = &( pxCurrentTCB->xNewLib_reent );
        }
        #endif /* configUSE_NEWLIB_REENTRANT */

        xNextTaskUnblockTime = portMAX_DELAY;
        xSchedulerRunning = pdTRUE;
        xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;

        portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();

        traceTASK_SWITCHED_IN();

        if( xPortStartScheduler() != pdFALSE )
        {
            /* Should not reach here as if the scheduler is running the
            function will not return. */
        }
        else
        {
            /* Should only reach here if a task calls xTaskEndScheduler(). */
        }
    }
    else
    {
        configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
    }

    /* Prevent compiler warnings if INCLUDE_xTaskGetIdleTaskHandle is set to 0,
    meaning xIdleTaskHandle is not used anywhere else. */
    ( void ) xIdleTaskHandle;
}
  • Creating the idle task, if you use static memory, you use the function xTaskCreateStatic () to create a priority of the idle task is 0, the lowest priority;
  • If you use a software timer, then, we need to create a timer service job by function xTimerCreateTimerTask ();
  • Close interruption;
  • Variable is set to xSchedulerRunning pdTRUE, showing the scheduler starts running;
  • If the macro is configGENERATE_RUN_TIME_STATS time 1, indicating the time statistics function is enabled, at this time, the user needs to implement macro portCONFIGURE_TIMER_FOR_RUN_TIME_STATS, to configure a timer / counter;
  • Call the function xPortStartScheduler () to initialize the scheduler to start related hardware;

FreeRTOS system clock tick timer is provided, and will be used PendSV interrupt task switching, which is done by the hardware initialization xPortStartScheduler () function, the specific code is as follows:

/* 部分重要代码如下: */
/* Make PendSV and SysTick the lowest priority interrupts. */
portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;

/* Start the timer that generates the tick ISR.  Interrupts are disabled
here already. */
vPortSetupTimerInterrupt();

/* Initialise the critical nesting count ready for the first task. */
uxCriticalNesting = 0;

/* Start the first task. */
prvStartFirstTask();
  • PendSV set interrupt priority lowest priority;
  • Set SysTick interrupt priority level is the lowest priority;
  • Calling function vPortSetupTimerInterrupt () to set the timing cycle of timer ticks, and the timer tick interrupt can;
  • Initialize nested critical section counter is zero;
  • Call the function prvStartFirstTask () to start the first task;

Start the first task

Function prvStartFirstTask () for starting the first task, the source function as follows:

__asm void prvStartFirstTask( void )
{
    PRESERVE8

    /* Use the NVIC offset register to locate the stack. */
    ldr r0, =0xE000ED08
    ldr r0, [r0]
    ldr r0, [r0]

    /* Set the msp back to the start of the stack. */
    msr msp, r0
    /* Globally enable interrupts. */
    cpsie i
    cpsie f
    dsb
    isb
    /* Call SVC to start the first task. */
    svc 0
    nop
    nop
}
  • PRESERVE8 The, directive, which means eight-byte aligned;

  • Generally vector table should be the start address (0x00000000) start storing, but some applications may need to be modified at runtime or redefined vector table, Cortex-M processors provided for this purpose is called a relocation characteristic vector table , which provides a programmable register called Vector Table offset register (VTOR), the address of the register is 0xE000ED08, vector table may be redefined by the register;

    In the start address is stored in the scale MSP (primary stack pointer) initial value, and the operation of the following lines of MSP is to get the initial value, and assigned to r0, r0 immediately assign values ​​to the MSP, the upcoming initial value to the MSP, the reset;

    ldr r0, =0xE000ED08
    ldr r0, [r0]
    ldr r0, [r0]
    msr msp, r0
  • Instructions cpsie i and cpsie f meanings as follows:

  • Instructions dsb and isb following meaning:

  • 0 svc , call the SVC SVC instruction triggers an interrupt, also known as SVC call request management, SVC and PendSV exception is very important for the OS designs, and only use the SVC in FreeRTOS exception to start the first task, on the back of the program never less than the SVC ;

Henceforth, will enter the SVC exception interrupt handler, SVC interrupt service routine should be SVC_Handler (), but FreeRTOSConfig.h usually redefined by #define way for vPortSVCHandler (), as follows:

#define vPortSVCHandler SVC_Handler

Function vPortSVCHandler () is defined in port.c file source code is as follows:

__asm void vPortSVCHandler( void )
{
    PRESERVE8

    ldr r3, =pxCurrentTCB   /* Restore the context. */
    ldr r1, [r3]            /* Use pxCurrentTCBConst to get the pxCurrentTCB address. */
    ldr r0, [r1]            /* The first item in pxCurrentTCB is the task top of stack. */
    ldmia r0!, {r4-r11}     /* Pop the registers that are not automatically saved on exception entry and the critical nesting count. */
    msr psp, r0         /* Restore the task stack pointer. */
    isb
    mov r0, #0
    msr basepri, r0
    orr r14, #0xd
    bx r14
}
  • pxCurrentTCB is an implementation pointer TCB_t of the needle points running task, and here is to get the pointer to the stored address ; the following instruction series of operations is the first to obtain TCB address pxCurrentTCB pointer points, and then get through this address TCB is the first field, i.e. the task stack pxTopOfStack stack pointer is pointing;

    ldr   r3, =pxCurrentTCB
    ldr r1, [r3]
    ldr r0, [r1]  

    The ultimate goal is to get the first task to run the task stack pointer ;

    Since the value of the register corresponding to the task, that is, these values ​​are stored in the context saving, the need to restore a task switch site, i.e., to restore these registers;

  • ldmia r0 !, {r4-r11}, LDMIA instruction is a multiple load / store instruction, but is used here multiple load / store instructions with write-back, where the role is, and following the multi-address stored in the register r0 value consecutive addresses assigned to the register r4-r11, and for r0 ~ r3, r12, PC, xPSR MCU etc. these registers will automatically exit out of the stack when the interrupt recovery, r4 ~ r11 require users to manually pop;

  • msr psp, r0 process stack pointer PSP set to the task stack;
  • msr basepri, r0 i.e. register basepri = 0, enable interrupts;
  • orr r14, # 0xd, r14 is the link register (LR), r14 bit the last four or 0x0d, saying that he quit abnormal CPU enter Thread mode and use the process stack;
  • bx r14, after the instruction is executed, the hardware automatically restore the value of the register r0 ~ r3, r12, LR, PC and xPSR, the process stack stack usage PSP, and then execute the save PC register in the task function;

At this point, FreeRTOS task scheduler started running;

Task switching

The core system is the RTOS task management, task management is the core task switching, task switching determines the execution order of the tasks, task switching efficiency level also determines the performance of a RTOS;

PendSV exception

PendSV (suspendable system call) exception, the priority can be programmed by the ICSR status register and interrupt control 1 bit28 is set to trigger an interrupt PendSV; and SVC Anomaly, it may suspend state within the higher priority exception handling settings, and will be executed after the completion of high-priority exception handling to take advantage of this feature, the PendSV set to the lowest priority exception, allowing PendSV exception handling in all other execution after interrupt processing is completed, this is useful for context switching, is a key design various OS;

FreeRTOS task switching systems are ultimately in PendSV interrupt service function completed;

PendSV interrupt service routine should have to PendSV_Handler (), but in the FreeRTOS redefined as follows:

#define xPortPendSVHandler  PendSV_Handler

Function xPortPendSVHandler () source as follows:

__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
}

The code of the process operations:

  1. Save site (currently running task);
  2. Disable interrupts, enter the critical section;
  3. Call vTaskSwitchContext () function to get a task next to run, will pxCurrentTCB update for this task to run the task control block;
  4. Stack pointer get new tasks;
  5. Recovery site (site is about to run a task);
  6. Update process stack pointer value of the PSP;
  7. The last execution instruction BX r14 , a new task starts running;

Find the next task to be run

Call vTaskSwitchContext (a function to find the next task to run), while inside the function ultimately calls taskSELECT_HIGHEST_PRIORITY_TASK () to complete;

Find the task you want to run a FreeRTOS in two ways, namely common mode and hardware mode, which requires specific use is determined by whether the configuration configUSE_PORT_OPTIMISED_TASK_SELECTION this macro when the macro is 1, is used hardware, otherwise, it is a common way;

The difference between the two approaches as follows:

Common way

Array pxReadyTasksLists [] array is ready task list, a priority corresponding to a list, with priority ready tasks are articulated in the corresponding list;

UxTopReadyPriority variable represents the highest priority value in the ready state, the situation is the updated value in two ways:

  1. Each time you create a new task will determine when the priority of the task is greater than the current value uxTopReadyPriority, if it is greater than the priority value assigned to the new task uxTopReadyPriority;
  2. When a new ready task is added to the ready list will determine and update the value of uxTopReadyPriority;

In general manner, that is, from uxTopReadyPriority refer to this current state of readiness to start the highest priority value judgment, which list is not empty it shows which have priority ready task;

Hardware

Hardware is built using a processor to implement the hardware instructions, such as Cortex-M processors with the calculated number of leading zeros instruction: CLZ;

When using hardware, uxTopReadyPriority variable represents the ready state is not the highest priority, but to use every bit of the variable representative of a priority, bit0 on behalf priority 0, bit31 represents the priority of 31, when a priority there will then ready tasks each position 1, and therefore, when using the hardware must have at most 32 priority;

CLZ instruction for calculating the number of leading zeros, i.e. starting from the highest bit to a bit of 1 bit, the number of which is between 0, then, the number obtained by subtracting 31 and then is in a ready state, highest priority value;

After obtaining the highest priority ready state, using listGET_OWNER_OF_NEXT_ENTRY () to find the corresponding list from a list item, the list item corresponding to assign tasks to block pxCurrentTCB, thus determining the next tasks to be run;

Task switching

In both cases will trigger the task switching: the implementation of system calls and tick timer interrupt;

System calls

System call is the implementation of the relevant API FreeRTOS provided by the system, such as task switching function taskYIELD () and other indirect call taskYIELD () of API;

Function taskYIELD () is actually a macro, which is defined as follows:

#define taskYIELD()  portYIELD()

/* Scheduler utilities. */
#define portYIELD()                                                             \
{                                                                               \
    /* Set a PendSV to request a context switch. */                             \
    portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;                             \
                                                                                \
    /* Barriers are normally not required but do ensure the code is completely  \
    within the specified behaviour for the architecture. */                     \
    __dsb( portSY_FULL_READ_WRITE );                                            \
    __isb( portSY_FULL_READ_WRITE );                                            \
}

The final operation is the above-mentioned source by writing to the status register and interrupt control bit28 ICSR 1, suspended PendSV PendSV triggered interrupt so that it can interrupt the task switching function service PendSV;

Interrupt level task switching function portYIELD_FROM_ISR (), ultimately by function portYIELD () to complete;

Tick ​​timer interrupt

Need to modify the timer tick interrupt service function as follows:

void SysTick_Handler(void)
{
    if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
    {
        xPortSysTickHandler();
    }
}

Call xPortSysTickHandler () ticking timer service function, the function source code as follows:

void xPortSysTickHandler( void )
{
    vPortRaiseBASEPRI(); //关中断
    {
        if( xTaskIncrementTick() != pdFALSE ) //增加时钟计数器xTickCount的值
        {
            //向中断控制和状态寄存器ICSR的bit28写入1,挂起PendSV来触发PendSV中断
            portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
        }
    }
    vPortClearBASEPRIFromISR(); //打开中断
}

Time slicing

FreeRTOS support multiple tasks simultaneously have the same priority, that is a task with a priority in the operation of a time slice (the length of a clock pulse) allow the use of the CPU, so that there is the next task with priority run;

To use time slicing it, and the macro macro configUSE_PREEMPTION configUSE_TIME_SLICING 1 must be configured;

The length of the time slice is determined by the macro configTICK_RATE_HZ, is the length of a time slice timer tick interrupt cycle, such configTICK_RATE_HZ value is set to 1000, then a time slice length is 1ms;

Time slicing occur timer tick interrupt service function;

Guess you like

Origin www.cnblogs.com/jasontian996/p/11957853.html