因为只考虑单个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,这就会没法正常执行。
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;
}
一些说明:
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
一些说明: