FreeRtos learning notes (10) analysis of task switching principle

Posted by ericorx on Thu, 03 Feb 2022 19:27:07 +0100

FreeRtos learning notes (10) analysis of task switching principle

Starting process of STM32 single chip microcomputer SP and PC registers are introduced in,
STM32 single chip microcomputer bootloader literacy How to control the program jump from bootloader to APP by controlling SP and PC registers is mentioned in. RTOS task switching is similar to the jump between bootloader and APP, but also realizes the jump between tasks by controlling SP and PC pointers.

MSP and PSP

MSP is used as the stack pointer in the interrupt service function. If there is no special setting in the project (i.e. non RTOS project), MSP will be used by default in the whole project. If RTOS is used in the project, PSP is used as stack pointer for other tasks except interrupt service function.

Cortex ‐ M3 has two stack pointers, but they are banked, so only one of them can be used at any time.

Main stack pointer (MSP): the stack pointer used by default after reset, which is used for operating system kernel and exception handling routines (including interrupt service routines)

Process stack pointer (PSP): used by the user's application code.

The lowest two bits of the stack pointer are always 0, which means that the stack is always 4-byte aligned. In the field of ARM programming, any event that interrupts the sequential execution of the program is called exception. In addition to external interrupts, when an instruction executes an "illegal operation", or accesses a prohibited memory interval, fault s caused by various errors, and unshielded interrupts occur, the execution of the program will be interrupted. These conditions are collectively referred to as exceptions. Exceptions and interrupts can also be mixed in a loose context. In addition, the program code can also actively request to enter the abnormal state (commonly used in system call).

Why do stack pointers have two?

  1. The stack of user application can be separated from the stack of privilege level / operating system kernel, which prevents the user program from accessing the stack of kernel and eliminates the possibility of kernel data destruction. (for example, under the windos system, a software jam will not cause the whole windos operating system to jam.)
  2. It enables RTOS to realize "preemptive system call" between tasks, Greatly improve the real-time performance (use PSP before interrupt, automatically use MSP after entering the interrupt service function, modify the PSP value during interrupt, and SP will automatically switch to PSP after exiting the interrupt service function, and the PSP value has been modified during interrupt. When exiting the interrupt, PC register and other register values will be output according to the new PSP and POP, so as to complete task switching)

How to switch between MSP and PSP?

The M3 authoritative guide points out that there are two methods for switching between MSP and PSP:

  • Write CONTROL[1] in privilege level thread mode
  • At the end of the interrupt service function, modify the LR register (R14). The following figure shows the meaning represented by the lower four bits of the LR register

FreeRtos realizes switching from MSP to PSP by modifying the LR register value.

After power up, the default use of MSP, then initialize the peripherals, create tasks, and finally call vTaskStartScheduler() to start RTOS. In vTaskStartScheduler(), xPortStartScheduler() function will be invoked. xPortStartScheduler() function will call port.. prvPortStartFirstTask() in C; Start the first task.

prvPortStartFirstTask() is an assembly function whose main function is to trigger SVC interrupt

static void prvPortStartFirstTask( void )
{
    __asm volatile (
        " ldr r0, =0xE000ED08 	\n"/* Use the NVIC offset register to locate the stack. */
        " ldr r0, [r0] 			\n"
        " ldr r0, [r0] 			\n"
        " msr msp, r0			\n"/* Set the msp back to the start of the stack. */
        " cpsie i				\n"/* Globally enable interrupts. */
        " cpsie f				\n"
        " dsb					\n"
        " isb					\n"
        " svc 0					\n"/* Trigger SVC exception and start the first task in SVC interrupt service function */
        " nop					\n"
        " .ltorg				\n"
        );
}

   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

SVC interrupt service function - vPortSVCHandler() is also an assembly function, which mainly does two things: restoring the task site (that is, POP the register value saved in the task stack to the corresponding register); Switch MSP to PSP; The specific meaning of assembly statements can be translated by referring to the instruction set in Chapter 4 of M3 authoritative guide.

