RISCV-LEARN

Context Switch

1 思路

因为只考虑单个hart的情况,所以startup实验首先在汇编代码中判断当前hart id是否0,否则wfi,而后跳转入C语言函数;为了方便调试,uart实验通过uart和qemu模拟串口线实现了打印字符串的功能;ld在链接目标文件时会决定os.elf中的各个段会如何放置在物理内存上,为了自己管理内存,memory实验通过linker script来决定段放在物理内存的哪个位置,以及内存中数据段、代码段的划分和堆空间的划分和分配。至此,一些准备工作基本完成,操作系统比较贴近用户的功能还是执行任务。

因为这里只有单个hart,故而如果要执行多任务,那么一定是并发的,即涉及任务的调度,这里先实现协作式多任务,即每个任务会主动放弃hart的使用权,供其他任务使用。多任务指的是存在多个互不相关的任务,这和函数调用有区别,在一个任务中,无论内部存在怎样的函数调用过程,都被认为是一个任务。而多任务指的是,多个任务,先执行任务1到a位置,再去执行任务2,然后任务2主动放弃hart,拐回来接着任务1的a位置继续执行,给人一种多个任务在同时执行的感觉。

大致思路如下:

注:在任务创建和调度前需要做一个初始化操作,即将mscratch的值写为0,以便后续第一个任务被放在hart上执行时避免保存上下文;倘若在第一个任务放在hart时,不避免保存上下文,那么会将当前上下文保存在mscratch所指的内存部分,会出现异常访问的问题,因为此时mscratch的值位置或为0。

注:因为在Makefile里面没有明确规定链接目标文件的顺序,故而会按照文件名来排序,若start.o文件不在链接过程的第一个,那么就会出错,即_start函数不在0x80000000,而是其他函数在0x80000000,这就会没法正常执行。

2 实现

2-1 任务的创建

uint8_t __attribute__((aligned(16))) tasks_stack[MAX_TASKS][STACK_SIZE];
struct context tasks_ctx[MAX_TASKS];

int task_create(void (*task_entry)(void))
{
    if (_top < MAX_TASKS)
    {
        tasks_ctx[_top].ra = (reg_t)task_entry;
        tasks_ctx[_top].sp = (reg_t)&tasks_stack[_top][STACK_SIZE];
        _top++;
        return 0;
    }
    else
        return -1;
}

一些说明:

2-2 任务的调度

void schedule()
{
    if (!_top)
        panic("No Task has been created");
    else
    {
        _current = (_current + 1) % _top;
        struct context *next = &tasks_ctx[_current];
        switch_to(next);
    }
}

.text
.global switch_to
.balign 4
switch_to:
    csrrw t6, mscratch, t6
    beqz t6, init

    write_ctx_from_reg t6
    csrr t5, mscratch
    STORE t5, 30*REG_SIZE(t6)

init:
    csrw mscratch, a0
    mv t6, a0
    write_reg_from_ctx t6
    ret
.end

一些说明: