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
- 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];}
- 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