Stack usage during RTOS task switching

We know that the Cortex-M3 series microcontrollers have a dual-stack mechanism inside. That is, Cortex-M3 has two stack pointers: main stack (MSP) and process stack (PSP). Only one of them can be used at any time. Control is performed via select bits in the control register CONTROL. The two stack pointers are as follows:

  • Main stack pointer (MSP): The stack pointer used by default after reset for the operating system kernel and exception handling routines (including interrupt service routines)
  • Process Stack Pointer (PSP): Used by the user's application code.

After porting the RTOS to the Cortex-M3 series MCU, the task stack uses the PSP, but the task switching is completed in the interrupt processing function PendSV(). So how does the MCU choose the stack pointer during the execution of instructions during task switching? The following step by step analysis.

Basic operation of the stack

The stack operation is the read and write operation of the memory, and its address is given by SP. The data of the register is stored in the stack through the PUSH operation, and later retrieved from the stack with the POP operation. In the operation of PUSH and POP, the value of SP will be automatically adjusted according to the usage rules of the stack to ensure that the subsequent PUSH will not destroy the contents of the previous PUSH. The function of the stack is to put the data of the register into the memory, and when a task or a subroutine is executed, it can resume and continue to execute. Under normal circumstances, PUSH and POP must be used in pairs, and the participating registers must be completely consistent in terms of identity and sequence. When the PUSH/POP instruction is executed, the value of the SP pointer also decreases/increases accordingly. The Cortex-M3 uses a "growing down full stack" model. The stack pointer SP points to the last 32-bit value pushed onto the stack. When the stack is pushed next time, SP will be decremented by 4 first, and then a new value will be stored.

The POP operation is just the opposite: first read the last value pushed from the SP pointer, and then increment the SP pointer by 4.

When entering the ESR, CM3 will automatically push some registers onto the stack, and the SP pointer (MSP or PSP) that is being used at the moment when this exception occurs is used here. After leaving the ESR, as long as the ESR has not changed CONTROL[1], it will still use the SP pointer being used at the moment when the exception occurs to perform the pop-up operation.

stack usage control

It is already known that the stack of CM3 is divided into two: the main stack and the process stack, and which stack to use (MSP or PSP) is controlled by the special register CONTROL[1]. The control register CONTROL has two purposes: one is used to define the privilege level, and the other is used to select which stack pointer is currently used.

When CONTROL[1]=0, only MSP is used, at this time user program and exception handler share the same stack. This is also the default usage after reset.

When CONTROL[1]=1, thread mode will no longer use MSP, but use PSP (handler mode will always use MSP). What are the benefits of doing this? It turns out that in an OS environment, as long as the OS kernel is only executed in handler mode, user applications are only executed in user mode. This dual-stack mechanism prevents stack errors in user programs from destroying the stack used by the OS.

Introduce two operating modes again, Cortex-M3 supports two modes and two privilege levels:

  • Two modes: handler mode and thread mode
  • Two privilege levels: Privileged and User

When the processor is in thread state, either privileged or user mode can be used; handler mode, on the other hand, is always privileged. After reset, the processor enters thread mode + privilege level. Code at the privileged level can enter user level by setting CONTROL[0]. Regardless of any abnormality caused by any reason, the processor will run its service routine at a privileged level, and after the exception returns, the system will return to the level at which the exception was generated. Code under user level can no longer attempt to modify CONTROL[0] to return to privileged level. It must go through an exception handler, and the exception handler modifies CONTROL[0] in order to get the privilege level after returning to thread mode.

User code running in threaded mode uses the PSP, while exception service routines use the MSP. The switching of these two stack pointers is intelligent and fully automatic, and is handled by the CM3 hardware at the beginning and end of exception servicing.

   Information through train: Linux kernel source code technology learning route + video tutorial kernel source code

Learning through train: Linux kernel source code memory tuning file system process management device driver/network protocol stack

interrupt handling

The first action in response to an exception is to automatically save the necessary parts of the context: xPSR, PC, LR, R12, and R3-R0 are automatically pushed onto the appropriate stack by hardware in that order. When responding to an exception, if the current code is using PSP, push into PSP, that is, use the process stack; otherwise, push into MSP, use the main stack. Once the service routine is entered, the main stack is always used. After entering the exception service routine, the value of LR (link register R14) will be automatically updated to the special EXC_RETURN. This is a value whose upper 28 bits are all 1, and only the value of [3:0] has special meaning, as shown in the table below. When the exception service routine sends this value to the PC, it initiates the processor's interrupt return sequence. Because the value of LR is automatically set by CM3, so as long as there is no special requirement, do not change it.

To summarize, three legal EXC_RETURN values ​​can be derived:

