本系列是博主参考B站课程学习开发一个RISC-V的操作系统的学习笔记,计划从RISC-V的底层汇编指令学起,结合C语言,在Ubuntu 20.04上开发一个简易的操作系统。一个目的是通过实践操作学习和了解什么是操作系统,第二个目的是为之后学习RISC-V的集成电路设计打下一定基础。本系列持续不定期更新,分享出来和大家一同交流进步。
博主是微电子科学与工程专业的学生,对软件和操作系统难免有理解不到位的地方。如有谬误敬请不吝告知,不胜感激。
参考课程及文章:
【Bilibili】[完结] 循序渐进,学习开发一个RISC-V上的操作系统 - 汪辰 - 2021春
想要在计算机上运行程序,我们首先要将编辑好的程序进行编译和链接,将其存储在硬盘(Disk)中,通过硬盘的控制器(Disc Controller)将编译后的机器指令a.out
文件存储到内存中,CPU通过IO桥不断依次进行取指(Fetch)、译码(Decode)、执行(Excute),此时我们才说计算机正在运行我们的程序。取指是将机器指令取到对应的寄存器中。晶振是外部的程序推动者,不断驱动处理器进行上述过程。
假设现有一个只能执行加法操作的8位计算机,我们通过对加法过程进行分析(如上图所示),发现执行加法操作只需要从内存中取数据(LOAD)、执行加法操作(ADD)、存储数据到内存(STORE) 三步操作。我们对不同的操作进行编码,设计不同的指令编码格式。假设采用冯诺依曼架构,即操作指令和数据都存储在同一块内存中,那么计算机执行该程序前内存的存储内容如下图所示(内存中黄色的是指令,绿色的是数据):
如上图所示,操作系统是介于底层硬件和应用程序之间的结构,它通过两个接口分别和上下两层相连。操作系统和应用程序之间的接口称为系统调用(System Call),操作系统和底层硬件之间的接口称为指令集架构(ISA)。操作系统主要的作用主要可以归结为以下两点:
ISA(Instruction Set Architecture),指令集架构,是底层硬件电路面向上层软件程序提供的一层接口规范。ISA不单单指一种汇编语言,它定义的内容比汇编语言更加广泛。ISA定义了:
ISA为上层软件提供一层抽象,制定规则和约束,让编程者不用操心具体的电路结构。IBM 360 是第一个将 ISA 与硬件实现分离的计算机。
CISC复杂指令集(Complex Instruction Set Computing),针对特定的功能实现特定的指令,导致指令数目比较多,但生成的程序长度相对较短。一般而言,使用复杂指令集开发的CPU指令的种类很多,对于每一种操作都有一种特定的指令。就像在中国古代,文字写在竹简或布匹。为了表达更多的信息,考虑到成本问题,一个文字表达的含义被极大地丰富起来。典型的复杂指令集有x86等。
RISC精简指令集(Reduced Instruction Set Computing)只定义常用指令,对复杂的功能采用常用指令组合实现,这导致指令数目比较精简,但生成的程序长度相对较长。精简指令集就像现代的白话文,当人们不再考虑写字带来的成本时,表达相同的意思,人们就不再“惜字如金”,用简单、易读、易理解的方式来表达信息。典型的简单指令集有SPARC、Power、ARM、MIPS、RISC-V等。
现如今,RISC 和 CISC 也逐渐有相互融合的趋势。Intel最早采用复杂指令集,现如今也在逐渐向精简指令集靠近。
ISA (处理器)的宽度指的是 CPU 中通用寄存器的宽度(二进制的位数),实际上就是CPU处理器的字长,这决定了寻址范围的大小、以及数据运算的能力。
注意:ISA 的宽度和指令编码长度无关。
例子:RV32IMA,表示该32位处理器使用RISC-V的I、M、A指令模块,RV64GC同理。
对于ISA而言,其有两种发展方式,分别是增量化和模块化。
增量化 ISA: 计算机体系结构的传统方法,同一个体系架构下的新一代处理器不仅实现了新的 ISA 扩展,还必须实现过去的所有扩展,目的是为了保持向后的二进制兼容性。典型的,以 80x86 为代表。
模块化 ISA: 由 1 个基本整数指令集 + 多个可选的扩展指令集组成。基础指令集是固定的,永远不会改变。扩展指令集类似一种插件的思想。
RISC-V的ISA中的基本整数指令集(Integer)是唯一强制要求实现的基础指令集,其他指令集都是可选的扩展模块。其中嵌入式指令集(Embedded)是基本整数指令集的子集,在一些小型的嵌入式场景中适用。高字长的基本整数指令集向下兼容,如下表所示:
扩展模块指令集:
RISC-V 的非特权规范(Unprivileged Specification)定义了 32 个通用寄存器以及一个 PC(程序计数)寄存器。这种结构对 RV32I/RV64I/RV128I 都是相同的。如果实现支持 F/D 扩展则需要额外支持 32个 浮点(Float Point)寄存器。RV32E 将 32 个通用寄存器缩减为 16 个。
寄存器的宽度由 ISA 指定,RV32 的寄存器宽度为 32 位,RV64 的寄存器宽度为 64 位,依次类推。
每个寄存器具体编程时有特定的用途以及各自的别名。由 RISC-V Application Binary Interface (ABI) 定义。
HART = Hardware Thread。早期的CPU中,一般只有一个CU(Control Unit),所以在任何时刻,只有一条硬件流被执行。如今Intel的CPU中一般有多个核,一个核可以有两个硬件流,这就容易引起线程混淆的问题。通过HART可以很好地区分两个不同的指令线程,可以将一个HART想象成一个独立的CPU。在规范手册中HART的概念频频出现,而处理器CPU的概念却不怎么出现。下面的话摘自RISC-V的官方手册:
从软件编程的角度来说,一个HART就是一个可以在执行环境中自主地,独立地,不受干扰地取指令和执行指令的资源。
为了实现分级保护的功能,RISC-V 的 Privileged Specification 定义了三个特权级别(privilege level),在CPU内部要求有一个类似多级开关的结构,对应三种特权级别。如下表所示:
Machine级别是最高的级别,所有的实现都需要支持。RISC-V的CPU上电之后自动运行在Machine(机械)态,此时CPU中操作的地址是真实的物理地址。通过特定的操作进入Supervisor(管理者)态之后,此时访问的地址就是虚拟地址,对真实的物理地址起到了保护的作用。此外,RISC-V还提供了可选的Debug级别:
CPU中有控制和状态寄存器(CSR,Control and Status Register),CPU在不同的工作模式下使用不同的独立的CSR,用于控制和获取相应Level下处理器的工作状态。这些寄存器在不同的工作模式下是不能互相访问的。通过这种设计,才能真正实现分级保护的目的。
高级别的特权级别下可以访问低级别的 CSR,譬如 Machine Level 下可以访问Supervisor/User Level 的 CSR,以此类推,但反之不可以。
RISC-V 定义了专门用于操作 CSR 的指令(“Zicsr”扩展)。
RISC-V 定义了特定的指令可以用于在不同特权级别之间进行切换(ECALL/EBREAK)
物理内存保护(Physical Memory Protection,PMP),它是一种较为低级的内存管理和保护方式,在一定程度上可以保护内存。
虚拟内存(Virtual Memory),程序访问的地址不再是实际的物理地址,而是虚拟地址。通过特定的硬件外设MMU(Memory Management Uint,内存管理单元)可以将虚拟地址映射到物理地址上。
异常(Exception):在当前RISC-V HART中与指令相关的运行时发生的一种异常情况,例如除零异常。发生异常后执行异常处理程序(由程序员自行编写),执行过后回到发生异常的地方再次执行。再次执行相当于程序对异常指令的又一次尝试。
中断(Interrupt):可能导致RISC-V HART经历意想不到的控制转移的外部异步事件,发生中断后,CPU会先执行完当前的指令,之后再执行中断服务程序,返回时执行发生中断的指令的下一条指令继续执行。
原创笔记,码字不易,欢迎点赞,收藏~ 如有谬误敬请在评论区不吝告知,感激不尽!博主将持续更新有关嵌入式开发、机器学习方面的学习笔记。