学习上下文交换

课前复习: RISC-V 暂存器

在先前的文章已经有详细介绍 RISC-V 的暂存器。由於本篇文章有阅读原始码的需求,所以将暂存器的对照表贴上方便读者使用。

进入正题

我们日常所使用的电脑都能够同时处理多个任务,像是: 同时执行浏览器以及程序编辑器。
看似简单的功能,作业系统其实在背後做了非常多的处理,试想: 在单核心电脑上运行 Windows 作业系统,而单核心一次仅能完成一个作业时,电脑是如何达到多工的需求呢?
其实,即使我们一次打开了很多应用程序,单核心电脑也只能在同一时间处理一项任务。为了让使用者不易察觉出来,电脑会先花一点时间处理程序 A,再处理程序 B, C...,就像是:

A -> B -> C -> A (done!) -> B -> C -> B -> C (done!) -> B (done!)

透过在任务之间进行快速切换,做到多工的功能。

Process control block

不同的程序在运行时 (Process) 也会有各自的资料,因此电脑在切换下一个 Process 前会需要将目前运行中的 Process 内的资料储存下来。
在作业系统中,有一个特别的资料结构被用来存放 Process 的独立资料,该资料结构被称之为 Process control block,其构造如下图:

PCB content: Description
Process state 可以是 new、ready、running、waiting 或 blocked 等。
Process Id 用於识别 Process,就像人类的身分证字号一样。
Program counter 待执行指令的位址。
registers 该 Process 执行时 CPU 暂存器存放的资料。
memory limits --
list of open files 纪录开启了哪些档案

Context switch

上下文交换 (Context switch) 能让多个 Process 共享同一个 CPU 的运算资源。

内文切换有三种常见状况:

  • 多工
  • 中断处理
  • 用户与内核状态的交换

在本篇所阅读的原始码中属於第一种状况 (从 main 转移到 task0)。

实际应用

本篇使用 mini-riscv-os 作为教学范例。

首先,我们可以在 os.c 看到 task0 以及主回圈的定义:

#include "os.h"

#define STACK_SIZE 1024
uint8_t task0_stack[STACK_SIZE];
struct context ctx_os;
struct context ctx_task;

extern void sys_switch();

void user_task0(void)
{
	lib_puts("Task0: Context Switch Success !\n");
	while (1) {} // stop here.
}

int os_main(void)
{
	lib_puts("OS start\n");
	ctx_task.ra = (reg_t) user_task0;
	ctx_task.sp = (reg_t) &task0_stack[STACK_SIZE-1];
	sys_switch(&ctx_os, &ctx_task);
	return 0;
}

系统开机後,会在主画面印出 OS start,随後开始进行 Context switch
为了顺利切换到 task0 执行,我们需要将 ra (return address) 以及 sp (Stack pointer) 分别指向 task0 和 task 的 stack 位址。

ctx_task.ra = (reg_t) user_task0;
ctx_task.sp = (reg_t) &task0_stack[STACK_SIZE-1];

关於任务的堆叠空间,我们可以参考你所不知道的 C 语言:函式呼叫篇取得更进一步的资讯。

指派完成後,开始进行上下文交换:

sys_switch(&ctx_os, &ctx_task);

sys_switch 被定义在 sys.s 中:

# Context switch
#
#   void sys_switch(struct context *old, struct context *new);
# 
# Save current registers in old. Load from new.

.globl sys_switch
.align 4
sys_switch:
        ctx_save a0  # a0 => struct context *old
        ctx_load a1  # a1 => struct context *new
        ret          # pc=ra; swtch to new task (new->ra)

根据 sys.s,我们可以清楚的知道 sys_switch 的 payload 会分别存在 a0 以及载入到 a1 暂存器中。

至於为什麽会存放在 a0 和载入到 a1 暂存器,可以参考一开始所附上的暂存器参考表。
BTW: 在 RISC-V 中,若 a0 - a7 都被放满,多余的参数才会存放在堆叠内。

此外,ctx_save 以及 ctx_load 的定义也同样可以在 sys.s 找到:

# ============ MACRO ==================
.macro ctx_save base
        sw ra, 0(\base)
        sw sp, 4(\base)
        sw s0, 8(\base)
        sw s1, 12(\base)
        sw s2, 16(\base)
        sw s3, 20(\base)
        sw s4, 24(\base)
        sw s5, 28(\base)
        sw s6, 32(\base)
        sw s7, 36(\base)
        sw s8, 40(\base)
        sw s9, 44(\base)
        sw s10, 48(\base)
        sw s11, 52(\base)
.endm

.macro ctx_load base
        lw ra, 0(\base)
        lw sp, 4(\base)
        lw s0, 8(\base)
        lw s1, 12(\base)
        lw s2, 16(\base)
        lw s3, 20(\base)
        lw s4, 24(\base)
        lw s5, 28(\base)
        lw s6, 32(\base)
        lw s7, 36(\base)
        lw s8, 40(\base)
        lw s9, 44(\base)
        lw s10, 48(\base)
        lw s11, 52(\base)
.endm
# ============ Macro END   ==================

其目的就是将处理器的暂存器状态储存/载入,让下方的程序码可以做到上下文切换的作用:

# Context switch
#
#   void sys_switch(struct context *old, struct context *new);
# 
# Save current registers in old. Load from new.

.globl sys_switch
.align 4
sys_switch:
        ctx_save a0  # a0 => struct context *old
        ctx_load a1  # a1 => struct context *new
        ret          # pc=ra; swtch to new task (new->ra)

ret 指令执行後,便会将 ra 暂存器存放的内容 (函式 user_task0 的位置) 指派给程序计数器。
如此一来,上下文切换就顺利完成了!

Reference


<<:  Day 17:AWS是什麽?30天从动漫/影视作品看AWS服务应用 -《ExMachina》

>>:  Day 28 权限宝石:IAM Role 建立与使用

Unity自主学习(十八):认识Unity介面(9)

昨天看完了"Transform"栏位之後,接下来"属性检视区"...

Day 7 - DOM - Window Object

之前介绍的只是 JavaScript 的基本语法,今天要来介绍 DOM(Document Objec...

Day11 HTML一

如果有一些编写网页的基础,在之後撰写爬虫程序时会比较轻松呦~ 接下来的几天,会简单地介绍HTML与C...

[Golang]同步工具-sync包的WaitGroup-心智图总结

1. WaitGroup类型有三个指针方法,Add、Done、Wait A. 这个类型提供ㄧ个计数器...

Day-4 演算法分析概念

分析演算法 分析演算法,即是分析一个演算法的效率,来决定我们要使用哪一种演算法,而效率的分析方式通常...