0%

Xv6实验二学习笔记

当RISC-V计算机上电时,它会初始化自己并运行一个存储在只读内存中的引导加载程序。引导加载程序将xv6内核加载到内存中。然后,在机器模式下,中央处理器从_entry (kernel/entry.S:6)开始运行xv6。

_entry.S 入口

简述:完成多核cpu栈的分配并跳转到kernel/start.c执行start函数

entry.S
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    # qemu -kernel loads the kernel at 0x80000000
# and causes each CPU to jump there.
# kernel.ld causes the following code to
# be placed at 0x80000000.
.section .text
_entry:
# set up a stack for C.
# stack0 is declared in start.c,
# with a 4096-byte stack per CPU.
# sp = stack0 + (hartid * 4096)
la sp, stack0
li a0, 1024*4
csrr a1, mhartid
addi a1, a1, 1
mul a0, a0, a1
add sp, sp, a0
# jump to start() in start.c
call start
spin:
j spin

qemu -kernel 将内核加载到地址0x80000000处,并让每个CPU跳转到这里来执行。
kernel.ld 会将上述代码加载到0x800000000处,以供CPU跳转执行。(是否可以认为这里是入口)
我们将一行行解释这里的指令

la sp, stack0,stack0被定义在start.c中:char stack0[4096 * NCPU]la可以理解成load address指令,这里即是将stack0的地址加载到sp寄存器上(问题1:sp寄存器一般不是指向栈顶吗,这里的栈顶怎么成了stack0[0]了)。
li a0, 1024*4li可以理解成load imm(立即数)指令,即是将10244加载到a0寄存器,(说实话我没看到li la指令的区别,实际上是有的,还有比较大的区别,我们不细究,知道怎么使用就好,一个是地址,一个是立即数*)。
csrr a1, mhartidcsrr我们要详细介绍下:有兴趣可以看下这篇文章,下面我们摘取介绍下CSR

  • CSR 是支撑 RISC-V 特权指令集的一个重要概念。CSR 的全称为 控制与状态寄存器(control and status registers)。

  • 简单来说,CSR 是 CPU 中的一系列特殊的寄存器,这些寄存器能够反映和控制 CPU 当前的状态和执行机制。在 RISC-V 特权指令集手册 中定义的一些典型的 CSR 如下:

    • misa,反映 CPU 对于 RISC-V 指令集的支持情况,如 CPU 所支持的最长的位数(32 / 64 / 128)和 CPU 所支持的 RISC-V 扩展。
    • mstatus,包含很多与 CPU 执行机制有关的状态位,如 MIE 是否开启 M-mode 中断等。
    • mhartid,简单理解为当前cpu的id
  • 操作CSR寄存器的有以下几种指令(其中包括伪指令)

    • csrr,读取一个CSR寄存器值到通用寄存器。如:csrr t0, mstatus,读取mstatus的值到t0中。
    • csrw,将通用寄存器的值写入到CSR寄存器。如:csrw mstatus, t0, 将t0写入到mstatus中。
    • csrs,把CSR中指定的bit置1。如:csrsi mstatus, (1 << 2),将mstatus的右起第3位置1。
    • csrc,把CSR中指定的bit置0。
    • csrrw,读取一个CSR的值到通用寄存器,然后把另一个值写入该CSR。如:csrrw t0, mstatus, t0,将mstatus的值与t0的值交换
    • csrrs,读取一个CSR的值到通用寄存器,然后把该CSR中指定的bit置1。
    • csrrc, 读取一个CSR的值到通用寄存器,然后把该CSR中指定的bit置0。

那么对于本行指令,就是将mhartid寄存器的值读取至a1中。
addi a1, a1, 1, a1 = a1 + 1
mul a0, a0, a1a0 = a0 * a1,即 a0 = 1024*4*a1,这里我理解cpuid是从0开始递增的,所以对于cpu0来说,那么a0就等于1024*4
add sp, sp, a0,sp = sp + a0。

经过上述操作每个cpu分配到了一块栈,栈顶为stack0 + mhartid,大小为4096。

分配完栈后,便调用start(kernel/start.c:21)。

kernel/start的start函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void
start()
{
// set M Previous Privilege mode to Supervisor, for mret.
// 将机器模式改为管理模式
unsigned long x = r_mstatus();
x &= ~MSTATUS_MPP_MASK;
x |= MSTATUS_MPP_S;
w_mstatus(x);

// set M Exception Program Counter to main, for mret.
// requires gcc -mcmodel=medany
// 将main地址写入mepc寄存器中(mepc寄存器用于做什么?)
w_mepc((uint64)main);

// disable paging for now.
// 将satp寄存器设置为0,作用是禁用页表映射
w_satp(0);

// delegate all interrupts and exceptions to supervisor mode.
w_medeleg(0xffff);
w_mideleg(0xffff);
w_sie(r_sie() | SIE_SEIE | SIE_STIE | SIE_SSIE);

// ask for clock interrupts.
timerinit();

// keep each CPU's hartid in its tp register, for cpuid().
int id = r_mhartid();
w_tp(id);

// switch to supervisor mode and jump to main().
asm volatile("mret");
}
  1. 将机器模式切换到管理模式
  2. 将main函数入口写入到mepc(M Exception Program Counter)寄存器中,前程序计数器?
  3. w_satp(0); 禁用页表映射
  4. 将中断与异常代理到管理模式下
  5. 开启时钟中断
  6. 记录cpuid到tp寄存器中
  7. 调用mret,返回,弹出了mepc寄存器里main函数地址,开始执行main函数(具体的mret怎么执行还需要看下)。

main

-------- 本文结束 感谢阅读 --------