Embedded engineers implement a simple operating system

Task and task switching in operating system

1. Why is there a task?

    We often see the operating system and often have to learn the operating system. From a small white point of view, everything has its opposite things, or affairs develop step by step. Since there is an operating system, there must be software without an operating system.

    If everyone has learned to play with MCU, such as 51 MCU or STM32 Single chip microcomputer, then we should all have such experience in the era of no operating system. In the era of no operating system, it can be regarded as an operating system with one task. What is the software flow like without an operating system?

No operating system, bare metal environment

/*
 *    No operating system:
 *    The four function functions can only be executed in sequence,
 *    When a blocking event occurs in any function,
 *     For example, some functions require some delay operations, which will occupy the processor's time as empty
 *    Other functions cannot be performed
 */
while(1)
{
    taskA();

    taskB();

    taskC();

    taskD();
}

With operating system environment

/*
 *    With operating system:
 *    The four function functions can be executed at any time,
 *    When a blocking event occurs in any function
 *    The operating system can schedule and switch tasks to perform other tasks
 *    Macroscopically, it can be regarded as four functional tasks that can be performed by colleagues
 */
taskA();

taskB();

taskC();

taskD();

So we need an operating system to improve the efficiency of our processor and do meaningful things every moment of the processor.

This indirectly improves the performance of the processor.

To sum up, the biggest difference between an operating system and no operating system is that multiple tasks can be executed in parallel under the operating system, and can be switched between tasks to select appropriate tasks to execute on the processor.

Therefore, an operating system we implement first realizes the following functions

  • You can create tasks
  • You can switch between different tasks

With the above functions, it can be said that the transformation from no operating system to operating system has been realized

The operating system is a god perspective, and each task is a function of a wireless loop

void taskA()
{
    initA();
    while(1)
    {
        aaa();
    }
}

void taskB()
{
    initB();
    while(1)
    {
        bbb();
    }
}

2. How to realize task switching

2.1 task switching process

    I don't know how much you know about functions and processor operation. Task switching is a bit similar to the process of function call,But there will be many differences
void taskA()
{
    initA();
    while(1)
    {
        aaa();
        bbb();
        ccc();
        ddd();
    }
}

void taskB()
{
    initB();
    while(1)
    {
        xxx();
        yyy();
        zzz();
    }
}

Assuming that there are two tasks executed in infinite loops, the possible execution process is as follows

aaa -> xxx -> bbb -> ccc -> yyy -> zzz -> xxx -> ddd -> aaa -> yyy

In the above execution process, the processor executes back and forth in two cycles, which must be the process of task switching

When a processor executes a function, there are several important elements:

  • stack
  • PC program pointer
  • LR program return pointer
  • Other general purpose registers

Each function has its own stack content. When entering the function, open up a stack space, and the stack stores the following contents:

  • Parameters of function
  • Local variables defined within a function

Because the parameters and local variables of each function are private to the function and cannot be modified at will, consider the stack when switching tasks

Then there is the PC program pointer. The processor executes that code program, which is determined by the PC pointer. The processor always takes instructions from the address stored in the PC pointer for execution.

Therefore, switching the processor from one task to another may have something to do with the PC pointer

2.2 what needs to be done for task switching

Let's think about how to switch from one task to another?

1. Protect stack, save stack

    A typical processor contains a SP Stack register, which stores the address of the current stack. We need to switch tasks now, and switch back from other tasks after a period of time. Therefore, we need to save the stack pointer before switching, and recover the stack pointer of the target task during the switching process

2. Jump to target task

    When we switch from the current task to the target task, we need to save the current running address before switching. Similarly, the target task has saved the running address, so switching the target task is to restore the saved running address of the target task to PC Pointer to let the processor execute the target task

3. Save general register

    The general register stores some useful information during function execution. It should be saved before task switching and restored when switching to the target task

4. Where is the above information stored

    Define and create a structure for each task to store the above important information

2.3 materials for preparing switching tasks

The above describes what is needed for task switching. Now that we know what is needed, we begin to prepare

  1. Define a structure for saving task information, including the following parts:
  • Stack pointer
  • PC pointer
  • General register storage
struct task{    void *sp;    void *pc;    uint64_t regs[32];}
  1. Create a task

To create a task, you need to fill in the task structure, define a task initialization function, and provide the following parameters

  • Task structure
  • stack address
  • Task entry address
/**
 * @brief Create a task
 *
 * @param t Task structure variable pointer
 * @param sp_addr The stack address of the task
 * @param pc_addr The starting address of the task
 * @return int
 */
int task_init(struct task *t, void *sp_addr, void *pc_addr)
{
    t->sp = sp_addr;
    t->pc = pc_addr;

    return 0;
}

The above function completes the initialization of a task

Now that we have prepared the materials required for the task, we can start switching

2.4 implementation of switching tasks

Specific task switching can be divided into several types:

  • From the start of the system to the start of the first task, if there is no task at present, switch to the first task

    task_switch_to(struct task *task_to);
    
  • Currently in one task, switch to another task

    task_switch_from_to(struct task *task_from, struct task *task_to)
    

    We should think about how to fill and implement the above functions. Of course, this part is the most relevant part to the specific processor architecture

You can learn how common RTOS realize the above functions under various architectures, especially aarch64 architecture, because my current operating system is based on aarch64 processor architecture

The implementation of the above two task switching functions has been uploaded to the project code of github, which can be browsed and viewed

//According to the calling process of C language and assembly language, the first parameter is stored in x0 register
//Our function passes the task structure pointer
task_switch_to:
    ldr x1, [x0]    //The pc address of the task is the first parameter and is read into the x1 register
    mov x30, x1     //Put the pc address into the x30 register, which is the lr register. When the switch is completed later, it will return to the task for execution

    add x0, x0, 8   //The offset of sp parameter in structure is octet
    ldr x1, [x0]    //Remove and set sp address
    mov sp, x1

    ret             //The function returns and jumps to the storage address of x30 register for execution

If you are not clear about the above steps, you can use vscode with gdb for debugging, step-by-step code execution, track the execution process of the processor, and try to see if the task switching can be completed correctly

At present, the above code has been implemented in the github project, which can be downloaded and tried to debug and execute

Project address: https://github.com/jhbdream/armv8_os.git

Welcome to star

Tags: C Operating System ARM RTOS

Posted on Sun, 10 Oct 2021 09:55:59 -0400 by jwalsh