山东大学RISC-V公共开放平台开发记录5

山东大学RISC-V公共开放平台开发记录

4移植Linux操作系统

4.1移植前相应知识

4.1.1特权等级与操作系统的关系

现代操作系统(如Linux、Windows等)为了权限的区分都区分为用户态和内核态。一般来说用户程序都运行在用户态,当它们需要切换到内核态以获得更高权限时,需要向操作系统申请;而操作系统内核和设备驱动程序则默认就运行在内核态。每个用户线程也拥有两个栈,一个是用户态栈,一个是内核态栈,分别当处于用户态和内核态时使用。

操作系统的用户态和内核态对应到处理器的硬件层面上,即为不同的特权等级。在RISC-V中,用户态一般对应User Mode,而内核态一般对应Supervisor Mode。

4.1.2用户态切换到内核态的方式

一般来说用户程序一直运行在用户态中,只有当发生了一下三种事件之一时才会转到内核态中:

\1. 系统调用,即应用程序使用操作系统提供的接口调用内核功能;

\2. 异常,当应用程序运行时出现异常时(比如最常见的缺页异常)也会切换到内核态进行处理;

\3. 外部中断,最常见的情况为当外设(如磁盘、网络适配器)完成用户请求时会向处理器发出中断,此时操作系统会暂停当前的程序运行从而转移到内核态处理这些事件。

4.1.3RISC-V特权指令与CSR

在RISC-V ISA Specification中的用户指令中,这些用户指令主要是完成数值计算、内存存取和分支跳转等“具体任务”。与此同时,在RISC-V Specification中的第二部分,即Previleged Architectures中,还规定了一些特权指令,这些指令有的在用户指令中也出现过(比如ECALL和EBREAK),这些指令在不同特权等级下执行会有不同的效果;另一些指令仅在指定环境中执行(比如WFI只能在Machine模式下执行)。同时还有一些CSR只能在对应的(或者更高等级的)特权等级中访问。

接下来将简介几个特权指令以及特权CSR。

4.1.4特权指令举例

ECALL指令用于向执行环境发出请求,在不同的特权等级中执行ECALL指令有不同的效果:在User Mode中会引发environment-call-from-U-mode异常,在Supervisor Mode中会引发environment-call-from-S-mode异常, 而在Machine Mode中会引发environment-call-from-M-mode异常。

这条指令在Linux中用于系统调用,如下为arch/riscv/kernel/sbi.c中的部分代码,使用ECALL指令时,将异常类型写在a7寄存器, 参数写在a0-a5寄存器,后面会根据异常类型的不同调用不同的异常处理函数。

struct sbiret sbi_ecall(int ext, int fid, unsigned long arg0,
                                                         unsigned long arg1, unsigned long arg2,
                                                         unsigned long arg3, unsigned long arg4,
                                                         unsigned long arg5){
                   struct sbiret ret;
 
                   register uintptr_t a0 asm ("a0") = (uintptr_t)(arg0);
                   register uintptr_t a1 asm ("a1") = (uintptr_t)(arg1);
                   register uintptr_t a2 asm ("a2") = (uintptr_t)(arg2);
                   register uintptr_t a3 asm ("a3") = (uintptr_t)(arg3);
                   register uintptr_t a4 asm ("a4") = (uintptr_t)(arg4);
                   register uintptr_t a5 asm ("a5") = (uintptr_t)(arg5);
                   register uintptr_t a6 asm ("a6") = (uintptr_t)(fid);
                   register uintptr_t a7 asm ("a7") = (uintptr_t)(ext);
                   asm volatile ("ecall"
                                            : "+r" (a0), "+r" (a1)
                                            : "r" (a2), "r" (a3), "r" (a4), "r" (a5), "r" (a6), "r" (a7)
                                            : "memory");
                   ret.error = a0;
                   ret.value = a1;
 
                   return ret;}

比如实现一个putchar函数用于打印一个字符到系统控制台上,就如下通过ECALL来实现

void sbi_console_putchar(int ch){
                   sbi_ecall(SBI_EXT_0_1_CONSOLE_PUTCHAR, 0, ch, 0, 0, 0, 0, 0);}

