先抛出来一个问题,为什么不直接使用 mscratch ?
直接使用 mscratch 作为基地址是不合适的,因为 mscratch 是一个CSR寄存器,而 reg_save 和 reg_restore 宏需要一个通用寄存器作为基地址。此外,mscratch 不能用于加载和存储指令的基地址,因为这些指令只接受通用寄存器作为基地址。
内核启动
#include "os.h"
//外部文件定义的函数
extern void uart_init(void);
extern void page_init(void);
extern void sched_init(void);
extern void schedule(void);
void start_kernel(void)
{
uart_init();
uart_puts("Hello, RVOS!\n");
page_init(); //内存初始化
sched_init(); //多任务切换的初始化
schedule();//触发多任务切换
uart_puts("Would not go here!\n");
while (1) {}; // stop here!
}
切换的核心逻辑
static void w_mscratch(reg_t x) {
asm volatile("csrw mscratch, %0" : : "r"(x));
}
void user_task0(void);
void sched_init() {
w_mscratch(0);
// ctx_task没有切换之前这里只是一段内存空间
// sp 是内存模拟,这代区域代表,栈空间,给了一个新的地址, 这个很重要, 灵魂
// ra 也是内存模拟。这会还都是内存空间,没啥实际意义,需要触发才有意义
// sp 是ra 这个任务用到的栈空间
ctx_task.sp = (reg_t)&task_stack[STACK_SIZE];
ctx_task.ra = (reg_t)user_task0;
}
void schedule() {
struct context *next = &ctx_task;
//切换给当前线程的一个任务,这里出发一个函数调用
//a0=next
switch_to(next);
//退出之后, 有xxx输出,正常应该是走不到
}
// 下面是任务的模拟, 没啥好说的
void task_delay(volatile int count) {
count *= 50000;
while (count--)
;
}
void user_task0(void) {
uart_puts("Task 0: Created!\n");
while (1) {
uart_puts("Task 0: Running...\n");
task_delay(1000);
}
}
接下来分析一个switch_to 函数
.text
.globl switch_to
.balign 4
switch_to:
# 从总的执行过程来看:
# 函数第一次被调用的时候,mscratch=0,执行到label=1地方,建议先看label=1 之后的代码
# 函数第二次调用的时候,mscratch=a0=要执行任务的地址, 不等于0,执行到 reg_save t6, 把自己保存起来
# mscratch 相当于一个任务的指针,为啥没有直接用它呢? 而是用一个t6?
# 原因是-->
csrrw t6, mscratch, t6
beqz t6, 1f
reg_save t6
mv t5, t6
csrr t6, mscratch
STORE t6, 30*SIZE_REG(t5)
1:
csrw mscratch, a0 #此时,a0是next指针
mv t6, a0
#相当于一个环境的初始化, 触发之前的准备,把内存模拟的sp,ra 等真正的加载到寄存器中
reg_restore t6
#伪指令 相当于 jalr x0, ra, 0。
#开始触发,跳转到ra的地址处执行,ra 存储的是任务的地址。sp存储的是任务的栈
#ret 执行的是ra , 不是return 关键字
#这个函数严格来说,只是一个切换任务的函数
ret
.end
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。