void vPortSVCHandler( void )
{
    __asm volatile (
        "	ldr	r3, pxCurrentTCBConst2		\n"/* Restore the context. */
        "	ldr r1, [r3]					\n"/* Use pxCurrentTCBConst to get the pxCurrentTCB address. */
        "	ldr r0, [r1]					\n"/* The first item in pxCurrentTCB is the task top of stack. */
        "	ldmia r0!, {r4-r11}				\n"/* Pop the value of R4-R11 from the stack to the corresponding register, and the compiler of other registers will pop it up automatically */
        "	msr psp, r0						\n"/* Restore the task stack pointer. */
        "	isb								\n"
        "	mov r0, #0 						\n"
        "	msr	basepri, r0					\n"
        "	orr r14, #0xd 					\ n "/ * return to thread mode and use thread stack (SP switches from MSP to PSP)*/
        "	bx r14							\n"
        "									\n"
        "	.align 4						\n"
        "pxCurrentTCBConst2: .word pxCurrentTCB				\n"
        );
}

   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

How to switch between tasks?

When switching between tasks, you need to save the scene so that you can restore the scene and continue to execute when you jump back next time. The so-called site is the register of the kernel, while the saving site is to PUSH the current value of the register group to the stack and save it. The restoring site is to POP the register value saved in the stack to the corresponding register. The following figure shows the cortex-M3 register group.

The task switching of FreeRtos is completed in the PendSV interrupt service function, which is in port C.