If the main program runs in thread mode and is interrupted when using MSP, then LR=0xFFFF_FFF9 in the service routine (LR before the main program is interrupted has been automatically pushed into the stack). If the main program runs in thread mode and is interrupted when using PSP, then LR=0xFFFF_FFFD in the service routine (LR before the main program is interrupted has been automatically pushed into the stack).

PendSV Interrupt Introduction

SVC (system service call, also referred to as system call) and PendSV (suspension system call), which are mostly used in software development on top of the operating system. SVC is used to generate call requests of system functions. SVC exceptions are generated by executing the "SVC" instruction. This instruction requires an immediate value, which acts as a system call identifier. The SVC exception service routine will extract this code later to explain the specific requirements of this call, and then call the corresponding service function. For example,

SVC 0x0 ; 调用 0 号系统服务

After the SVC service routine is executed, the address of the SVC instruction executed last time can be calculated according to the return address automatically pushed into the stack. After finding the SVC instruction, it can read the machine code of the SVC instruction, extract the immediate value from the machine code, and know the function code requested to be executed. If the user program is using the PSP, the service routine also needs to execute  MRS Rn,PSP instructions to obtain the stack pointer of the application program. By analyzing the value of LR, you can know which stack is being used when the SVC instruction is executed. PendSV (a suspendable system call) is used in conjunction with SVC. SVC exceptions must be responded immediately (for SVC exceptions, if the priority is not higher than the current one being processed, or other reasons make it impossible to respond immediately, it will cause a hard fault), when the application executes SVC The desired request is expected to be responded to immediately. PendSV is different, it can be suspended like a normal interrupt. The OS can use this to "suspend execution" of an exception - not to perform an action until other important tasks have completed. The method to suspend PendSV is: manually write 1 to the PendSV suspension register of NVIC. After suspension, if the priority is not high enough, it will be postponed to wait for execution. A typical use case for PendSV is during context switching (switching between different tasks). For example, in a system with two ready tasks, context switches can be triggered when:

  • execute a system call
  • System tick timer (SYSTICK) interrupt, (required in round-robin scheduling)

The PendSV exception automatically delays the context switch request until all other ISRs have finished processing. To implement this mechanism, PendSV needs to be programmed as the lowest priority exception. If the OS detects that an IRQ is active and is being preempted by a SysTick, it will raise a PendSV exception in order to suspend the execution of the context switch.

  1. Task A calls the SVC to request a task switch (for example, to wait for some work to complete)
  2. The OS receives the request, prepares for the context switch, and pends a PendSV exception.
  3. When the CPU exits SVC, it immediately enters PendSV, thereby performing a context switch.
  4. When PendSV finishes executing, it will return to task B and enter thread mode at the same time.
  5. An interrupt occurs and the interrupt service routine begins execution
  6. During ISR execution, a SysTick exception occurs and the ISR is preempted.
  7. The OS performs the necessary operations, then pend raises the PendSV exception in preparation for the context switch.
  8. When SysTick exits, return to the previously preempted ISR, and the ISR continues to execute
  9. After the ISR finishes executing and exits, the PendSV service routine starts executing and a context switch is performed inside
  10. When PendSV is executed, return to task A, and the system enters thread mode again

Dual stack operation in RTOS system

A truly robust CM3 software system must use a real-time operating system kernel, which usually meets the following requirements:

  • Service routines use MSP
  • Although the exception service routines use MSP, they can still continue in content after returning in form - and at this time, they can also use PSP, so as to realize "preemptible system calls" and greatly improve real-time performance
  • Through SysTick, the code of the real-time kernel is called every fixed time, runs at the privilege level, and is responsible for task scheduling, task time management and other routine maintenance of the system
  • User applications run as threads, use the PSP, and run at user level
  • When the kernel executes key parts of the code, MSP is used, and when supplemented with MPU, the stack corresponding to MSP only allows privileged access

In the operating system, the modification of EXC_RETURN is just a common and basic requirement. After starting to schedule the user program, there must be a SysTick exception, which periodically transfers the execution right to the operating system, so that routine system management and necessary round-robin scheduling can be maintained. The task switching process is shown in the figure:

The figure above is the round-robin scheduling mode diagram of SysTick abnormal promotion time slice. Here, the context switch is performed using PendSV (a lowest priority exception), which eliminates the possibility of a context switch in the interrupt service routine. Perform the necessary operations in the SysTick interrupt routine, then hoist the PendSV exception in preparation for the context switch. After exiting the SysTick interrupt handler, the PendSV service routine starts executing and context switching is performed inside. PSP (Thread Stack) is used during task-1 and task-2 program execution. After entering the interrupt service routine (SysTick and PendSV) the MSP (main stack) is used internally.

 

Guess you like

Origin blog.csdn.net/youzhangjing_/article/details/131596064