Location>code7788 >text

[rCore Study Notes 023] Task Switching

Popularity:772 ℃/2024-08-08 12:19:32

guide (e.g. book or other printed material)

Or should I read it first?Official Handbook.

Comrades who have studied DMA may be better able to understand, in a word.Releasing the CPU bus :
If the whole process of application execution is further analyzed, it can be seen that when the program accesses the I/O peripherals or sleeps, in fact, does not need to occupy the processor, so we can divide the application execution process at different times into two categories, the computational phase of the effective task of occupying the processor and the waiting phase that does not need to occupy the processor. These phases form a familiar "pause-continue..." combination of control flow or execution history. The entire control flow from the beginning to the end of an application's execution is the entire execution of the application.
The focus of this section is on the core mechanisms of the operating system - theTask Switching In the kernel this mechanism is in the__switch function. Task switching supports scenarios where an application actively or passively surrenders CPU usage in the middle of a run, where it can only pause execution and wait until the kernel reallocates processor resources before resuming and resuming execution.

Conceptualization of the mandate

Here's a direct viewOfficial Handbook.

Here are some of the main concepts mentioned, and I'll summarize them.

  1. The execution fragment is called "computational task slice (computing) ”
  2. Idle segments are called "idle task piece ”
  3. Resources that need to be preserved and restored are called "mission context ”

Different types of contexts and switching

This part was repeated earlier in Chapter 2 when we reviewed Chapter 1's knowledge about the Chapter 1function call stack and the second chapter ofKernel Stack/User Stack analogies and distinctions.

Here's a direct viewOfficial HandbookJust review it. It's supposed to be forTask Switching Laying the groundwork.

In my mind, task switching is a straightforward way of usingspThe process of manipulating the pointer, as we learned in the previous chapter, only requires that you set the(textual) context Just save it to the user stack. Possible additions.

  1. Adding in-stack functionality to the user stack
  2. To add a function to switch apps, you may need to call assembly code, kind of like__restore, but it doesn't need to be triggeredTrap.

Design and implementation of task switching

Official HandbookThe similarities and differences mentioned in are still very different from those summarized in my own head: the

  • Unlike Trap switching, it does not involve privilege-level switching;
  • Unlike the Trap switch, which is part of theCompiler help Done;
  • As with Trap switching, it is the same for applications that areopen (non-secretive) The.

this oneCompiler help cap (a poem)Transparent to the application This is something that needs to be taken into account in the follow-up process.

Flow of task switching.

  1. An application Trap toS-mode in the kernel of the operating system.
  2. Trap control flow calls__switch.
  3. Trap control flowA It will be paused and switched out first.
  4. The CPU turns to run another Trap control flow applied in the kernelB
  5. Then, at some appropriate time, the original Trap control flowA before it is taken from one of the Trap control streams.C (Most likely not the one it switched to before)B ) switches back to continue execution and eventually returns

concern: Why does it go into Trap if it doesn't need a privilege level switch? How does it trap? Does it do it through theecallWhat? - I don't know.

From a realization point of view.__switch The core difference between a function and a regular function is simply that it willbonded warehouse 。

Speaking of stack context switching, we have to think of the last chapter where we preserved the containment ofCSRcap (a poem)X0~X31context, then similarly, in the context of theTask Switching The process also has the context of the task: the

Look carefully at the chart. On the left side, it says.(of a computer) run state of a task that keeps two parts of its kernel stack.

  1. What we learned in the last chapterTrapContext
  2. in that caseTrapafterwardssppointer to the kernel stack, some of the context of the function call is also stored in the kernel, except for theTrapContextThe kernel stack also holds theTrapHandlerfunctionalcall stack information .

On the right side it saysintend state of a task (you can see a detail of thesp processor registernot pointed at This stack).

In order to ensure thatspRe-pointing to the right kernel stack is able toRestoration of the site , so there must be something that needs to be saved, and that is the task context.

Define heremission context : Some of the CPU's current registers.

You can see that both the left and right side of the diagrams have a lowerTASK_MANAGER, which is an analog of our previous chapter's implementation of theAPP_MANAGERIt's a structure.TaskManageranglobal instance .

You can see that it saves thesp,ra,s0~s11etc. registers.for what reason? These registers have to be saved in order toassurances The ability of the task to continue to run is the focus of the rest of our study.

