Into Test load_fault, we will insert an invalid load operation...
Kernel should kill this application!
[kernel] PageFault in application, bad addr = 0x0, bad instruction = 0x1009c, kernel killed it.
store_fault APP running...
Into Test store_fault, we will insert an invalid store operation...
Kernel should kill this application!
[kernel] PageFault in application, bad addr = 0x0, bad instruction = 0x1009c, kernel killed it.
power_3 [130000/300000]
其中包含两个应用程序04load_fault
, 05store_fault
// usr/src/bin/04load_fault.rs
......
unsafe {
let _i=read_volatile(null_mut::<u8>());
}
// usr/src/bin/05store_fault.rs
......
unsafe {
null_mut::<u8>().write_volatile(1);
}
软件架构
构建应用
└── user
├── build.py(移除:给应用设定唯一起始地址的脚本)
└── src(用户态库和应用程序)
├── bin(各个应用程序)
├── ...
└── linker.ld(修改:将所有应用放在各自地址空间中固定的位置)
改进OS
├── os
└── src
├── config.rs(修改:新增一些内存管理的相关配置)
├── linker-k210.ld(修改:将跳板页引入内存布局)
├── linker-qemu.ld(修改:将跳板页引入内存布局)
├── loader.rs(修改:仅保留获取应用数量和数据的功能)
├── main.rs(修改)
改进OS
├── os
└── src
├── mm(新增:内存管理的 mm 子模块)
├──address.rs(物理/虚拟 地址/页号的 Rust 抽象)
├──frame_allocator.rs(物理页帧分配器)
├──heap_allocator.rs(内核动态内存分配器)
├──memory_set.rs(引入地址空间 MemorySet 及逻辑段 MemoryArea 等)
├──mod.rs(定义了 mm 模块初始化方法 init)
└──page_table.rs(多级页表抽象 PageTable 以及其他内容)
改进OS
├── os
└── src
├── syscall
├──fs.rs(修改:基于地址空间的 sys_write 实现)
├── task
├──context.rs(修改:构造一个跳转到不同位置的初始任务上下文)
├──mod.rs(修改)
└──task.rs(修改)
└── trap
├── context.rs(修改:在 Trap 上下文中加入了更多内容)
├── mod.rs(修改:基于地址空间修改了 Trap 机制)
└── trap.S(修改:基于地址空间修改了 Trap 上下文保存与恢复汇编代码)
虚拟地址将内存划分为固定大小的页来进行地址转换和内容保护。
satp(Supervisor Address Translation and Protection,监管者地址转换和保护)S模式控制状态寄存器控制了分页。satp 有三个域:
RISC-V SV39页机制
satp CSR:(S-Mode)
Supervisor Address Translation and Protection,监管者地址转换和保护)
控制硬件分页机制
satp:页表基址寄存器
RISC-V SV39页机制
初始化&使能页机制
MODE
=8PPN
satp:页表基址寄存器
RISC-V SV39页机制
页表项属性含义
应用角度:理解地址空间
OS角度:理解地址空间
理解跳板
跳板页
trap.S
中的执行代码理解跳板
基于跳板页的平滑过渡
_all_traps
入口理解陷入上下文页
_all_traps
汇编函数会保存相关寄存器到陷入上下文_restore
汇编函数会从陷入上下文中恢复相关寄存器回顾之前没有页机制的OS
陷入上下文保存在内核栈顶
sscratch
保存应用的内核栈
sscratch
寄存器中转用户/内核的栈指针对比使能页机制的OS
如何只通过sscratch
寄存器中转栈指针和页表基址?
sscratch
寄存器中转用户/内核的栈指针sscratch
寄存器中转用户栈指针/页表基址对比使能页机制的OS
如何只通过sscratch
寄存器中转栈指针和页表基址?
sscratch
寄存器中转用户/内核的栈指针
对比使能页机制的OS
如何只通过sscratch
寄存器中转栈指针和页表基址?
sscratch
寄存器中转用户栈指针/页表基址对比使能页机制的OS
如何只通过sscratch
寄存器中转栈指针和页表基址?
方案3:sscratch
:应用的陷入上下文地址
sscratch
进行应用的用户态栈指针<->陷入上下文地址切换;BASE_ADDRESS
= 0x100000x80000000
os/src/linker.ld
中ekernel
地址0x80800000
alloc
和dealloc
函数接口VPN: Virtual Page Number
PPN: Physical Page Number
satp: 包含页表起始处PPN的CSR
在多级页表中找到一个虚拟地址对应的页表项。
找到后,只要修改页表项的内容即可完成键值对的插入和删除。
satp= root_ppn
核心数据结构的包含关系
TCB-->MemorySet-->PageTable-->root_ppn
任务控制块 --------------->任务的页表基址
应用的页表代表现实情况下的应用地址空间
应用的逻辑段代表了理想情况下的应用地址空间
理想: 丰满 v.s. 现实: 骨感
MapArea
// os/src/mm/memory_set.rs
pub struct MapArea {
vpn_range: VPNRange, //一段虚拟页号的连续区间
data_frames: BTreeMap<VirtPageNum, FrameTracker>,//VPN<-->PPN映射关系
map_type: MapType, //映射类型
map_perm: MapPermission, //可读/可写/可执行属性
}
data_frames
是一个保存了该逻辑段内的每个虚拟页面和它被映射到的物理页帧 FrameTracker 的一个键值对容器
MemorySet
// os/src/mm/memory_set.rs
pub struct MemorySet {
page_table: PageTable, //页表
areas: Vec<MapArea>, //一系列有关联的不一定连续的逻辑段
}
地址空间 包含
PageTable
的变量page_table
MapArea
的向量 areas
MemorySet
=PageTable
+MapAreas
MemorySet
MemorySet
所占内存MemorySet
MemorySet
为内核的MemorySet
MemorySet
为任务的MemorySet
MemorySet
的相关操作
MemorySet
的相关操作
对分时共享多任务操作系统的扩展
KERNEL_SPACE
pub static ref KERNEL_SPACE: MemorySet = MemorySet::new_kernel()
KERNEL_SPACE
,// os/src/mm/mod.rs
pub fn init() {
heap_allocator::init_heap();
frame_allocator::init_frame_allocator();
KERNEL_SPACE.exclusive_access().activate();
}
实现跳板机制的动机
实现跳板机制的大致思路
实现跳板机制的大致思路
实现跳板机制的大致思路
实现跳板机制的大致思路
实现跳板机制的大致思路
建立跳板页面
将 trap.S 中的整段汇编代码放置在 .text.trampoline 段,并在调整内存布局的时候将它对齐到代码段的一个页面中
# os/src/linker.ld
stext = .;
.text : {
*(.text.entry)
. = ALIGN(4K);
strampoline = .;
*(.text.trampoline);
. = ALIGN(4K);
*(.text .text.*)
}
扩展Trap 上下文数据结构
// os/src/trap/context.rs
pub struct TrapContext {
pub x: [usize; 32],
pub sstatus: Sstatus,
pub sepc: usize,
pub kernel_satp: usize, //内核页表的起始物理地址
pub kernel_sp: usize, //当前应用内核栈栈顶的虚拟地址
pub trap_handler: usize,//内核中 trap handler 入口点的虚拟地址
}
保存Trap上下文
恢复Trap上下文
保存Trap上下文
Q:为何用jr t1
而不是 call trap_handler
完成跳转?
保存Trap上下文
Q:为何用jr t1
而不是 call trap_handler
完成跳转?
因为在内存布局中,这条 .text.trampoline 段中的跳转指令和 trap_handler 都在代码段之内,汇编器(Assembler)和链接器(Linker)会根据 linker-qemu.ld 的地址布局描述,设定跳转指令的地址,并计算二者地址偏移量,让跳转指令的实际效果为当前 pc 自增这个偏移量。
但实际上由于我们设计的缘故,这条跳转指令在被执行的时候,它的虚拟地址被操作系统内核设置在地址空间中的最高页面之内,所以加上这个偏移量并不能正确的得到 trap_handler 的入口地址。
// os/src/task/task.rs
pub struct TaskControlBlock {
pub task_cx: TaskContext,
pub task_status: TaskStatus,
pub memory_set: MemorySet,
pub trap_cx_ppn: PhysPageNum,
pub base_size: usize,
}
由于应用的 Trap 上下文不在内核地址空间,因此我们调用 current_trap_cx 来获取当前应用的 Trap 上下文的可变引用而不是像之前那样作为参数传入 trap_handler 。至于 Trap 处理的过程则没有发生什么变化。
为了简单起见,弱化了 S态 –> S态的 Trap 处理过程:直接 panic 。
注:ch9会支持 S态 –> S态的 Trap 处理过程
let restore_va = __restore as usize - __alltraps as usize + TRAMPOLINE;
unsafe {
asm!(
"fence.i",
"jr {restore_va}",
)
}
类似Trap处理的改进,由于内核和应用地址空间的隔离, sys_write 不再能够直接访问位于应用空间中的数据,而需要手动查页表才能知道那些数据被放置在哪些物理页帧上并进行访问。
为此,页表模块 page_table 提供了将应用地址空间中一个缓冲区转化为在内核空间中能够直接访问的形式的辅助函数:
// os/src/mm/page_table.rs
pub fn translated_byte_buffer(
头甲龙 ankylosauridae 白垩纪晚期
--- ## S-Mode编程 -- 虚存机制 - 通过stap CSR建立页表基址 - 建立OS和APP的页表 - 处理内存访问异常 ![bg right 90%](figs/riscv_pagetable.svg) --- ## S-Mode编程 -- 虚存机制 - S、U-Mode中虚拟地址会以从根部遍历页表的方式转换为物理地址: - satp.PPN 给出了一级页表的基址, VA [31:22] 给出了一级页号,CPU会读取位于地址(satp. PPN × 4096 + VA[31: 22] × 4)页表项。 - PTE 包含二级页表的基址,VA[21:12]给出了二级页号,CPU读取位于地址(PTE. PPN × 4096 + VA[21: 12] × 4)叶节点页表项。 - 叶节点页表项的PPN字段和页内偏移(原始虚址的最低 12 个有效位)组成了最终结果:物理地址(LeafPTE.PPN×4096+VA[11: 0]) --- ## S-Mode编程 -- 虚存机制 - S、U-Mode中虚拟地址会以从根部遍历页表的方式转换为物理地址: ![w:500](figs/satp2.png)
主要的事情就是**实现地址空间**
**程序设计**
- 但用户态无法访问此内存区域 - 产生异常/中断时,会跳到跳板页的``_all_traps``入口 - 并在切换页表后,平滑地继续执行
**程序设计**
**程序设计**
--- RISC-V只提供一个 sscratch 寄存器可用来进行临时周转 1. 必须先切换到内核地址空间,这就需要将内核地址空间的 token 写入 satp 寄存器; 2. 之后还需要保存应用的内核栈栈顶的位置,这样才能以它为基址保存 Trap 上下文。 3. 这两步需要用通用寄存器作为临时周转,然而我们无法在不破坏任何一个通用寄存器的情况下做到这一点。 4. 所以,我们不得不将 Trap 上下文保存在应用地址空间的一个虚拟页面中,而不是切换到内核地址空间去保存。