From f0cae190cf6a991a43a3b720bd886438f687af17 Mon Sep 17 00:00:00 2001 From: Zhiyuan Shao Date: Sat, 15 May 2021 17:33:20 +0800 Subject: [PATCH] add TOC and inter-page links --- README.md => README.zh-CN.md | 9 +++- chapter1.md | 92 +++++++++++++++++++++++++++--------- preliminary.md | 8 ++-- 3 files changed, 81 insertions(+), 28 deletions(-) rename README.md => README.zh-CN.md (50%) diff --git a/README.md b/README.zh-CN.md similarity index 50% rename from README.md rename to README.zh-CN.md index 2d689b3..b76f5fa 100644 --- a/README.md +++ b/README.zh-CN.md @@ -4,7 +4,14 @@ [前言](preliminary.md) -[第一章. RISC-V体系结构](chapter1.md) +[第一章. RISC-V体系结构](chapter1.md) +- [1.1 RISC-V发展历史](chapter1.md#history) +- [1.2 RISC-V汇编语言](chapter1.md#assembly) +- [1.3 机器的特权状态](chapter1.md#machinestates) +- [1.4 中断和中断处理](chapter1.md#traps) +- [1.5 页式虚存管理](chapter1.md#paging) +- [1.6 什么是代理内核](chapter1.md#proxykernel) +- [1.7 相关工具软件](chapter1.md#toolsoftware) [第二章. (实验1)非法指令的截获](chapter2.md) diff --git a/chapter1.md b/chapter1.md index 2e40215..9daf805 100644 --- a/chapter1.md +++ b/chapter1.md @@ -1,7 +1,17 @@ ## 第一章.RISC-V体系结构 -本章的1.1节将简单介绍RISC-V的诞生和发展历史,其后将介绍和讨论RISC-V体系结构的各个方面,如1.2节介绍汇编语言、1.3节讨论机器状态、1.4节分析中断和中断处理机制、1.5节讨论分页机制。最后,在1.6节介绍PKE实验涉及的工具软件,以及它们的使用方法。值得注意的是,RISC-V的体系结构本身就是一个庞大的知识体系,实际上是很难用一章的内容完全讲清楚的(我们鼓励对该体系结构感兴趣的读者延申阅读参考文献[开源指令集的指南]以及[RISC-V instruction set manual])。然而,不懂体系结构又无法理解操作系统和开展PKE的实验,所以本章将重点阐述和讨论与操作系统设计有关的知识内容,力求精简和突出重点。 - +### 目录 +- [1.1 RISC-V发展历史](#history) +- [1.2 RISC-V汇编语言](#assembly) +- [1.3 机器的特权状态](#machinestates) +- [1.4 中断和中断处理](#traps) +- [1.5 页式虚存管理](#paging) +- [1.6 什么是代理内核](#proxykernel) +- [1.7 相关工具软件](#toolsoftware) + +本章的1.1节将简单介绍RISC-V的诞生和发展历史,其后将介绍和讨论RISC-V体系结构的各个方面,如1.2节介绍汇编语言、1.3节讨论机器状态、1.4节分析中断和中断处理机制、1.5节讨论分页机制。最后,在1.6节介绍PKE实验涉及的工具软件,以及它们的使用方法。值得注意的是,RISC-V的体系结构本身就是一个庞大的知识体系,实际上是很难用一章的内容完全讲清楚的(我们鼓励对该体系结构感兴趣的读者延申阅读参考文献[开源指令集的指南](#references)以及[RISC-V instruction set manual](#references))。然而,不懂体系结构又无法理解操作系统和开展PKE的实验,所以本章将重点阐述和讨论与操作系统设计有关的知识内容,力求精简和突出重点。 + + ### 1.1 RISC-V发展历史 RISC-V(读做“risk-five”)是一种典型的精简(Reduced Instruction Set Computer,简写为RISC)指令集(Instruction Set Architecture,简写为ISA),它是加利福尼亚大学伯克利分校的David Patterson教授与Krste Asanovic教授研究团队于2010年提出的一个开放指令集架构。名称中的“V”指的是该ISA的第五个版本,有时“V”也被解读为向量(Vector)扩展。与之前的诸多商用指令集(如x86、ARM,以及MIPS等)不同的是,RISC-V是一个开放指令集,它的提出受到了全球工业界和学术界的广泛关注。在我国,RISC-V的发展、研究和应用同样得到了广泛关注,在网信办、工信部、中科院等多个国家部委支持和指导下,中国开放指令生态(RISC-V)联盟于2018年11月8日浙江乌镇举行的第五届互联网大会上正式宣布成立。 @@ -14,7 +24,7 @@ RISC-V(读做“risk-five”)是一种典型的精简(Reduced Instruction ● **场景丰富**。RISC-V指令集的设计,综合考虑了多种应用场景(涵盖了从嵌入式低功耗应用到高性能服务器场景),为各种场景考虑并设计了多个不同但指令上接近的分支。在这一点上,RISC-V跟ARM类似,针对不同应用场景,RISC-V也都有对应版本。 -● **开源参考**。自RISC-V提出以来,工业界和学术界都积极开展了对其的应用和研究,出现了大量的开源实现(参见[RISC-V开源项目列表])。这些开源实现,为具体应用领域的开发者提供了大量可借鉴的参考,同时也极大地降低了CPU的设计门槛。可以预见的是,随着大量基于该指令集的芯片开源项目的诞生,RISC-V的发展将像开源软件生态中的Linux那样,成为计算机芯片与系统领域创新的基石。 +● **开源参考**。自RISC-V提出以来,工业界和学术界都积极开展了对其的应用和研究,出现了大量的开源实现(参见[RISC-V开源项目列表](#refenences))。这些开源实现,为具体应用领域的开发者提供了大量可借鉴的参考,同时也极大地降低了CPU的设计门槛。可以预见的是,随着大量基于该指令集的芯片开源项目的诞生,RISC-V的发展将像开源软件生态中的Linux那样,成为计算机芯片与系统领域创新的基石。 在本书所设计的实验中我们主要考虑通用计算场景,所以选择RV64G作为我们目标计算机的指令集。其中,RV64G中的“RV”代表RISC-V;“64”代表所支持的指令是64位的(实际上,交叉编译器在生成代码时可能会采用32位指令减小生成的目标代码长度),地址长度和寄存器长度都为64位;而“G”代表通用(general)计算平台。实际上,“G”等效于“IMAFD”,其中“I”代表整数(Integer)计算指令、整数load、整数store以及控制流(如分支跳转)指令,这些指令在任何RISC-V的实现中都是必须的;“M”代表乘法(Multiply),即平台支持乘法和除法运算;“A”代表原子(Atomic)扩展,支持对寄存器进行的原子读、修改和原子写操作,这些操作在多核设计中非常有用;“F”代表单精度浮点(Float)运算支持,“D”代表双精度浮点(Double)运算支持。 @@ -22,9 +32,10 @@ RISC-V(读做“risk-five”)是一种典型的精简(Reduced Instruction 考虑到今天64位电脑(x86_64)已广泛普及到桌面、服务器,我们通常接触到的操作系统(如Ubuntu、Windows10等)都是内置的64位编译器,编译出来的二进制代码(特别是我们将要用到的RISC-V模拟器)也都默认是64位,我们将PKE实验的目标平台类型设定为桌面电脑(采用通用版本RV64G)。 + ### 1.2 RISC-V汇编语言 -关于RISC-V汇编语言,较好的中文参考资料有《RISC-V手册——一本开源指令集的指南》(参见[开源指令集的指南]),里面完整列举和解释了RISC-V的所有汇编指令,感兴趣的读者可以通过阅读该书了解RISC-V汇编。在这里,我们不打算详细讨论RISC-V的汇编知识,只重点讨论RISC-V汇编与8086汇编的不同,以及PKE的代码涉及到的一些汇编语法和指令。 +关于RISC-V汇编语言,较好的中文参考资料有《RISC-V手册——一本开源指令集的指南》(参见[开源指令集的指南](#refenences)),里面完整列举和解释了RISC-V的所有汇编指令,感兴趣的读者可以通过阅读该书了解RISC-V汇编。在这里,我们不打算详细讨论RISC-V的汇编知识,只重点讨论RISC-V汇编与8086汇编的不同,以及PKE的代码涉及到的一些汇编语法和指令。 **1.2.1 寄存器** @@ -73,7 +84,7 @@ RISC-V汇编指令的格式与8086汇编是截然不同的。以最基础的加 其中,rd代表目标寄存器(register destination),rs1和rs2分别代表源寄存器1(register source)和源寄存器2,immediate代表立即数。前一条指令的执行效果是将两个源寄存器(rs1和rs2)中所存储的值进行求和,并将结果写到rd寄存器;后一条指令的执行,将源寄存器1中的值与立即数相加,结果写到rd寄存器(忽略算术溢出)。需要注意的是,由于我们采用了64位指令集,意味着以上指令中所有的操作数(无论是rd还是rs中的值,以及immediate)都是64位的。如果immediate不足64位,则对其进行符号位(最高位)扩展到64位。如果要操作更小一点的数,则需要用到addw,它最后的w符号表示操作数为一个字(32位)。 -以上我们只是以add汇编指令作为例子,举例说明了RISC-V汇编语言。读者会发现跟8086汇编语言相比,RISC-V汇编语言在语法上有着根本的区别,源和目的寄存器的位置是不一样的。由于本书是操作系统课程的实验指导,无法完整地列举和解释所有RISC-V汇编指令,我们强烈建议读者继续阅读参考文献中的[开源指令集的指南],进一步了解RISC-V汇编语言。 +以上我们只是以add汇编指令作为例子,举例说明了RISC-V汇编语言。读者会发现跟8086汇编语言相比,RISC-V汇编语言在语法上有着根本的区别,源和目的寄存器的位置是不一样的。由于本书是操作系统课程的实验指导,无法完整地列举和解释所有RISC-V汇编指令,我们强烈建议读者继续阅读参考文献中的[开源指令集的指南](#refenences),进一步了解RISC-V汇编语言。 **1.2.3访存和寻址模式** @@ -293,6 +304,7 @@ clobbered_regs(破坏描述部分) 我们看到,不同的函数具有的栈帧(stack frame)的大小实际上是很不一样的,一般来说,简单的函数(如main和bar)的栈帧比较小都只有16个字节。这是因为对于函数来说,栈的作用在于保存进入时的现场、函数中被使用的寄存器,以及函数的局部变量。对于简单的函数而言,其使用的寄存器较少,同时也没啥局部变量,这些因素就导致了较小的栈帧。另外,这个函数调用过程中比较有趣的是ra和fp两个寄存器。ra寄存器指向函数的返回地址,一般会在函数入口处入栈。但是对于bar函数来说,因为它是叶子函数(leaf function)处于函数调用的最后一级,所以就无需将ra寄存器入栈。fp寄存器则指向上一级函数的栈帧,函数在入口处都需要将它存入栈中,因为只有这样函数才能够找到来时的路,在返回时做到有路可走。 + ### 1.3 机器的特权状态 采用RV64G指令集的处理器,定义了三种特权模式:运行在最高特权级的机器模式(machine mode),运行在次高特权级的监管者模式(supervisor mode),以及最低特权级的用户模式(user mode)。机器模式是启动后,计算机所处的模式,在该模式下能够运行所有特权指令,访问所有内存空间。监管者模式所处的特权级略低于机器模式,但仍能够运行部分特权指令,以及访问机器模式所规定的所有内存空间。用户模式的特权级最低,无法执行改变机器状态的特权指令,和访问超过应用程序范围的内存空间。为后续讨论的方便,我们有时会简称机器模式为M模式,监管者模式为S模式,用户模式为U模式。 @@ -305,7 +317,7 @@ clobbered_regs(破坏描述部分) 通过《操作系统原理》课我们知道,**现代的处理器定义不同特权级的根本原因,是为了对操作系统进行保护**。例如,让操作系统运行在较高的特权模式,而用户代码则运行在较低的特权模式,以防止用户态代码执行恶意的动作破坏操作系统。然而,用户态的代码在它的生命周期里往往会要求做一些“合法”的特权模式行为(例如进行I/O,典型例子如常用的printf函数),这就意味着处理器同时需要支持特权模式的转换(control transfer)。在RISC-V处理器中,实现这种特权模式转换的工具就是中断,当执行在低特权模式的代码被中断时,处理器将进入更高特权模式执行中断处理例程来处理打断(低特权)代码执行的事件。中断处理完成后,处理器将从高特权模式返回低特权模式。这里的中断有时被称为异常或系统调用,我们将在1.4节详细讨论中断的分类、相关术语和处理过程。需要注意的是,RISC-V处理器可以实现跨特权模式的转换,例如从U模式直接进入M模式,或者从M模式返回U模式,这些转换的发生都取决于机器的状态寄存器的设置。 -RISC-V为每一种特权模式定义了一组寄存器,用于控制机器的状态(status)以及实现状态的转换。对于操作系统而言,比较重要的是机器模式和监管模式的一组寄存器(Control and Status Registers,简写为CSRs),下面我们将分别讨论这两种模式下的CSR,以及状态转换的实现。需要说明的是,关于RISC-V机器的特权状态,[RISC-V instruction set manual]是一个比较好的文档,这里我们将重点讨论与操作系统相关的内容。 +RISC-V为每一种特权模式定义了一组寄存器,用于控制机器的状态(status)以及实现状态的转换。对于操作系统而言,比较重要的是机器模式和监管模式的一组寄存器(Control and Status Registers,简写为CSRs),下面我们将分别讨论这两种模式下的CSR,以及状态转换的实现。需要说明的是,关于RISC-V机器的特权状态,[RISC-V instruction set manual](#refenences)是一个比较好的文档,这里我们将重点讨论与操作系统相关的内容。 **1.3.1 机器模式下的CSR** @@ -405,8 +417,8 @@ SUM(permit Supervisor User Memory access)位用于控制S模式下的虚拟 需要注意的是,对于表1.5中的csrc、csrs、csrrs、csrrc以及csrrw指令,都有其立即数版本。例如csrci就是csrc的立即数版本,立即数版本将rs1替换为立即数,并取立即数的低5位参与运算。立即数版本的功能跟非立即数版本一致,但因为我们设计的实验(PKE)较少用到这类指令,所以我们并未把它们列入表1.5中。 - + ### 1.4 中断和中断处理 通过上一节的讨论,我们知道中断是处理器在实现特权级之间 “穿越”,特别是从低特权模式向高特权模式进行控制转移的重要工具和手段。由于历史或翻译的原因,各种文献(特别是原理课的教材)中与中断相关的名词多且繁杂,然而对中断概念的理解又是操作系统中非常重要的一环。我们将在1.4.1节对中断的概念和分类进行讨论,力求厘清与中断相关的各种表述,帮助读者形成对中断的准确理解和表达。在1.4.3节,我们将讨论RISC-V处理器的中断处理,并在1.4.4节介绍RISC-V平台的中断代理机制。 @@ -417,7 +429,7 @@ SUM(permit Supervisor User Memory access)位用于控制S模式下的虚拟 在RISC-V处理器上,中断并不一定意味着特权级的变迁(RISC-V支持平级中断,例如简单嵌入式设备上从M模式到M模式的中断),但是对于传统意义的操作系统而言,由于多个特权级(例如我们考虑的M+S+U模式的RISC-V机器)的存在,中断处理例程往往在比用户模式(也就是被中断的用户程序所运行的特权模式)更高级别的特权模式中(如监管模式或机器模式)执行。对操作系统而言,这样做也可以更好地对操作系统本身,以及计算机的内存资源以及I/O资源进行保护。 -实际系统中导致中断发生的事件往往是比较复杂的,它们的来源、处理时机和返回方式都不尽相同。为了便于读者对中断的理解以及表达的准确性,我们借鉴参考文献[SiFive Interrupt]的中断分类标准,将系统中发生的可能中断当前执行程序的事件分为3类: +实际系统中导致中断发生的事件往往是比较复杂的,它们的来源、处理时机和返回方式都不尽相同。为了便于读者对中断的理解以及表达的准确性,我们借鉴参考文献[SiFive Interrupt](#refenences)的中断分类标准,将系统中发生的可能中断当前执行程序的事件分为3类: ● **Exception**(异常):这类中断是处理器在执行某条指令时,由于条件不满足而产生的。典型的异常有除零错误、缺页、执行当前特权级不支持的指令等。**相对于正在执行的程序而言,exception是同步(synchronous)发生的。exception产生的时机是指令执行的过程中(即处理器流水线的执行阶段),在exception处理完毕后,系统将返回发生exception的那条指令重新执行**。 @@ -441,7 +453,7 @@ SUM(permit Supervisor User Memory access)位用于控制S模式下的虚拟 **1.4.2中断向量表** -系统发生中断(我们用中文的“中断”这个名词来指代广义的中断,并非以上的interrupt)时执行的这段程序,往往被称为**中断例程**(interrupt routine)。因为事件的多样性,系统可能有多个这样的中断例程,通常的做法是把这些例程的入口放在一张表中,而这张表一般称为**中断向量表**(interrupt table)。RV64G处理器在发生中断后,会将发生的中断类型、编号自动记录(硬件完成)到目标模式的CSR中。假设发生中断的目标模式为M模式,则中断的这些信息会记录到mcause寄存器。表1.7列出了mcause的可能取值以及对应的中断信息(为简化讨论,我们只考虑为处理器配置本地中断控制器Core Local Interrupt即CLINT的情况[SiFive Interrupt])。 +系统发生中断(我们用中文的“中断”这个名词来指代广义的中断,并非以上的interrupt)时执行的这段程序,往往被称为**中断例程**(interrupt routine)。因为事件的多样性,系统可能有多个这样的中断例程,通常的做法是把这些例程的入口放在一张表中,而这张表一般称为**中断向量表**(interrupt table)。RV64G处理器在发生中断后,会将发生的中断类型、编号自动记录(硬件完成)到目标模式的CSR中。假设发生中断的目标模式为M模式,则中断的这些信息会记录到mcause寄存器。表1.7列出了mcause的可能取值以及对应的中断信息(为简化讨论,我们只考虑为处理器配置本地中断控制器Core Local Interrupt即CLINT的情况[SiFive Interrupt](#refenences))。 表1.7 mcause寄存器的取值(由Interrupt及Code字段拼接)及值的含义 @@ -564,12 +576,10 @@ csrw medeleg, 1<<0 | 1<< 3 | 1<<8 | 1<<12 | 1<<13 | 1<<15 这段代码的作用是将M模式中interrupt中的1、5和9号,分别对应Supervisor software interrupt,Supervisor timer interrupt和Supervisor external interrupt代理出去,到S模式处理;再将M模式中的exception(或trap)中的0、3、8、12、13和15号,分别对应Instruction address misaligned,调试中断Breakpoint(3号),用户态系统调用Environment call from U-mode(8号),缺页或访存异常(12、13和15号)代理出去,到S模式处理。实际上,将这些重要的中断代理出去后,系统中产生的绝大部分中断事件将都在S模式处理。所以,在其后的PKE实验中,读者主要跟U模式以及S模式的代码打交道,除启动过程和一些简单的设置(如访存、中断代理等),实验也基本不涉及M模式的代码。 -[//]:# (### 1.5 页式虚存管理) -[comment]: <> (### 1.5 页式虚存管理) - -###1.5 页式虚存管理 ### + +### 1.5 页式虚存管理 -
我们知道,程序中的代码对数据进行访问(如使用load和store指令)时,采用的是数据的逻辑地址(即程序地址)。然而,将程序装入内存时,装载器无法保证数据的逻辑地址和物理地址(内存的编址)之间有完全相等的关系。实际上,由于操作系统往往是计算机装入物理内存的第一个程序,如果仔细规划逻辑地址空间,还能勉强建立(操作系统程序内部)逻辑地址到(其所装入的物理内存的)物理地址间的相等关系,但是这一点对于后续装入的应用程序,几乎是无法也不可能保证的。 +我们知道,程序中的代码对数据进行访问(如使用load和store指令)时,采用的是数据的逻辑地址(即程序地址)。然而,将程序装入内存时,装载器无法保证数据的逻辑地址和物理地址(内存的编址)之间有完全相等的关系。实际上,由于操作系统往往是计算机装入物理内存的第一个程序,如果仔细规划逻辑地址空间,还能勉强建立(操作系统程序内部)逻辑地址到(其所装入的物理内存的)物理地址间的相等关系,但是这一点对于后续装入的应用程序,几乎是无法也不可能保证的。 为了实现程序逻辑地址到(其装入物理内存后的)物理地址的转换,保证程序对数据的正确寻址,采用RV64G指令集的RISC-V处理器在监管模式(即S模式,也就是我们的PKE操作系统代码运行的特权模式)提供了三种逻辑地址到物理地址的转换方式:Bare,Sv39和Sv48。由于逻辑地址到物理地址的转换与物理内存的管理有着紧密的关联,所以以上三种方式也被称为虚拟内存管理(Virtual Memory Management,简称VMM)方式。其中Bare模式是最简单的VMM方式,即寻址时不对虚拟地址进行任何转换,所访问的物理内存地址就等于虚拟内存地址,这种方式在操作系统启动和刚进入S模式时很有用,单任务模式(只执行一个应用)下仍然可用,但多任务模式(启动多个进程)下就没法用了;Sv39和Sv48是页式虚拟内存的管理方式,它们分别支持39位和48位的逻辑地址,且将物理内存以页面(page)的粒度进行管理。Sv39和Sv48这两种VMM用得较多的是Sv39,Sv48只是在Sv39上的一个简单扩展,所以在后续讨论中,我们将着重讨论Sv39。 @@ -657,7 +667,7 @@ satp寄存器包含一个44位的PPN,它指向了一个页表的根目录所 | 8 | Sv39 | 采用Sv39的方案对虚地址进行转换 | | 9 | Sv48 | 采用Sv39的方案对虚地址进行转换 | -实际上除了Sv39和Sv48外,系统在未来的扩展中还可能支持更多的扩展,如Sv57以及Sv64(见[RISC-V instruction set manual])。从39、48以及57这几个数字,我们可以发现的规律是,它们之间两两的差是9!结合图1.9中的地址翻译过程,我们就很容易理解什么是Sv48了:它在Sv39的基础上,用上了虚地址中的39~47这9个位(一共用了虚地址中的48个位),作为第四级虚页号VPN[3]。这样做的结果是:在进行地址翻译的时候新增了一级地址翻译。显然,Sv48提供了比Sv39更大的虚拟地址空间(2^48B=256TB>512GB),从而能够支撑更大的应用程序的执行,那么选择Sv48是不是比选择Sv39更具优势呢?答案是:取决于是否真的有必要支撑超过512GB虚地址空间的应用,如果不需要,则Sv39是更合理的选择。从图1.9中的地址翻译过程,我们还可以看到,一次虚地址va到实地址pa的转换涉及到4次(根目录+页目录+页表+pa所在的页)访问内存操作!而相比于处理器内部进行的运算而言,访存显然是更为耗时的操作。可以想象,如果采用Sv48,这个转换所需要的访存操作是(5次)比Sv39更多的。 +实际上除了Sv39和Sv48外,系统在未来的扩展中还可能支持更多的扩展,如Sv57以及Sv64(见[RISC-V instruction set manual](#refenences))。从39、48以及57这几个数字,我们可以发现的规律是,它们之间两两的差是9!结合图1.9中的地址翻译过程,我们就很容易理解什么是Sv48了:它在Sv39的基础上,用上了虚地址中的39~47这9个位(一共用了虚地址中的48个位),作为第四级虚页号VPN[3]。这样做的结果是:在进行地址翻译的时候新增了一级地址翻译。显然,Sv48提供了比Sv39更大的虚拟地址空间(2^48B=256TB>512GB),从而能够支撑更大的应用程序的执行,那么选择Sv48是不是比选择Sv39更具优势呢?答案是:取决于是否真的有必要支撑超过512GB虚地址空间的应用,如果不需要,则Sv39是更合理的选择。从图1.9中的地址翻译过程,我们还可以看到,一次虚地址va到实地址pa的转换涉及到4次(根目录+页目录+页表+pa所在的页)访问内存操作!而相比于处理器内部进行的运算而言,访存显然是更为耗时的操作。可以想象,如果采用Sv48,这个转换所需要的访存操作是(5次)比Sv39更多的。 为了加快虚地址到实地址的转换,今天的处理器(RISC-V处理器也不例外,但取决于具体的设计)一般都内置了TLB(Translation Lookaside Buffer)单元,其速度接近寄存器。在进行地址转换时,将常用的PDE/PTE存储在TLB中以加速虚实地址的转换速度。然而,这又带来一个新的问题,即当发生进程切换时,TLB中很有可能还保留了上一个进程的地址变换所涉及的PDE/PTE!这时,我们就需要借助satp寄存器中的ASID(Application Specific ID)字段来判断是否发生了进程切换,如果发生了,操作系统就需要调用SFENCE.VMA指令来将TLB中的内容刷新。 @@ -665,9 +675,41 @@ satp寄存器包含一个44位的PPN,它指向了一个页表的根目录所 实际上,开启采用兆页或吉页的方法,在RISC-V规范中并未提供,应该是让各个设计RISC-V处理器的单位自行制定。考虑到我们的PKE实验并未涉及这些非基础页,所以在这里我们不打算对这部分内容进一步讨论。 - + +### 1.6 什么是代理内核 + +代理内核是一种轻量级的应用程序执行环境,可以承载静态链接的RISC-V ELF文件,其运行机制如下图所示。 + + fig1_11 + +图1.10 代理内核的概念 + +如图1.10所示,代理内核的工作适配的场景是,既有主机(host)又有目标机(target)的场景。代理内核并非独立存在的系统,它不具有独自的IO实现,而是将IO功能代理到Host主机上。它可以看成是操作系统的一个极小集,为应用提供最基本的操作系统支撑,使得应用可以在只具备核心资源(包括处理器、内存)的裸机上运行。为实现IO操作,代理内核通过HTIF接口(Host Target Interface的缩写)同宿主机器交互,完成大量IO相关的系调用,其工作机制如下图所示。 + + fig1_12 + +图1.11 代理内核与主机内核的交互 + +图1.11中的Frontend Server可理解为在Host主机上运行的一个守护进程,它将二进制文件(如代理操作系统内核Proxy kernel)加载到内存,通过控制/状态寄存器使target机器复位,并启动代理内核。后者和Frontend Server通过共享内存的HTIF接口通讯,实现IO操作以及加载和执行目标应用程序的动作。当目标应用程序执行完毕后,目标机器通过tohost CSR通知主机端的Frontend。 + +代理内核的设计,对于在类似于[Zedboard开发板](#references)这样的环境下开发、验证RISC-V处理器,以及面向所开发RISC-V处理器的应用有着极大的帮助。 Zedboard是一个搭载了ARM处理器核的FPGA系统,其中ARM硬核部分一般被称为PS,而FPGA部分一般被称为PL,PS和PL端共享内存。PS端可以运行一个完整的ARM版Linux系统(如ubuntu),成为图1.10中的Host部分,PL端的逻辑资源则可以用来编程形成一个RISC-V处理器(软核)。为了在RISC-V软核上运行目标应用,就可以采用代理内核的思路(而无需为RISC-V处理器去开发一个完整的操作系统),让在RISC-V上运行的代理内核通过HTIF去“共享”访问PS端所控制的所有外部设备,如终端、存储等。这样,可以极大加速开发-验证的循环,最终形成可靠的基于RISC-V的系统。 -### 1.6 工具软件简介 +例如,我们可以采用伯克利大学的开源项目[fpga-zynq](#references),在Zedboard开发板上测试所开发的RISC-V处理器,fpga-zynq就用到了代理内核[Proxy Kernel](#references)来支撑目标应用。运行结果如图1.12所示: + + fig1_13 + +图1.12 代理内核与主机内核的交互 + +我们的PKE内核实验中,也存在两个并行的Host和Target系统,Host就是我们的开发主机,其上运行了一个类Linux的完整操作系统,而Target就是我们用[Spike](#references)模拟出来的RISC-V机器,目标应用程序将在代理内核中执行。系统运行过程中,代理内核借助Spike所提供的HTIF完成所有IO动作。 + +我们认为,代理内核除了它的实用特性(如以上讨论的对RISC-V的开发和验证)外,具有很好的教学用途。在开发代理内核的过程中,学生只需要将精力放在对计算机的“核心资产”(即处理器和内存)的管理,和通过各种数据结构实现的逻辑到物理的映射上,而无须过多考虑对IO设备控制的细节。这些都有助于降低操作系统开发的复杂度,尽量降低学生开发的难度和复杂度。**同时,代理内核的目标是支撑给定目标应用,无需搞个“大而全”的内核,去运行各种可能的应用**。 + +一个显而易见的事实是:**比起复杂的多进程网络服务器(如[Apache HTTP Server](https://httpd.apache.org/))所需要的操作系统支持而言,简单的Hello world!程序所需要的操作系统支持显然要少得多!**例如,PKE的第一个实验(lab1_1)所就采用了Hello world!程序作为目标应用,而支撑该应用的PKE内核代码只有500多行,这可能是世界上**最小**的“操作系统”了!基于该事实,采用代理内核思想设计操作系统实验的一个更Strong的理由,是我们可以通过对输入的目标应用的迭代(从易到难,从简单到复杂),反推代理操作系统内核本身的“进化”,学生在进化代理内核的过程中完成对操作系统课程知识点的学习。 + +需要说明的是,我们的PKE实验代码即可以满足操作系统课程实验的开发需求,同时,所开发的代理内核可以无缝地在Zedboard开发板上运行。这样PKE实验实际上是同时兼顾了教学用途(采用Spike模拟RISC-V环境),和实际工程开发(在Zedboard开发板的PL上搭载需要验证的RISC-V处理器软核)用途的。这样,就为选择PKE实验的学生打下了一定的RISC-V(软硬协同)开发基础。 + + +### 1.7 相关工具软件 PKE的实验将涉及Linux环境下较多工具软件的使用,以下列出一些我们认为比较重要的工具软件,并简要介绍其使用方法。详细的使用方法,希望读者通过阅读其使用手册进一步掌握。 @@ -691,9 +733,9 @@ git是一个开源的分布式版本控制系统,对于一个本地仓库, 当clone完成,你就会有本地的工作目录,进入该目录,每个文件都有两种状态:tracked or untracked。已跟踪的文件是指那些被纳入了版本控制的文件,在上一次快照中有它们的记录,在工作一段时间后,它们的状态可能是未修改,已修改或已放入暂存区。简而言之,已跟踪的文件就是 git 已经知道的文件。其状态转换如图: - fig1_10 + fig1_10 -图1.10 git仓库中文件的状态转换 +图1.13 git仓库中文件的状态转换 使用git status可以查看目录下的文件状态 @@ -915,11 +957,11 @@ RISC-V是一个可扩展指令集,spike默认支持的ISA为RV64IMAFDC,我 我们将目标机器的ISA指定为RV64GCV。 - + 参考文献: -[RISC-V开源项目列表] RISC-V开源项目列表. https://riscv.org/risc-v-cores/ +[RISC-V开源项目列表] RISC-V开源项目列表. https://riscv.org/risc-v-cores/ [开源指令集的指南] 勾凌睿、黄成、刘志刚,《RISC-V手册——一本开源指令集的指南》. Avail at: http://crva.ict.ac.cn/documents/RISC-V-Reader-Chinese-v2p1.pdf @@ -929,6 +971,10 @@ RISC-V是一个可扩展指令集,spike默认支持的ISA为RV64IMAFDC,我 [SiFive Interrupt] SiFive, SiFive Interrupt Cookbook (version 1.0). Avail. at: https://sifive.cdn.prismic.io/sifive/0d163928-2128-42be-a75a-464df65e04e0_sifive-interrupt-cookbook.pdf - +[Zedboard] Digilent Zedboard. https://reference.digilentinc.com/programmable-logic/zedboard/start + +[Proxy Kernel] RISC-V Proxy Kernel and Boot Loader. https://github.com/riscv/riscv-pk + +[fpga-zynq] Rocket Chip on Zynq FPGAs. https://github.com/ucb-bar/fpga-zynq - \ No newline at end of file +[Spike] Spike RISC-V ISA Simulator. https://github.com/riscv/riscv-isa-sim \ No newline at end of file diff --git a/preliminary.md b/preliminary.md index 9be2e13..016ee41 100644 --- a/preliminary.md +++ b/preliminary.md @@ -2,7 +2,7 @@ 本书的写作目标是围绕基于RISC-V的代理内核(Proxy Kernel),设计和指导读者完成一组实验作为《操作系统原理》课程的课程实验以及课程设计内容。课程实验的内容需要学习《操作系统原理》课程的读者独立完成,课程设计的内容则鼓励有条件的读者多人合作完成。我们希望通过本书所设计的实验,加深读者对原理课程所授知识的理解。 -**操作系统的本质,是介于硬件和用户软件(应用)之间的一个大型软件系统,通过对硬件的包装来支撑应用的运行**。这一点,在代理内核(Proxy Kernel)上体现得尤为明显:它的作用是为应用提供最基本的操作系统支撑,使其能够在裸机(在实验中,我们采用的是Spike模拟器)上运行。正是由于这一特点,代理内核可以看成是操作系统的一个极小子集。在采用代理内核的场景下,计算机的软硬件界限以及代理内核的地位如图1.1所示。 +**操作系统的本质,是介于硬件和用户软件(应用)之间的一个大型软件系统,通过对硬件的包装来支撑应用的运行**。这一点,在代理内核(Proxy Kernel)上体现得尤为明显:它的作用是为应用提供最基本的操作系统支撑,使其能够在裸机(在实验中,我们采用的是Spike模拟器)上运行。正是由于这一特点,代理内核可以看成是操作系统的一个极小子集(对于代理内核的详细讨论,参见[1.6](chapter1.md#proxykernel))。在采用代理内核的场景下,计算机的软硬件界限以及代理内核的地位如图1.1所示。 ![fig0-1](pictures/fig0-1.png) @@ -12,14 +12,14 @@ 本书设计的实验并未采用完整操作系统内核的方案,这是因为如果采用完整内核的思路来设计实验,非常容易陷入某个模块(如启动、中断处理、内存管理、进程调度、设备管理、文件系统、Shell等等)的设计细节,整个过程虽然也会用到一些应用来验证,但设计的重点始终是操作系统本身。完整方案中,即使是为了运行一个最简单的Hello world!程序,都需要实现如用户态进程封装、进程调度等以及之前的一系列操作系统模块(如启动、中断处理、内存管理等),这样做反而忽略了操作系统对硬件进行封装以支撑应用运行的本质!同时,为了更“像”一个完整的操作系统,完整操作系统内核实验方案往往需要自己“发明”一个简单文件系统,并用它来组织虚拟磁盘(一般是用一个文件来模拟),这样就不得不引入大量与应用支撑无关的代码,导致了工程庞大和重点不突出等诸多问题。 -代理内核方案避免了完整内核方案以上的很多缺点,因为代理内核的存在“天生”就是为在硬件(模拟器)上支撑应用,而设计的最小化的操作系统内核。**它可以根据应用的复杂度的不同,调整自身的复杂度**:例如,如果只是为了支撑Hello world!程序,它就只需要加载、系统调用支持即可,无需进程调度、设备、文件,甚至虚拟存储等等内容。当然,如果所支撑的应用非常复杂(例如,一个多线程的网络媒体服务器),代理内核将约等于一个完整的操作系统内核!**从学习操作系统知识的角度来看,代理内核的这些特点更有利于读者站在应用的角度来审视操作系统对硬件的包装和为应用所做的支撑,从而更好地理解从原理课程所学到的知识**。也正是代理内核的这些优点,驱使我们在RISC-V Proxy Kernel and Boot Loader这个开源项目[ProxyKernel]的基础上,开发并设计了本书的一组操作系统实验。为了与开源项目进行区分,在本书中,我们将称我们开发和设计的代理内核为PKE(Proxy Kernel for Education)。 +代理内核方案避免了完整内核方案以上的很多缺点,因为代理内核的存在“天生”就是为在硬件(模拟器)上支撑应用,而设计的最小化的操作系统内核。**它可以根据应用的复杂度的不同,调整自身的复杂度**:例如,如果只是为了支撑Hello world!程序,它就只需要加载、系统调用支持即可,无需进程调度、设备、文件,甚至虚拟存储等等内容。当然,如果所支撑的应用非常复杂(例如,一个多线程的网络媒体服务器),代理内核将约等于一个完整的操作系统内核!**从学习操作系统知识的角度来看,代理内核的这些特点更有利于读者站在应用的角度来审视操作系统对硬件的包装和为应用所做的支撑,从而更好地理解从原理课程所学到的知识**。也正是代理内核的这些优点,驱使我们在RISC-V Proxy Kernel and Boot Loader这个开源项目[ProxyKernel](#references)的基础上,开发并设计了本书的一组操作系统实验。为了与开源项目进行区分,在本书中,我们将称我们开发和设计的代理内核为PKE(Proxy Kernel for Education)。 为完成本书所设计的实验,读者应具备汇编语言、C语言、基础的计算机体系结构知识、Linux环境中的基础软件(如编译器、常见命令,以及git工具等)的使用,这些基础知识。考虑到《计算机组成原理》课程与《操作系统原理》课程在开课时间上可能的重叠,以及RISC-V汇编语言和8086汇编语言的巨大不同,我们将在第一章介绍RISC-V计算机的体系结构的相关知识。从第二章开始,我们将开始介绍PKE的实验。在完成PKE的5个实验后,本书的第七章将给出操作系统课程设计题目。 - + 参考文献: -[ProxyKernel] RISC-V Proxy Kernel and Boot Loader. Avail. at: https://github.com/riscv/riscv-pk +[ProxyKernel] RISC-V Proxy Kernel and Boot Loader. https://github.com/riscv/riscv-pk \ No newline at end of file