MIT-6S081-L1
计算机结构
为了更好地理解OS,这里将计算机组成放到这里。

CPU有不同字长区分,CPU字长指整数和指针数据的标称大小。早期PC机性能较差,多为32位计算机。现多为64位计算机。
通过指针字节长度可以算出虚拟地址最大存储空间(即指针排列情况),32位计算机只有2^32存储空间,64位计算机有2^64的存储空间。
但只是虚拟地址,实际物理地址可能并没有这么大空间。
应用程序也不会直接调用硬件级别的命令,而是使用OS级别的命令,通过OS调用硬件命令。
这是为了实现OS系统的特性:
- isolation 隔离性:将软件和物理内存分分隔,保证进程Crush不会导致整个OS崩溃。
- multiplexing 多进程同时操作:将软件和物理内存分分隔,保证进程间互不影响。
- interaction 交互性:提供统一的硬件调用方法。
页表
通过页表实现了虚拟地址到物理地址的映射。

在虚拟地址中,堆从下往上增长,栈从上往下增长。
.text是代码区,放的是编译好的代码。
data区域是全局变量和静态变量。
虚拟地址还需要在CPU中翻译为物理地址。负责计算物理地址的叫memory management unit(mmu)。
在RISC-V操作系统中,地址后12位为offset,前面为物理页表的页号。页表中记录PPN(Physical Page Number)和flag(记录地址状态,如valid等)

分级页表
但前面提到虚拟地址可能要比物理地址大(可以等于,也可以小于,但一般VA大于PA),是因为在VA映射到PA时PPN可能是无效的,即没有实际对应的物理内存,就会及时开辟一个新的页表让该地址对应到物理地址。而如图一张页表,字节数也较大,为了一个地址申请一个新表可能会很耗时,遂考虑解决方法。
考虑将页表分级。

快表TLB
实际上mmu映射到PA也是个复杂的过程,为了快速访问,会将最近使用的VA和PA映射关系存到一个缓存表中,名为快表TLB
- 总结:因此,软件进程中访问一个虚拟地址,流程是:查TLB —》无结果则mmu映射到物理地址 —》多级缓存机制加速地址访问过程…
Calling Convention & Stack Frames
- Review:
C/C++代码需要编译为二进制代码。
编译过程如下:

Stack Frames && Calling Convention
编译后的代码会存储在.text代码段,调用函数即使用函数第一行的地址,如下:

函数嵌套调用时会先移动sp指针,包含调用函数中创建的临时变量、前一帧的地址、函数返回地址等, 调用结束后返回fp指针(返回地址),sp向后移动。
sp指向栈“顶”,fp指向当前的栈帧首地址。

寄存器对编译的函数有统一的限制,如寄存器a0~a1仅用于存储函数参数或返回值。这里不做深入讨论。
仅补上下面概念:
Caller —— 返回一个调用当前函数的引用
Callee —— 返回一个正在被执行函数的引用
trap
在调用ecall时(如fork()、write()等),需要权限从用户空间转换到内核空间,其中的设计和细节对建立OS系统的隔离性和安全性很重要,。
什么是trap
trap 是 CPU 提供的一种不可绕过的控制转移机制。当程序在运行过程中发生以下情况之一时,CPU 会强制中断当前执行流,将控制权交给操作系统内核:
- System Call:程序主动请求内核服务(如 ecall)
- Exception:程序执行过程中出现异常(如 page fault、非法指令)
- Interrupt:外部或定时事件(如时钟中断、设备中断)
trap主要负责,在不信任当前程序的前提下,将 CPU 的控制权安全地移交给内核。
比如,ecall时需要从user space到kernel space再切换回user space,该过程就是trap。

trap的过程
一个程序运行最后都是在寄存器上执行汇编代码的。主要有一定数量的寄存器(a0、a1等与软件执行相关的寄存器)和特殊寄存器(satp、satt、stvec、sscratch等)。为了隔离和安全,且让任何程序都能在os上互不干扰地执行,就要在ecall时保存进入内核空间的程序堆栈,还不能保存到程序堆栈上。
(略过了一万个细节…暂时不想在这上面浪费很多时间)
另一点是,user page始终会保存一个用来缓存寄存器(仅a0、a1那些)的页表。系统启动时和每次回到user space时都会重新设置user page的该值以便下次ecall时使用。