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);
}
提纲
2.2 RISC-V SV39页机制
└── user
├── build.py(移除:给应用设定唯一起始地址的脚本)
└── src(用户态库和应用程序)
├── bin(各个应用程序)
├── ...
└── linker.ld(修改:将所有应用放在各自地址空间中固定的位置)
├── os
└── src
├── config.rs(修改:新增一些内存管理的相关配置)
├── linker-k210.ld(修改:将跳板页引入内存布局)
├── linker-qemu.ld(修改:将跳板页引入内存布局)
├── loader.rs(修改:仅保留获取应用数量和数据的功能)
├── main.rs(修改)
├── 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
└── src
├── syscall
├──fs.rs(修改:基于地址空间的 sys_write 实现)
├── task
├──context.rs(修改:构造一个跳转到不同位置的初始任务上下文)
├──mod.rs(修改)
└──task.rs(修改)
└── trap
├── context.rs(修改:在 Trap 上下文中加入了更多内容)
├── mod.rs(修改:基于地址空间修改了 Trap 机制)
└── trap.S(修改:基于地址空间修改了 Trap 上下文保存与恢复汇编代码)
提纲
2.1 代码结构
MODE
=8PPN
提纲
3.2 跳板页
3.3 应用的地址空间
提纲
3.1 ASOS地址空间
3.3 应用的地址空间
trap.S
中的执行代码_all_traps
入口_all_traps
汇编函数会保存相关寄存器到陷入上下文_restore
汇编函数会从陷入上下文中恢复相关寄存器陷入上下文保存在内核栈顶,sscratch
保存应用的内核栈
sscratch
寄存器中转用户/内核的栈指针如何只通过sscratch
寄存器中转栈指针和页表基址?
sscratch
寄存器中转用户/内核的栈指针sscratch
寄存器中转用户栈指针/页表基址sscratch
寄存器中转用户/内核的栈指针sscratch
寄存器中转用户/内核的栈指针
sscratch
寄存器中转用户栈指针/页表基址sscratch
寄存器中转用户栈指针/页表基址sscratch
- 应用的陷入上下文地址sscratch
进行应用的用户态栈指针<->陷入上下文地址切换(中转);提纲
3.1 ASOS地址空间
3.2 跳板页
BASE_ADDRESS
= 0x10000提纲
4.2 建立内核/应用页表
4.3 管理地址空间
0x80000000
os/src/linker.ld
中ekernel
地址0x80800000
alloc()
和dealloc()
函数接口提纲
4.1 管理物理内存
4.3 管理地址空间
VPN: Virtual Page Number
PPN: Physical Page Number
satp: 包含页表起始处PPN的CSR
satp= root_ppn
核心数据结构的包含关系
TCB-->MemorySet-->PageTable-->root_ppn
任务控制块 --------------->任务的页表基址
提纲
4.1 管理物理内存
4.2 建立内核/应用页表
理想: 丰满 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
的过程提纲
5.2 实现跳板机制
5.3 加载和执行应用程序
5.4 改进 Trap 处理的实现
5.5 改进 sys_write 的实现
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();
}
提纲
5.1 启动分页模式
5.3 加载和执行应用程序
5.4 改进 Trap 处理的实现
5.5 改进 sys_write 的实现
在启动分页机制后,让处于不同地址空间的应用和内核能够进行正常的特权级切换操作和数据交互。
将 trap.S 中的整段汇编代码放置在 .text.trampoline 段,并在调整内存布局的时候将它对齐到代码段的一个页面中
# os/src/linker.ld
stext = .;
.text : {
*(.text.entry)
. = ALIGN(4K);
strampoline = .;
*(.text.trampoline);
. = ALIGN(4K);
*(.text .text.*)
}
// 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 入口点的虚拟地址
}
Q:为何用jr t1
而不是 call trap_handler
完成跳转?
Q:为何用jr t1
而不是 call trap_handler
完成跳转?
提纲
5.1 启动分页模式
5.2 实现跳板机制
5.4 改进 Trap 处理的实现
5.5 改进 sys_write 的实现
// 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,
}
提纲
5.1 启动分页模式
5.2 实现跳板机制
5.3 加载和执行应用程序
5.5 改进 sys_write 的实现
由于应用的 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}",
)
}
提纲
5.1 启动分页模式
5.2 实现跳板机制
5.3 加载和执行应用程序
5.4 改进 Trap 处理的实现
页表模块 page_table 提供了将应用地址空间中一个缓冲区转化为在内核空间中能够直接访问的形式的辅助函数:
// os/src/mm/page_table.rs
pub fn translated_byte_buffer()
头甲龙 ankylosauridae 白垩纪晚期
TLB、PCID与ASID的故事 https://blog.csdn.net/lee_ham/article/details/103107135 有的TLB在每个TLB条目中还保存地址空间标识码(address-space identifier,ASID)。 ASID可用来唯一标识进程,并为进程提供地址空间保护。当TLB试图解析虚拟页号时,它确保当前运行进程的ASID与虚拟页相关的ASID相匹配。如果不匹配,那么就作为TLB失效。 除了提供地址空间保护外,ASID允许TLB同时包含多个进程的条目。如果TLB不支持独立的ASID,每次选择一个页表时(例如,上下文切换时),TLB就必须被冲刷(flushed)或删除,以确保下一个进程不会使用错误的地址转换。 PCID的功能和ASID类似。ASID主要在ARM。 PCID(进程上下文标识符)是在Westmere架构引入的新特性。简单来说,在此之前,TLB是单纯的VA到PA的转换表,进程1和进程2的VA对应的PA不同,不能放在一起。加上PCID后,转换变成”VA和进程上下文ID“到PA的转换表,放在一起完全没有问题了。这样进程1和进程2的页表可以和谐的在TLB中共处。 ASID域(Address Space IDentifier)为地址空间标识符,标记该项地址属于哪个地址空间,只有CPU当前的ASID号和该域匹配的时候,地址查找才能命中,ASID域可以理解为用于区分不同进程表项的编号
--- ## 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``入口 - 并在切换页表后,平滑地继续执行
![bg right:50% 100%](figs/trampoline.png)
--- RISC-V只提供一个 sscratch 寄存器可用来进行临时周转 1. 必须先切换到内核地址空间,这就需要将内核地址空间的 token 写入 satp 寄存器; 2. 之后还需要保存应用的内核栈栈顶的位置,这样才能以它为基址保存 Trap 上下文。 3. 这两步需要用通用寄存器作为临时周转,然而我们无法在不破坏任何一个通用寄存器的情况下做到这一点。 4. 所以,我们不得不将 Trap 上下文保存在应用地址空间的一个虚拟页面中,而不是切换到内核地址空间去保存。