Appearance
start.c
#include "types.h"
#include "param.h"
#include "memlayout.h"
#include "riscv.h"
#include "defs.h"
void main();
void timerinit();
TIP
entry.S 需要为每个 CPU 核(hart)准备一个栈空间。 xv6 在机器模式(M-mode)下运行,因此需要一个物理地址的栈。 attribute ((aligned (16))) 确保了栈是 16 字节对齐的, 这是 RISC-V ABI(应用程序二进制接口)的要求。
__attribute__ ((aligned (16))) char stack0[4096 * NCPU];
TIP
entry.S 在机器模式(M-mode)下,设置好栈指针后会跳转到这里。 RISC-V 处理器启动时处于 M-mode,具有最高权限。 start() 函数的职责是完成从 M-mode 到监管者模式(S-mode)的切换, 并为内核的 C 代码执行做好准备。
void
start()
{TIP
- 设置下一次
mret指令的目标特权级别为监管者模式(S-mode)。mstatus是一个控制状态寄存器。MSTATUS_MPP_MASK用于屏蔽掉 MPP(Machine Previous Privilege)字段,然后将其设置为 S-mode。 当mret执行时,CPU 的特权级别会切换到 MPP 字段指定的值。
unsigned long x = r_mstatus();
x &= ~MSTATUS_MPP_MASK;
x |= MSTATUS_MPP_S;
w_mstatus(x);
TIP
- 设置
mret指令的返回地址为main函数。mepc(Machine Exception Program Counter)存放的是异常返回地址。 通过将main函数的地址写入mepc,mret将会跳转到main函数执行。 这需要 GCC 的-mcmodel=medany编译选项,以确保能生成位置无关的代码。
w_mepc((uint64)main);
TIP
- 禁用分页机制,切换到物理地址模式。
satp(Supervisor Address Translation and Protection)寄存器控制分页。 写入 0 会关闭虚拟地址到物理地址的转换。 在内核页表建立之前,我们只能使用物理地址。
w_satp(0);
TIP
- 将所有中断和异常委托给监管者模式(S-mode)处理。
medeleg(Machine Exception Delegation) 和mideleg(Machine Interrupt Delegation) 寄存器分别控制异常和中断的委托。 将所有位置为 1,意味着 M-mode 将所有类型的异常和中断都直接委托给 S-mode 处理, 这样内核就可以在 S-mode 下统一处理,而无需陷入到 M-mode。
w_medeleg(0xffff);
w_mideleg(0xffff);
TIP
- 开启 S-mode 下的外部中断(SEIE)、时钟中断(STIE)和软件中断(SSIE)。
sie(Supervisor Interrupt Enable) 寄存器控制 S-mode 下的中断使能。
w_sie(r_sie() | SIE_SEIE | SIE_STIE | SIE_SSIE);
TIP
- 配置物理内存保护(PMP),允许 S-mode 访问所有物理内存。 PMP 是 RISC-V 的一个安全特性,可以限制对物理内存的访问。 这里通过设置 PMP 寄存器,将整个物理地址空间配置为对 S-mode 可读、可写、可执行。
pmpaddr0设置地址范围,0x3fffffffffffffull覆盖所有 56 位物理地址。pmpcfg0设置权限,0xf表示读(R)、写(W)、执行(X)权限都开放。
w_pmpaddr0(0x3fffffffffffffull);
w_pmpcfg0(0xf);
TIP
- 初始化时钟中断。 这将为每个 CPU 核设置一个定时器,以便进行周期性的任务调度。
timerinit();
TIP
- 将每个 CPU 核的硬件线程ID(hartid)保存在其
tp寄存器中。tp寄存器在 RISC-V 中通常用作线程指针。 xv6 利用它来快速获取当前 CPU 的 ID,cpuid()函数会读取这个值。
int id = r_mhartid();
w_tp(id);
TIP
- 执行
mret指令,正式切换到监管者模式(S-mode)并跳转到main函数。 这是从 M-mode 到 S-mode 的关键一步。
asm volatile("mret");
}
TIP
为每个 hart 设置时钟中断。 S-mode 不能直接访问 mtimecmp,但可以通过 stimecmp 来间接设置。
void
timerinit()
{TIP
- 在 M-mode 下,使能 S-mode 的时钟中断。
mie(Machine Interrupt Enable)寄存器控制 M-mode 的中断。 MIE_STIE 位控制 S-mode 时钟中断是否能被触发。
w_mie(r_mie() | MIE_STIE);
TIP
- 启用
sstc扩展,允许 S-mode 直接写stimecmp来触发时钟中断。 这可以避免每次设置时钟都需要陷入到 M-mode,提高了效率。
w_menvcfg(r_menvcfg() | (1L << 63));
TIP
- 允许 S-mode 访问
time寄存器。mcounteren控制 S-mode 对性能计数器的访问权限。
w_mcounteren(r_mcounteren() | 2);
TIP
- 设置第一次时钟中断的触发时间。
stimecmp是 S-mode 的时钟比较器。当time寄存器的值 大于等于stimecmp的值时,就会触发一个时钟中断。 这里将当前时间加上一个固定的间隔,作为下一次中断的时间点。
w_stimecmp(r_time() + 1000000);
}