EBREAK指令与调试模式有关,通过该指令将执行流返回到调试器中,执行该指令会引发breakpoint exception。

WFI指令全称Wait For Interrupt,它会将该Hart陷入等待状态,直到有中断触发为止。在下文介绍系统启动过程时,会用到该指令陷入自旋,等待fesvr将程序代码准备好并触发中断开始执行。

4.1.5特权CSR举例

上文中的mstatus就是特权CSR中非常重要的一个,接下来再举例几个典型的特权CSR。

mtvec与(Machine模式的)trap处理函数向量有关。它的底两位为MODE,其余位为字对其的Base地址。

当Mode为0时为直接模式,所有的Trap均将PC指向Base地址;当Mode为1时为向量模式,发生Trap时PC设置为Base+4*cause。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9BPayFZl-1654330970635)(file:///C:/Users/Jeremiah/AppData/Local/Temp/msohtmlclip1/01/clip_image002.jpg)]

mtvec的字段

与此类似的还有stvec,指的是Supervisor模式下的Trap处理向量。

上文的cause需要我们的mcause寄存器出场解释。当Trap发生时,mcause中会写入Trap原因的编号。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NDqqffs1-1654330970636)(file:///C:/Users/Jeremiah/AppData/Local/Temp/msohtmlclip1/01/clip_image004.jpg)]mcause的字段

mcause的最高位为Interrupt,当该Trap是由中断触发时,Interrupt置为1。

当然还有对应的scauseCSR,代表着Supervisor模式下的Trap原因。

4.2在RISC-V上启动Linux内核

作为一个“成熟”(主要指生态建设方面)指令集的标志是在Linux内核代码仓库的arch目录中拥有一席之地,riscv-linux(即Linux内核的RISC-V移植)到目前位置已经合并到Linux代码仓库中。

接下来介绍在RISC-V平台上启动Linux内核的过程。

4.2.1Linux内核如何感知与操作硬件

由于RISC-V的硬件与之前的其它指令集的硬件均不同,最关键的问题就是如何让Linux内核感知硬件并能够操控具体的硬件?有两个方法可以满足这个需求:

通过设备树的方式。这里简要介绍一下设备树,它是一种描述操作系统使用的硬件的一种格式,包括内存地址映射,CPU核的配置以及外设等。设备树文件目前写在Linux内核代码的arch/riscv/boot/dts(以RISC-V为例)中,包括一个.dts文件和多个.dtsi文件,它们经过编译形成设备树。.dtsi文件大概是SoC的一些公共特性(比如各种型号的CPU核,包括他们的指令集、Cache大小等以及中断控制器、PWM等外设),而.dts文件则包括的是具体某个板级系统的组成(即将.dtsi中定义的各项模块组合起来)。

通过SBI(Supervisor Binary Interface)的方式。RISC-V的SBI是具体平台的固件与正在运行的操作系统之间的接口,用于Machine模式与Supervisor模式进行交互(读者应该比较熟悉ABI,即Application Binary Interface,它规定了数据类型的大小、布局、调用约定与系统调用、目标文件的二进制格式等。满足一个完整ABI的应用程序应该无需重新编译即可在其它支持此ABI的操作系统上运行,可以类比的是,满足一个完整SBI的操作系统应该可以无需修改直接运行在任何支持该SBI的硬件平台上),操作系统可以通过SBI进行硬件方面的调用。 RISC-V的SBI文档在riscv-sbi-doc中,目前还在持续更新。其中大概包括了操作系统对于定时器、中断、缓存相关fence指令的执行、CPU核心状态的控制等接口。其参考实现为opensbi,开发人员可以参考其实现方式根据需求实现自己版本的SBI。

4.2.2内核启动之前

本节介绍基于RISC-V的SoC中从上电到Linux内核启动之前发生的事情。

以Chipyard SoC为例,Chipyard中包含了一个名为BootROM的部分,它存储了系统上电后首先执行的指令以及设备树的信息。BootROM的汇编代码编写在bootrom.S中。 阅读该代码后可以发现,BootROM所做的事情就是启动一个hart(硬件线程),休眠其它hart(即使其自旋),在启动的hart中设置中断开关,并陷入wfi,以等待程序被加载好后触发的中断。

如果是裸机程序,那么这里由fesvr将待执行的程序加载到内存后,fesvr会触发中断,Chipyard就会开始执行用户程序。

如果想启动Linux内核,那么这时就需要第二级的BootLoader,目前使用最广泛的是Berkeley实现的BBL(Berkeley Boot Loader),在内核启动之前,BBL会做如下事项:

选择一个hart作为主线程,其它hart均休眠,等待Linux内核准备好后再唤醒他们

把BootROM中的设备树信息经过过滤(省去一些和Linux无关的内容,比如平台相关的功耗控制设备等)后传递给Linux内核

唤醒其它线程,设置它们的PMP(允许Supervisor模式访问所有内存区域),trap处理向量并进入Supervisor模式

执行mret指令进入Supervisor Mode

开始执行Linux内核

4.2.3内核刚开始启动

内核刚开始启动时,要求系统做好如下准备: 1. a0寄存器存储hartID,即CSR mhartid中存储的值,这个hartID将被映射到Linux的CPU ID 2. a1存储的是指向设备树的地址 3. 内存地址是直接映射的,没有开启分页 4. 内核的ELF执行映像已经加载完成

然后在start_kernel(即内核入口函数)被调用之前,完成下面的过程: 1. 利用PAGE_OFFSET对整个内存地址进行划分(划分为内核空间和用户空间) 2. 启用分页 3. 设立C运行时环境,建立栈和全局指针 4. 设置处理启动早期异常的trap向量 5. 调用start_kernel,启动内核

4.2.4setup_arch

进入start_kernel以后,处理器运行的代码就从“汇编”变成了“C”,但这并不意味着从此以后全是指令集无关的部分了。 在start_nernel中,还有一个名为setup_arch的函数这个大块头,它是和指令集密切相关的。

在RISC-V的系统中,setup_arch进行了如下初始化工作: 1. 如果SBI实现了console驱动,则启动EARLY_PRINTKconsole。在RISC-V中一般都会启动EARLY_PRINTK这样一来用户在启动早期就能看到日志输出 2. 处理内核命令行参数中的体系结构相关的部分,在RISC-V中通常只有内存大小相关参数 3. 解析设备树中的内存,释放出来给内核使用 4. 初始化内存管理子系统,包括初始化Zero Page和不同的内存区(包括Zone_DMA, Zone_Normal和Zone_Highmem)。RISC-V这里只支持了Zone_Normal,比较简单 5. 唤醒其它hart 6. 从设备树中读取处理器的ISA,并写入ELF的hwcap字段中,以告知应用程序它们正在运行在怎样的处理器上。

4.2.5其他启动方式

和SiFive使用BBL不同,平头哥使用U-boot作为启动程序,但只是和上文内容略有区别,平头哥的处理器系统启动Linux内核的过程如下图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g51lSb5L-1654330970637)(file:///C:/Users/Jeremiah/AppData/Local/Temp/msohtmlclip1/01/clip_image006.gif)]

和上文Chipyard中的BootROM类似,平头哥也在SoC中嵌入了ROM,用于存储启动代码。 在上电时,ROM中的程序从外部介质(eMMC或Flash)将U-boot SPL读取到片上SRAM中运行,U-boot SPL全称为(U-boot Second Program Loader),它运行在系统启动的第一阶段,用于初始化DDR以及各种外设。

之后U-boot可以从网络或者Flash中加载Linux内核映像到DDR中,并开始启动内核。

到此为止,Linux内核启动的过程中与RISC-V体系结构相关的部分就介绍完毕。

4.3RISC-V Linux 内核使用的虚拟内存布局。

RISC-V S-mode 虚拟地址翻译分几种方式Sv32, Sv39 和Sv48。

其中Sv39 和 Sv48 为RV64 设计。为了更直观的理解地址翻译过程,我们给出 SV39 地址转换的全过程图示

C-V体系结构相关的部分就介绍完毕。

4.3RISC-V Linux 内核使用的虚拟内存布局。

RISC-V S-mode 虚拟地址翻译分几种方式Sv32, Sv39 和Sv48。

其中Sv39 和 Sv48 为RV64 设计。为了更直观的理解地址翻译过程,我们给出 SV39 地址转换的全过程图示

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值