准备知识
开篇¶
相信大家通过“计算机组成原理”这门课程已经对计算机如何计算以及执行指令有了一定的了解。
但我们计算机的处理器除了简单地执行指令之外,它还拥有特权级管理、异常与中断等功能,这些硬件功能与操作系统软件密切相关。
相信大家在通过本实验的学习后,能够对计算机的启动流程以及操作系统的运行流程有一个基本的认识,解开大家心中计算机底层的神秘面纱。
LoongArch32简介¶
LoongArch是一种精简指令集架构。分为32位与64位两个版本。我们操作系统实验采用的是32位版本。
通用寄存器¶
LoongArch32有32个通用寄存器,记为r0~r31,这些寄存器在ABI中定义了对应的别名,如下:
名称 | 别名 | 用途 | 在调用中是否保留 |
---|---|---|---|
$r0 |
$zero |
常数 0 |
(常数) |
$r1 |
$ra |
返回地址 | 否 |
$r2 |
$tp |
线程指针 | (不可分配) |
$r3 |
$sp |
栈指针 | 是 |
$r4 - $r5 |
$a0 - $a1 |
传参寄存器、返回值寄存器 | 否 |
$r6 - $r11 |
$a2 - $a7 |
传参寄存器 | 否 |
$r12 - $r20 |
$t0 - $t8 |
临时寄存器 | 否 |
$r21 |
保留 | (不可分配) | |
$r22 |
$fp / $s9 |
栈帧指针 / 静态寄存器 | 是 |
$r23 - $r31 |
$s0 - $s8 |
静态寄存器 | 是 |
注:LoongArch32老版本ABI中存在与v0
、v1
寄存器,与a0
、a1
同为$r4
与$r5
寄存器的别名,在新版ABI中已被舍弃。
基本整数指令¶
这一部分请同学们自行参考龙芯架构32位精简版参考手册_v1.02.pdf。
marco指令¶
marco指令是我们编写汇编时涉及到的一种特殊指令,它本身不定义在ISA中。这些marco指令定义出来后会在汇编器翻译成其他一条或多条真实存在的指令,而它存在的意义是为了让我们编写汇编代码时能够更加方便。
在我们移植的uCore for LoongArch32中,只用到了一种marco指令,就是li.w
。因为LoongArch32不支持32位的立即数,我们需要通过多条指令拼凑出一个32位的立即数,这样就可以用li.w
直接帮我们拼接,最终生成出来的是lu12i.w
与一条ori
指令共同完成li.w的操作。
由于助教暂未找到龙芯相关文档的说明,可以通过阅读汇编器的指令定义代码手动逆向marco指令:https://gitee.com/loongson-edu/la32r_binutils/blob/master/opcodes/loongarch-opc.c
如果有兴趣的同学想知道这些opcodes如何定义,可以参考助教的博客,曾经做过RISC-V上添加指令的工作。
LoongArch32拿来做操作系统实验有什么好处?¶
快速上手容易,与MIPS非常相似,简单易懂。且具有中文文档。
相比x86,历史包袱轻,不需要了解各种PIO相关寄存器的设置以及分段管理。
相比RISC-V,带MMU的操作系统直接运行在最高特权级,不需要了解SBI以及Supervisor特权级等一系列内容。
LoongArch32与x86、ARM、RISC-V在内存管理上的区别¶
它的虚拟内存管理采用 软件控制TLB ,与MIPS类似,当TLB查找失败或TLB查找成功但有效位为0时,产生对应的TLB异常,跳转到异常处理地址进行处理。
而x86、ARM、RISC-V都是采用硬件遍历页表方式,软件只需要将页表基地址通过一个CSR告知硬件(如x86上的CR3、RISC-V上的satp),硬件完成TLB填充。只有当页表遍历发现页面无效时才产生缺页异常,跳转到异常处理地址进行处理。
因此,在LoongArch32与MIPS一样 没有缺页异常 ,其缺页异常包含在TLB异常中,由操作系统判断是否是缺页。
在x86、ARM、RISC-V上 没有TLB异常。
控制状态寄存器¶
我们也许知道CPU上有一些通用寄存器,甚至有一些同学能非常熟悉他们的名字,例如x86-64上的RAX、RSP、RBP等,ARM/MIPS/RISC-V上的32个通用寄存器,以及各自的别名,如a0、a1、sp、ra等。
但我们的CPU上,还有一种特殊的寄存器,它称为控制状态寄存器(Control/Status Registers,CSR),它对计算机的系统状态与控制进行管理。并通过CSR指令对他们进行读写,在修改后,计算机的运行状态也就随之改变。
例如,在LoongArch32架构中,有两种特权级,PLV0(俗称内核态)与PLV3(俗称用户态)。类似于x86架构上的Ring0与Ring3。也类似ARM上的EL0与EL3,以及RISC-V架构中的Machine与User。
Note
小思考:操作系统一定运行在最高特权级吗?如果在操作系统之上还有一层特权级会带来什么好处?
感兴趣的同学可以了解RISC-V架构中的Supervisor特权级以及各个指令集架构的虚拟化机制。
在本次实验中,我们会涉及到CSR的CRMD
状态修改、DMW0
与DMW1
的配置。关于这里的寄存器各位域的具体含义以及配置方式,需要大家自行阅读龙芯架构32位精简版参考手册_v1.02.pdf完成。
Info
请大家自行阅读龙芯架构32位精简版参考手册_v1.02.pdf中的P55, 了解CRMD后,再继续了解后续内容。
关于DMW寄存器的部分可以暂时跳过,我们会在后文中介绍,可届时再回过头来看DMW的寄存器。
I/O与地址空间¶
I/O简介¶
许多同学也许会好奇,当我们的计算机连接上一个外设时,计算机是怎么发现这些外设的连接?外设与我们的计算机是怎么交互的呢?
这里就需要涉及到I/O的概念。
在我们的计算机中,I/O方式通常可分为3种:
PMIO (Port-Mapped I/O)¶
这是一种非常特殊的I/O,在LoongArch32、RISC-V、ARM、MIPS中 均不存在 。
在x86(含x86-64)架构中,有一个与内存独立的I/O地址空间,使用in[blw]
与out[blw]
指令,完成I/O地址与GPR(通用寄存器)间的数据移动。
MMIO (Memory-Mapped I/O)¶
内存映射I/O就是将I/O设备映射到物理内存地址空间中,像读写内存一样访问I/O设备。
这种方式对于软件而言非常方便,仅需要用一个指针指向对应的MMIO设备上的地址,并了解读写MMIO设备地址的具体含义即可完成IO操作。
例如我们实验中涉及到的UART串口就采用这种方式进行IO交互。
DMA (Direct Memory Access)¶
尽管MMIO很方便,但它的数据搬运需要CPU参与,效率较低。
对于一次访问大量数据的情况,我们可以让设备直接读写内存,避免经过CPU影响效率。
现在计算机的高速IO,例如网卡、硬盘控制器、GPU,都使用DMA技术将数据缓冲区直接放置于内存,避免大量数据传输时需要大量MMIO访问。
这里需要提醒同学们注意的是,DMA访问通常与其他IO方式以及中断配合使用。其他IO方式用于进行DMA传输的控制,中断用于通知DMA状态改变,例如有新数据已经完成写入内存或者缓冲区已经发到对应的设备。
Note
小思考:
-
如果CPU有Cache,对外设的MMIO访问能不能经过Cache呢?为什么?
-
如果CPU为乱序多发射架构,对外设的MMIO访问能否乱序发出呢?
-
除了插入内存屏障指令外,各个指令集架构还提供了什么配置使得使用虚拟地址进行MMIO访问时CPU一定能确保顺序执行?
-
如果CPU有Cache,DMA操作不经过Cache直接读写内存产生了Cache的数据与内存不一致怎么办?支持DMA一致性的CPU与不支持DMA一致性的CPU分别怎么做?
-
在C语言中完成MMIO访问时,为什么通常会对指向要访问的数据的指针加上 volatile 关键字,它会如何影响编译器行为?(同学们可以试试看简单写一个连续进行MMIO访问的裸机程序,并在编译时开启O2优化,观察生成汇编的变化。)
-
既然访问的数据终究是要被使用的,那么均摊时间来说DMA的优势在哪里呢?(提示:可以了解单次Uncached访问延迟,以及后续体系架构课Cache实验中的总线突发传输,SIMD指令,软件上对数据的零拷贝处理,或许能够解答同学们的疑惑。)
操作系统如何知道这台计算机上有哪些设备?¶
e820与ACPI¶
在x86计算机中,BIOS提供了一个e820内存管理器,并通过中断指令(x86上的int)调用BIOS完成物理内存地址的探测。
有在x86计算机上使用Linux的同学们,可以通过dmesg命令查看内核的输出,在内核启动时可以找到BIOS提供的e820内存映射表,且如果同学们的计算机采用核心显卡,往往会看到数百兆甚至数GB的内存带上了reserved标记,这是因为核心显卡会占用一部分内存。但这里还要提醒同学们,如果你使用虚拟机的话,看的就是你虚拟机的e820而不是真实计算机BIOS汇报的e820了。
ACPI(Advanced Configuration and Power Interface)是上个世纪90年代产生的一种开放标准,它定义了系统固件与操作系统之间的硬件抽象接口。x86计算机上的操作系统就可以利用ACPI对固件函数进行调用,获取系统上的内存布局、各设备所处的位置和设备型号等,就实现了同一个操作系统的二进制文件能够兼容多种不同的计算机。
设备树¶
自己给一些嵌入式开发板编译过系统软件的同学一定对设备树不陌生。
在这些没有ACPI机制的平台上,通常采用一种叫做设备树的脚本来描述计算机硬件上各个设备。该设备树通常会放置于Bootloader中通过参数传递给系统内核,也可能直接内嵌与系统内核中,想要知道设备树是个什么文件的同学可以翻看Linux内核源代码的arch/*/boot/dts
文件夹。
而对于一些即插即用的设备,例如USB与PCI-E,设备树通常仅提供其控制器本身的描述,这些即插即用设备的具体探测交给控制器驱动来完成。
还可以选择不探测¶
在LoongArch32的uCore移植中,为了简化操作系统,我们没有使用任何设备探测的方法。毕竟没有ACPI,也没有Bootloader传递设备树,即使有传递设备树也需要一部分解析设备树的代码,会增加系统的复杂程度。
对于内存大小,由于LoongArch32下可用物理内存范围是从0开始连续的,因此我们可以直接设定一个固定值32M,直接使用即可。
而对于IO外设,在uCore中仅使用串口,且它的MMIO地址以及中断服务号在我们使用的QEMU与Chiplab FPGA软核SoC环境中都是固定的,因此也将该地址固定了,直接使用即可。
虚拟地址管理¶
LoongArch32的虚拟地址映射有两种方式,一种是DMW,一种是TLB。DMW的查找优先于TLB的查找,当两者均查找失败时产生TLB REFILL例外(异常)。
DMW¶
LoongArch32有一种特殊的虚拟地址映射方式,称为直接映射配置窗口(DMW)。它可以提供虚拟地址[31:29]
部分到物理地址[31:29]
的映射配置。并可以在地址映射窗口中设置允许访问该窗口的特权级以及Cache属性。
详细信息请同学们参考龙芯架构32位精简版参考手册_v1.02.pdf中的P43。
TLB¶
大家应该在计算机组成原理课程时已经对TLB有一个初步的了解,它是一个虚拟地址翻译的缓冲区,储存了最近使用的一部分虚拟地址到物理地址的映射关系,并记录了对应虚拟页面的元数据,例如权限配置与Cache属性等。
也许有的同学会想,为什么需要TLB而不是每次访问页表加上Page Table Cache呢?这是因为多级格式遍历时需要逐层访问多个地址并不适合处理器单个周期访问,因为硬件上将页表遍历的结果放在一个缓冲区中,这个缓冲区就是TLB。
不同于x86、ARM、RISC-V等架构,由硬件遍历页表填充TLB,并提供页表基地址等寄存器由软件配置当前处理器遍历的页表。LoongArch32采用了与MIPS相同的软件控制TLB的地址翻译模式,详细信息请同学们参考龙芯架构32位精简版参考手册_v1.02.pdf中的P55。
本次实验中暂不涉及TLB,大家只需要了解它的存在即可。
如何配置DMW?¶
对于大多数指令集架构而言(我们熟悉的MIPS是个有固定虚拟地址映射的特例),在计算机上电启动时,软件运行在物理地址空间中。真实硬件上通常会在上电启动时从第一级Bootloader开始执行完成系统的初始化,再逐级载入更高级的Bootloader以至于最后启动操作系统。而具体从哪一级开始使用虚拟地址取决于各架构以及系统软件。对于我们本次实验使用的LoongArch32架构而言,在QEMU上运行时是在uCore操作系统启动时,由操作系统配置DMW窗口并修改CSR.CRMD寄存器开始开启虚拟地址映射的。而如果是使用Chiplab软核,它的第一级Bootloader是PMON,在PMON中就会设置DMW并开启虚拟地址映射。
在uCore中,会配置两个DMW窗口,分别用于内核普通的DRAM访问与IO外设访问,如下:
DMW编号 | MAT(存储访问类型) | PLV0 | PLV3 | PSEG | VSEG | VSEG范围 |
---|---|---|---|---|---|---|
DMW0 | 1(一致可缓存) | 1 | 0 | 0b000 (0x00000000-0x1fffffff) | 0b101 | 0xa0000000-0xbfffffff |
DMW1 | 0(强序非缓存) | 1 | 0 | 0b000 (0x00000000-0x1fffffff) | 0b100 | 0x80000000-0x9fffffff |
这里需要注意的是,如果是从QEMU模拟器启动操作系统,DMW我们可以自定义,但如果是从Chiplab的FPGA软核上使用PMON启动操作系统,由于PMON本身已经进行DMW的配置并运行在虚拟地址,因此我们的DMW配置必须与PMON一致才能保证系统正常启动。
特别需要注意的是,LoongArch32的QEMU有一个特点是它会在直接地址翻译模式下去除物理地址的高3位,以确保没有配置DMW时能够正确启动系统软件,但真实的LoongArch32硬件(如Chiplab软核)不会这么做。因此大家在编写系统软件的时候需要注意这个特征,编写那些运行在直接地址翻译模式的代码时需要确保取指地址和访存地址均为物理地址,若无法保证则可以做一层跳转(参考uCore处理TLBREFILL的代码),切换为映射地址翻译模式再进行下一步操作,防止编写的操作系统在QEMU上能运行但无法在真实硬件上运行。
QEMU简介¶
QEMU是一款模拟器(虚拟机),支持多种指令集架构。我们实验中使用的是LoongArch32架构的QEMU。