void xPortPendSVHandler( void )
{
    /* This is a naked function. */
__asm <span class="token keyword">volatile</span>
<span class="token punctuation">(</span>
<span class="token comment">/**************************Part I preservation site****************************/</span>
    <span class="token string">"	mrs r0, psp							\n"</span>
    <span class="token string">"	isb									\n"</span>
    <span class="token string">"										\n"</span>
    <span class="token string">"	ldr	r3, pxCurrentTCBConst			\n"</span><span class="token comment">/* R3 Points to pxCurrentTCBConst, and pxCurrentTCBConst points to the current task control block */</span>
    <span class="token string">"	ldr	r2, [r3]						\n"</span>
    <span class="token string">"										\n"</span>
    <span class="token string">"	stmdb r0!, {r4-r11}					\n"</span><span class="token comment">/* Save the field and press the value of R4-R11 into the stack */</span>
    <span class="token string">"	str r0, [r2]						\n"</span><span class="token comment">/* Since R4-R11 is pushed into the stack, the stack top PSP will also change. Here, the new stack top PSP is stored in the task control block */</span>
    <span class="token string">"										\n"</span>
    <span class="token string">"	stmdb sp!, {r3, r14}				\n"</span><span class="token comment">/* Put R3 on the stack and use R3 later, but the vTaskSwitchContext function will be called later to prevent the value of R3 from being modified in vTaskSwitchContext */</span>
    
<span class="token comment">/*******************The second part finds the highest priority task in the current ready task****************/</span>
    <span class="token string">"	mov r0, %0							\n"</span>
    <span class="token string">"	msr basepri, r0						\n"</span><span class="token comment">/* When the off interrupt enters the critical area, pxCurrentTCBConst should be modified */</span>
    <span class="token string">"	bl vTaskSwitchContext				\n"</span><span class="token comment">/* Find the task with the highest priority in the ready task and point pxCurrentTCBConst to the task control block */</span>
    <span class="token string">"	mov r0, #0							\n"</span>
    <span class="token string">"	msr basepri, r0						\n"</span><span class="token comment">/* Open interrupt */</span>
    
<span class="token comment">/**************************Part III site restoration****************************/</span>
    <span class="token string">"	ldmia sp!, {r3, r14}				\n"</span>
    <span class="token string">"										\n"</span><span class="token comment">/* Remove R3 from the stack. R3 points to pxCurrentTCBConst, but pxCurrentTCBConst may have been modified in vTaskSwitchContext */</span>
    <span class="token string">"	ldr r1, [r3]						\n"</span>
    <span class="token string">"	ldr r0, [r1]						\n"</span>
    <span class="token string">"	ldmia r0!, {r4-r11}					\n"</span><span class="token comment">/* POP R4-R11 from stack */</span>
    <span class="token string">"	msr psp, r0							\n"</span><span class="token comment">/* Update psp */</span>
    <span class="token string">"	isb									\n"</span>
    <span class="token string">"	bx r14								\n"</span>
    <span class="token string">"										\n"</span>
    <span class="token string">"	.align 4							\n"</span>
    <span class="token string">"pxCurrentTCBConst: .word pxCurrentTCB	\n"</span>
    <span class="token operator">::</span><span class="token string">"i"</span> <span class="token punctuation">(</span> configMAX_SYSCALL_INTERRUPT_PRIORITY <span class="token punctuation">)</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

The xPortPendSVHandler function can be roughly divided into three parts:

  1. Save the site

    Before entering the interrupt service function, xPSR, PC, LR, R12 and R3 ‐ R0 are automatically pushed into the appropriate stack by the hardware, while R4-R11 requires us to write our own code for stacking.

  1. Find the task with the highest priority in the current ready task

How to find the ready task is complex, which will be described in detail later. Here, we only need to know that pxCurrentTCBConst has pointed to the task control block with the highest task priority in the ready task.

  1. Restore the site
    Like saving the scene, when exiting from the interrupt service function, the stack will automatically pop up and recover the values of xPSR, PC, LR, R12 and R3 ‐ R0 registers. R4-R11 needs to write its own code to recover before exiting the interrupt service function.

Therefore, if you want to perform context switching (task switching) in FreeRtos, you only need to trigger the PendSV interrupt.

Task control block and task stack

RTOS tasks are all dead loops. Each task has its own independent stack. Where does the stack of tasks come from?
freeRtos in heap_4.c declares a large array. When creating a task, a space will be allocated from the array as the stack of the task according to the specified stack size.

Task control block is a structure that contains all the information of the task. When the kernel is trimmed through macro definition, the members in the task control block will also increase or decrease, and important structure members have been annotated. The switching between tasks mainly uses pxTopOfStack to save the top of the stack. xStateListItem, xEventListItem and uxPriority are related to the task with the highest priority among the search ready tasks, which will be described in detail later.

typedef struct tskTaskControlBlock       /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
    volatile StackType_t * pxTopOfStack; /*< Point to the top of the task stack Switching between tasks requires */
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">if</span> <span class="token expression"><span class="token punctuation">(</span> portUSING_MPU_WRAPPERS <span class="token operator">==</span> <span class="token number">1</span> <span class="token punctuation">)</span></span></span>
    xMPU_SETTINGS xMPUSettings<span class="token punctuation">;</span> <span class="token comment">/*&lt; It is required when using MPU */</span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">endif</span></span>

ListItem_t xStateListItem<span class="token punctuation">;</span>                  <span class="token comment">/*&lt; Status linked list node, which can be hung in different status (ready, blocked, suspended) linked lists */</span>
ListItem_t xEventListItem<span class="token punctuation">;</span>                  <span class="token comment">/*&lt; Linked list node, which can be hung in different queue linked lists to realize queue blocking and other functions */</span>
UBaseType_t uxPriority<span class="token punctuation">;</span>                     <span class="token comment">/*&lt; Task priority */</span>
StackType_t <span class="token operator">*</span> pxStack<span class="token punctuation">;</span>                      <span class="token comment">/*&lt; Point to the starting position of task stack */</span>
<span class="token keyword">char</span> pcTaskName<span class="token punctuation">[</span> configMAX_TASK_NAME_LEN <span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">/*&lt; Task name */</span>

<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">if</span> <span class="token expression"><span class="token punctuation">(</span> <span class="token punctuation">(</span> portSTACK_GROWTH <span class="token operator">&gt;</span> <span class="token number">0</span> <span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token punctuation">(</span> configRECORD_STACK_HIGH_ADDRESS <span class="token operator">==</span> <span class="token number">1</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span></span></span>
    StackType_t <span class="token operator">*</span> pxEndOfStack<span class="token punctuation">;</span> <span class="token comment">/*&lt; Points to the highest valid address for the stack. */</span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">endif</span></span>

<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">if</span> <span class="token expression"><span class="token punctuation">(</span> portCRITICAL_NESTING_IN_TCB <span class="token operator">==</span> <span class="token number">1</span> <span class="token punctuation">)</span></span></span>
    UBaseType_t uxCriticalNesting<span class="token punctuation">;</span> <span class="token comment">/*&lt; Holds the critical section nesting depth for ports that do not maintain their own count in the port layer. */</span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">endif</span></span>

<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">if</span> <span class="token expression"><span class="token punctuation">(</span> configUSE_TRACE_FACILITY <span class="token operator">==</span> <span class="token number">1</span> <span class="token punctuation">)</span></span></span>
    UBaseType_t uxTCBNumber<span class="token punctuation">;</span>  <span class="token comment">/*&lt; Stores a number that increments each time a TCB is created.  It allows debuggers to determine when a task has been deleted and then recreated. */</span>
    UBaseType_t uxTaskNumber<span class="token punctuation">;</span> <span class="token comment">/*&lt; Stores a number specifically for use by third party trace code. */</span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">endif</span></span>

<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">if</span> <span class="token expression"><span class="token punctuation">(</span> configUSE_MUTEXES <span class="token operator">==</span> <span class="token number">1</span> <span class="token punctuation">)</span></span></span>
    UBaseType_t uxBasePriority<span class="token punctuation">;</span> <span class="token comment">/*&lt; The priority last assigned to the task - used by the priority inheritance mechanism. */</span>
    UBaseType_t uxMutexesHeld<span class="token punctuation">;</span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">endif</span></span>

<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">if</span> <span class="token expression"><span class="token punctuation">(</span> configUSE_APPLICATION_TASK_TAG <span class="token operator">==</span> <span class="token number">1</span> <span class="token punctuation">)</span></span></span>
    TaskHookFunction_t pxTaskTag<span class="token punctuation">;</span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">endif</span></span>

<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">if</span> <span class="token expression"><span class="token punctuation">(</span> configNUM_THREAD_LOCAL_STORAGE_POINTERS <span class="token operator">&gt;</span> <span class="token number">0</span> <span class="token punctuation">)</span></span></span>
    <span class="token keyword">void</span> <span class="token operator">*</span> pvThreadLocalStoragePointers<span class="token punctuation">[</span> configNUM_THREAD_LOCAL_STORAGE_POINTERS <span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">endif</span></span>

<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">if</span> <span class="token expression"><span class="token punctuation">(</span> configGENERATE_RUN_TIME_STATS <span class="token operator">==</span> <span class="token number">1</span> <span class="token punctuation">)</span></span></span>
    <span class="token class-name">uint32_t</span> ulRunTimeCounter<span class="token punctuation">;</span> <span class="token comment">/*&lt; Stores the amount of time the task has spent in the Running state. */</span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">endif</span></span>

<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">if</span> <span class="token expression"><span class="token punctuation">(</span> configUSE_NEWLIB_REENTRANT <span class="token operator">==</span> <span class="token number">1</span> <span class="token punctuation">)</span></span></span>

    <span class="token comment">/* Allocate a Newlib reent structure that is specific to this task.
     * Note Newlib support has been included by popular demand, but is not
     * used by the FreeRTOS maintainers themselves.  FreeRTOS is not
     * responsible for resulting newlib operation.  User must be familiar with
     * newlib and must provide system-wide implementations of the necessary
     * stubs. Be warned that (at the time of writing) the current newlib design
     * implements a system-wide malloc() that must be provided with locks.
     *
     * See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html
     * for additional information. */</span>
    <span class="token keyword">struct</span>  <span class="token class-name">_reent</span> xNewLib_reent<span class="token punctuation">;</span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">endif</span></span>

<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">if</span> <span class="token expression"><span class="token punctuation">(</span> configUSE_TASK_NOTIFICATIONS <span class="token operator">==</span> <span class="token number">1</span> <span class="token punctuation">)</span></span></span>
    <span class="token keyword">volatile</span> <span class="token class-name">uint32_t</span> ulNotifiedValue<span class="token punctuation">[</span> configTASK_NOTIFICATION_ARRAY_ENTRIES <span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token keyword">volatile</span> <span class="token class-name">uint8_t</span> ucNotifyState<span class="token punctuation">[</span> configTASK_NOTIFICATION_ARRAY_ENTRIES <span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">endif</span></span>

<span class="token comment">/* See the comments in FreeRTOS.h with the definition of
 * tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */</span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">if</span> <span class="token expression"><span class="token punctuation">(</span> tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE <span class="token operator">!=</span> <span class="token number">0</span> <span class="token punctuation">)</span> </span><span class="token comment">/*lint !e731 !e9029 Macro has been consolidated for readability reasons. */</span></span>
    <span class="token class-name">uint8_t</span> ucStaticallyAllocated<span class="token punctuation">;</span>                     <span class="token comment">/*&lt; Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */</span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">endif</span></span>

<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">if</span> <span class="token expression"><span class="token punctuation">(</span> INCLUDE_xTaskAbortDelay <span class="token operator">==</span> <span class="token number">1</span> <span class="token punctuation">)</span></span></span>
    <span class="token class-name">uint8_t</span> ucDelayAborted<span class="token punctuation">;</span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">endif</span></span>

<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">if</span> <span class="token expression"><span class="token punctuation">(</span> configUSE_POSIX_ERRNO <span class="token operator">==</span> <span class="token number">1</span> <span class="token punctuation">)</span></span></span>
    <span class="token keyword">int</span> iTaskErrno<span class="token punctuation">;</span>
<span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">endif</span></span>

} tskTCB;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78

Topics: Single-Chip Microcomputer stm32 FreeRTOS