insofar asTaskManagerSpecific implementationsOfficial HandbookProviding ideas and details, the

  1. Realization of aTaskControlBlockStructures for storing task contextsTaskContext.
  2. because ofTaskManagerRealization of aTaskControlBlockArray for storing multiple contexts.

For the Trap control flow of the currently executing task, we use a control flow namedcurrent_task_cx_ptr variable to hold the address where the current task context is placed; and with thenext_task_cx_ptr variable to hold the address of the context where the next task to be executed will be placed.

Here directly look at the schematic, you can see that the implementation of acurrent_task_cx_ptr cap (a poem)next_task_cx_ptr parameterizedswtichfunction to switch contexts.

One thing that this also illustrates is that the control flow itself can sense before a switch is made.

  1. Which task is currently running
  2. Which is the next task to be performed?

Official HandbookFour phases of task switching are described for us.

  • Stage [1]: Calling in Trap Control Flow A__switch Previously, the only information on the kernel stack of A was the Trap context and the call stack information of the Trap handler function, while B was switched out earlier;
  • Phase [2]: A Saves a snapshot of the CPU's current registers inside the A task context space;
  • Stage [3]: This step is extremely critical, reading thenext_task_cx_ptr B task context pointed to, recovered based on what was saved in the B task contextra Registers,s0~s11 registers as well as thesp Registers. Only after this step is done, the__switch It is only possible to do a function that executes across two control streams, i.e.By switching the stacks, the control flow is also switched. 。
  • Stage [4]: After the register recovery in the previous step is completed, you can see that by recovering thesp registers are swapped onto Task B's kernel stack, which in turn enables the control flow switch. This is why the__switch It is possible to do a function execution across two control streams. Thereafter, when the CPU executesret Assembly pseudo-instruction completion__switch After the function has returned, task B can start with a call to the__switch position continues down the execution.

Here we can just look at__switchSpecific implementations of.

# os/src/task/

.altmacro
.macro SAVE_SN n
    sd s\n, (\n+2)*8(a0)
.endm
.macro LOAD_SN n
    ld s\n, (\n+2)*8(a1)
.endm
    .section .text
    .globl __switch
__switch:
    # point [1]
    # __switch(
    # current_task_cx_ptr: *mut TaskContext,
    # next_task_cx_ptr: *const TaskContext
    # )
    # point [2]
    # save kernel stack of current task
    sd sp, 8(a0)
    # save ra & s0~s11 of current execution
    sd ra, 0(a0)
    .set n, 0
    .rept 12
        SAVE_SN %n
        .set n, n + 1
    .endr
    # point [3]
    # restore ra & s0~s11 of next execution
    ld ra, 0(a1)
    .set n, 0
    .rept 12
        LOAD_SN %n
        .set n, n + 1
    .endr
    # restore kernel stack of next task
    ld sp, 8(a1)
    # point [4]
    ret

There shouldn't be much to read here, so I've drawn a diagram to illustrate it.TaskContextThe memory situation of.

homologousrustCode for.

// os/src/task/

pub struct TaskContext {
    ra: usize,
    sp: usize,
    s: [usize; 12],
}

Here's a note.

  1. In the RISC-V architecture, theraThe Return Address Register (RAR) is a special general-purpose register, numberedx1. This register is mainly used to store the return address, which is the address of the instruction that should be returned after a function call. When a function is called, the caller usually stores the return address in thera registers in order to be able to return correctly to the call point after the function has finished executing.exist__swtichAt the end of the execution use theretreturn toraWe have modified the position of therafor the next task to be executed in the context of theraNaturally, execution will continue until the last time the executed task saves its context by calling the__swtichlocation .
  2. The Rust/C compiler automatically generates code at the beginning of the function to hold thes0~s11 These are saved registers by the caller. However, the__switch is a special function written in assembly code that won't be handled by the Rust/C compiler, so we'll need to include it in the__switch Manually programmed saves in thes0~s11 assembly code.
  3. The reason you don't need to save the other registers is because: of the other registers, the ones belonging to the caller are saved by the compiler in thehigh level language in the calling function written byautomatic generation code to accomplish the saving; there are also registers that are temporary registers and do not need to be saved and restored.

homologousthird point It should be understood that the use ofRustcall in order for the compiler to automatically help us with theSave/restore caller save registers :

// os/src/task/

global_asm!(include_str!(""));

use super::TaskContext;

extern "C" {
    pub fn __switch(
        current_task_cx_ptr: *mut TaskContext,
        next_task_cx_ptr: *const TaskContext
    );
}