操作系统构建Part3:dummyxv6的初始化流程

3.1 前言

        第二章详细讨论了RISCV架构的中断机制,本章将回过头来继续探讨第一章讲述的流程。本章的内容比较少,主要是介绍只能在机器模式下进行的初始化流程。

3.2 dummyxv6初始化调用流程

        第一章介绍了kernel/entry.S的运行逻辑,它初始化了将用于运行start函数内部要运行的栈空间,紧接着就跳转到kernel/start.c文件里的start函数中,在完成各种寄存器设置之后,最后跳转到kernel/main.c文件的main函数中,如图1所示。
image图1

3.3 start函数内的初始化

        从图1的内容看,第一章完成了对_entry入口的论述,第二章讲述了硬件层面对操作系统的支持,它是支撑论述第三章的基础。本章主要就是论述start函数内的处理细节,首先看一下start函数的源码:

// kernel/start.c
1  #include "param.h"
2  #include "riscv.h"
3
4  // The stack space for each CPU to run start function in machine mode.
5  __attribute__((aligned(16))) char stack0[4096 * NCPU];
6
7  void timerinit();
8
9  void start() {
10    // Switch to supervisor mode after calling mret.
11    uint64_t mstatus_v = r_mstatus();
12    mstatus_v = ((mstatus_v & (~MSTATUS_MPP_MASK)) | MSTATUS_MPP_S);    
13    w_mstatus(mstatus_v);
14
15    // Jump to main function in supervisor mode after calling mret.
16    w_mepc((uint64_t)main);
17
18    // Disable page table now,
19    w_satp(0);
20
21    // Delegate all interrupts and exceptions to supervisor mode.
22    w_mideleg(0xffff);
23    w_medeleg(0xffff);
24    w_sie(r_sie() | SIE_SEIE | SIE_STIE | SIE_SSIE);
25    
26    // Save the hartid to tp register.
27    uint64_t mhartid = r_mhartid();
28    w_tp(mhartid);
29
30    w_pmpaddr0(0x3fffffffffffffull);
31    w_pmpcfg0(0xf);
32
33    timerinit();
34
35    asm volatile("mret");
36 }
37
38 void timerinit() {
39    // Enable supervisor timer interrupt.
40    w_mie(r_mie() | MIE_STIE);
41
42    // Set STCE bit to 1 to enable the timer in supervisor mode.
43    w_menvcfg(1ul << 63);
44
45    // Set TM bit to 1 to enable the timer in supervisor mode.
46    w_mcounteren(r_mcounteren() | 2);
47
48    // A timer interrupt will be triggered after 1s.
49    w_stimecmp(r_time() + 1000000);
50 }

        现在针对start函数,一行一行进行解析。首先要看的是第10行到第13行,第11行代码读取了CSR寄存器mstatus的值到mstatus_v变量中,然后第12行清除mstatus_v中MPP比特的值,再与MSTATUS_MPP_S进行或运算。最后第13行再把mstatus_v的值写入到mstatus寄存器中。这一步操作主要是将mstatus寄存器的MPP域设置成内核模式,这样在start函数最后调用mret指令时,hart切换到内核模式。具体切换流程可以参考第二章的2.3.3节。
        紧接着执行的是第16行代码,这里将main函数的物理地址写入到了mepc寄存器中,这样在mret指令之后,PC寄存器将更新位mepc所存的地址,然后跳转到这个main函数去执行这里面的逻辑。执行main函数的时候,CPU hart则已经进入到内核模式了,也就是说main函数是在内核模式下执行。
        往下就是执行第19行代码,将satp寄存器置0,这个操作代表目前暂时禁止操作系统的页表功能。这个阶段不使用虚拟内存。
        第21行到第24行的功能是将异常和中断响应委托给内核模式处理,也就是说CPU的hart在内核模式下也能响应异常和中断事件,而不需要先切到机器模式,再从机器模式重定向到内核模式,这样极大提升了异常和中断处理的效率。0xffff的二进制表示为1111 1111 1111 1111,读者可以回顾一下第二章的图19,medeleg寄存器的比特位布局,以及图20,mideleg寄存器的比特位布局,就能直观地看到,具体是哪些异常和中断事件被委托到内核模式。第24行代码,则是通过设置sie寄存器,允许内核态响应软件中断、定时器中断和全局中断,sie寄存器的比特位布局可以查阅第二章的图18。
        第26行到第28行代码的逻辑将执行start函数的CPU hart的id读出,并存入tp寄存器,tp寄存器即是thread pointer寄存器,这么做的目的是不需要进入机器模式,也能获取hart的id,这个功能对后续的操作系统调度管理非常重要。
        第30行和第31行则是设置内核模式下,对大于0小于pmpaddr0的地址段的访问权限。现在来看一下pmpcfg寄存器的布局,RISCV架构中一共有16个pmpcfg寄存器,从pmpcfg0到pmpcfg15,每个pmpcfg寄存器的布局都一样。
image图2
图2中,R代表可读权限,W代表可写权限,X代表可执行权限。A域的值比较有意思,它一共有4个值,如图3所示:
image图3
当A的值为0时,表示pmpaddr寄存器不论存什么值都无效。当第i个pmpcfg寄存器(i大于零小于16时),当第i个pmpcfg寄存器的A域设置为1时,那么pmpaddri-1~pmpaddri区间的地址段受pmpcfgi的配置影响,当第0个pmpcfg寄存器的A域设置为1时,那么0~pmpaddr0这个地址段的内存段受pmpcfg0的配置影响。其他设置读者感兴趣的可以直接查阅官方文档[1]。写入pmpaddr0的地址值为0x3fffffffffffffull,最后三个字符ull表示unsign long long。地址值中,有13个16进制的f值,最高数位是3,一共有13 * 4 + 2 = 54个比特位被设置为1。这是因为在64位的RISCV架构中,pmpaddr寄存器的有效位为第0位到第53位,一共54个比特位。这里的0x3fffffffffffffull,实际上就是地址值的最大值。
        第33行的逻辑,主要是调用了定时器初始化函数–timerinit。具体逻辑在第38行到第50行。xv6操作系统过去的版本是在机器模式响应定时器中断,然后再从机器模式重定向到内核模式。新版本则改成直接在内核模式也可以响应定时器中断,但是它需要用到sstc拓展[2]。dummyxv6也是采用这种方式,第40行将mie寄存器的STIE比特设置为1,允许内核模式能够响应中断,与此同时menvcfg的STCE域也要设置成1,否则往stimecmp寄存器写入内容会有异常。mcounteren的TM域也要设置为1,不然写入stimecmp也会抛出异常。最后朝stimecmp寄存器写入1s后触发定时器中断。
        最后执行第35行,直接执行mret指令,直接跳转到main函数,并且CPU的hart进入内核模式。

3.4 main函数–内核初始化入口

        进入到main函数之后,内核的初始化流程就开始执行了,不过目前还没有给dummyxv6添加任何内核功能,后续会在这里陆续添加。

3.5 结束语

        本章详细讲解了start函数初始化流程,主要是执行机器模式下才能进行的设置操作,包括设置start函数结束后,CPU hart要进入的模式,设置了main函数作为内核初始化的入口,暂时禁止页表功能,将异常和中断全部委托到内核模式,最后还介绍了定时器中断的初始化处理,相信经过这一章,读者对start函数内的初始化处理有了清晰的认识。

3.6 Reference

[1] 《riscv-privileged-v1.10》 3.6.1 Physical Memory Protection CSRs
[2] RISC-V “stimecmp / vstimecmp” Extension