diff --git a/ch4/os/src/config.rs b/ch4/os/src/config.rs index 4d60d5d..f351016 100644 --- a/ch4/os/src/config.rs +++ b/ch4/os/src/config.rs @@ -9,4 +9,7 @@ pub const MEMORY_END: usize = 0x80800000; // 设置我们当前操作系统最 pub const USER_STACK_SIZE: usize = 4096 * 2; // 每个应用用户态的栈大小为8kb pub const KERNEL_STACK_SIZE: usize = 4096 * 2; // 每个应用的内核栈 +pub const TRAMPOLINE: usize = usize::MAX - PAGE_SIZE + 1; // __alltraps对齐到了这里, (内核空间和用户空间这里虚拟地址都用这个)虚拟地址最高页 存放跳板, 跳板那一页, 被映射到 strampoline段(trap) 实际的物理地址, 即陷入时 cpu需要跳转的地址 +pub const TRAP_CONTEXT: usize = TRAMPOLINE - PAGE_SIZE; // 用户 trap context开始的位置 在次高页 + pub use crate::board::*; \ No newline at end of file diff --git a/ch4/os/src/mm/address.rs b/ch4/os/src/mm/address.rs index 7d0abbc..94bd43e 100644 --- a/ch4/os/src/mm/address.rs +++ b/ch4/os/src/mm/address.rs @@ -40,12 +40,16 @@ impl From for PhysPageNum { // 把物理地址转为 usize, 直接返回即可 impl From for usize { - fn from(v: PhysAddr) -> Self { v.0 } + fn from(v: PhysAddr) -> Self { + v.0 + } } // 把物理页结构体转为 usize类型的物理页 直接返回即可 impl From for usize { - fn from(v: PhysPageNum) -> Self { v.0 } + fn from(v: PhysPageNum) -> Self { + v.0 + } } // 把PhysAddr 转为 PhysPageNum @@ -67,12 +71,12 @@ impl PhysAddr { self.0 & (PAGE_SIZE - 1) } - // 舍去小数位, 把物理地址, 转为 物理页号 + // 舍去小数位, 把物理地址, 转为 物理页号 向下取整 pub fn floor(&self) -> PhysPageNum { PhysPageNum(self.0 / PAGE_SIZE) } - // 有小数统一 +1, 物理地址转为物理页号 + // 有小数统一 +1, 物理地址转为物理页号 向上取整 pub fn ceil(&self) -> PhysPageNum { PhysPageNum((self.0 + PAGE_SIZE - 1) / PAGE_SIZE) } @@ -101,6 +105,23 @@ impl PhysPageNum { } } +impl VirtAddr{ + // 舍去小数位, 把虚拟地址转为物理页号 向下取整 + pub fn floor(&self) -> VirtPageNum { + VirtPageNum(self.0 / PAGE_SIZE) + } + + // 有小数统一+1 虚拟地址转为虚拟页号 向上取整 + pub fn ceil(&self) -> VirtPageNum { + VirtPageNum((self.0 - 1 + PAGE_SIZE) / PAGE_SIZE) + } + + // 虚拟地址得到业内偏移 只保留12个bit即可 + pub fn page_offset(&self) -> usize { + self.0 & (PAGE_SIZE - 1) + } +} + // 把usize 转为 虚拟地址, 只保留39位 impl From for VirtAddr { @@ -128,6 +149,22 @@ impl From for VirtPageNum { } } +// 虚拟地址 转为虚拟页号 向下取整 +impl From for VirtPageNum { + fn from(v: VirtAddr) -> Self { + assert_eq!(v.page_offset(), 0); + v.floor() + } +} + +// 虚拟页号 转为虚拟地址 左移即可 +impl From for VirtAddr { + fn from(v: VirtPageNum) -> Self { + // 左移12位 + Self(v.0 << PAGE_SIZE_BITS) + } +} + impl VirtPageNum{ // 从虚拟页号中 抽离 9 9 9 的布局 @@ -140,4 +177,26 @@ impl VirtPageNum{ } idx } +} + + +// 代表一个range类型, 表示一段 虚拟地址 的区间 +#[derive(Copy, Clone)] +pub struct VPNRange { + pub l: VirtPageNum, + pub r: VirtPageNum +} + +impl Iterator for VPNRange { + type Item = VirtPageNum; + + fn next(&mut self) -> Option { + if self.l == self.r { + None + } else { + let current_vpn = self.l; + self.l = (self.l.0 + 1).into(); + Some(current_vpn) + } + } } \ No newline at end of file diff --git a/ch4/os/src/mm/memory_set.rs b/ch4/os/src/mm/memory_set.rs new file mode 100644 index 0000000..235b97a --- /dev/null +++ b/ch4/os/src/mm/memory_set.rs @@ -0,0 +1,261 @@ +use alloc::collections::BTreeMap; +use alloc::vec::Vec; +use bitflags::bitflags; +use crate::config::{MEMORY_END, PAGE_SIZE, TRAMPOLINE}; +use crate::mm::address::{PhysAddr, PhysPageNum, VirtAddr, VirtPageNum, VPNRange}; +use crate::mm::frame_allocator::{frame_alloc, FrameTracker}; +use crate::mm::page_table::{PageTable, PTEFlags}; +use crate::println; + +// 引入外部符号进来, 下面映射地址空间的逻辑段会用到 +extern "C" { + fn stext(); + fn etext(); + fn srodata(); + fn erodata(); + fn sdata(); + fn edata(); + fn sbss_with_stack(); + fn ebss(); + fn ekernel(); + fn strampoline(); // 这个符号 在linker.ld 中定义 +} + +// 某逻辑段的映射方式 +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum MapType { + Identical, // 恒等映射 + Framed, // 逻辑映射 +} + + +// 这个段的权限, 它是页表项标志位 PTEFlags 的一个子集, 仅仅保留4个标志位就足够了 +bitflags! { + /// map permission corresponding to that in pte: `R W X U` + pub struct MapPermission: u8 { + const R = 1 << 1; + const W = 1 << 2; + const X = 1 << 3; + const U = 1 << 4; + } +} + + +// 代表一个逻辑段 +pub struct MapArea { + vpn_range: VPNRange, // 逻辑段的虚拟页起始位置 和 结束位置 + data_frames: BTreeMap, // 这个逻辑段 虚拟页 对应的具体的物理帧, 这里使用rust 进行管理其物理帧的释放, 自动drop进行还给物理页帧管理器 + map_type: MapType, + map_perm: MapPermission, +} + +impl MapArea { + // 从虚拟地址起始和结束, 创建一个逻辑段结构体 + pub fn from(start_va: VirtAddr, end_va: VirtAddr, map_type: MapType, map_perm: MapPermission,) -> Self { + // 截取抛弃内存碎片 + let start_vpn: VirtPageNum = start_va.floor(); + let end_vpn: VirtPageNum = end_va.ceil(); + + Self { + vpn_range: VPNRange {l:start_vpn, r: end_vpn}, + data_frames: BTreeMap::new(), + map_type, + map_perm, + } + } +} + +impl MapArea { + // 把一个虚拟页号 创建一个对应的物理页帧 映射到page_table中, 并把创建的物理页帧生命周期 绑定到 self上 + pub fn map_one(&mut self, page_table: &mut PageTable, vpn: VirtPageNum) { + let ppn: PhysPageNum; + match self.map_type { + // 恒等映射 + MapType::Identical => { + ppn = PhysPageNum(vpn.0); + } + MapType::Framed => { + // 随便创建一个 物理页帧 + let frame = frame_alloc().unwrap(); + ppn = frame.ppn; + // 把随便创建的物理页帧, 绑定到data_frames 中被 self管理生命周期 + self.data_frames.insert(vpn, frame); + } + } + + // 根据上面创建的物理页帧映射pte 到三级页表中 + let pte_flags = PTEFlags::from_bits(self.map_perm.bits).unwrap(); + page_table.map(vpn, ppn, pte_flags); + } + + // 取消映射一个vpn, 并把 self管理的 物理页帧的生命周期结束 + pub fn unmap_one(&mut self, page_table: &mut PageTable, vpn: VirtPageNum) { + if self.map_type == MapType::Framed { + // 删除映射关系, 自动调用 drop 把vpn对应的ppn还给全局内存管理器 + self.data_frames.remove(&vpn); + } + // 物理帧不存在了 把vpn 对应的pte设置为空 + page_table.unmap(vpn); + } + + // 映射 当前逻辑段中的每一个虚拟页, 到页表中 + pub fn map(&mut self, page_table: &mut PageTable) { + for vpn in self.vpn_range { + self.map_one(page_table, vpn); + } + } + + // 在页表中取消映射每一个虚拟页 + pub fn unmap(&mut self, page_table: &mut PageTable) { + for vpn in self.vpn_range { + self.unmap_one(page_table, vpn); + } + } + + // 拷贝外部的数据二进制 到当前的逻辑段中 按照页 为单位 + pub fn copy_data(&mut self, page_table: &mut PageTable, data: &[u8]) { + assert_eq!(self.map_type, MapType::Framed); + + let mut start: usize = 0; + + // 当前逻辑段的起始页位置 + let mut current_vpn = self.vpn_range.l; + // 需要拷贝的字节总大小 + let len = data.len(); + loop { + // 拷贝 每次最大拷贝1个页 + let src = &data[start..len.min(start + PAGE_SIZE)]; + + // 得到逻辑段每个页的起始位置 + let dst = &mut page_table + .get_pte(current_vpn) // 在页表中, 根据逻辑段中的vpn 得到 54bit的pte (44+10) + .unwrap() + .ppn() // 根据54bit的pte, 得到44bit 的物理页帧的位置 + .get_bytes_array()[..src.len()]; // 得到物理页帧那一页的起始的地址 44bit<<12 + dst.copy_from_slice(src); + start += PAGE_SIZE; + if start >= len { + break; + } + current_vpn = (current_vpn.0 + 1).into(); + } + } +} + + +// 表示一个地址空间, 它是一个页表和 包含所有段信息的页表组成 +pub struct MemorySet { + page_table: PageTable, + areas: Vec, +} + +impl MemorySet { + pub fn new() -> Self { + Self { + page_table: PageTable::new(), + areas: Vec::new(), + } + } +} + +impl MemorySet { + // 得到当前地址空间的 页表token + pub fn token(&self) -> usize { + self.page_table.token() + } + + // 根据 start va 和end va 创建一个 逻辑段类型为逻辑映射, 插入到 当前地址空间 + pub fn insert_framed_area(&mut self, start_va: VirtAddr, end_va: VirtAddr, permission: MapPermission) { + self.push(MapArea::from(start_va, end_va, MapType::Framed, permission), None); + } + + // 把一个 逻辑段, 映射到当前页表中 + fn push(&mut self, mut map_area: MapArea, data: Option<&[u8]>) { + map_area.map(&mut self.page_table); + if let Some(data) = data { + map_area.copy_data(&mut self.page_table, data); + } + self.areas.push(map_area); + } + + /// 创建跳板, 跳板就是 trap.S中的代码段中.text.trampoline, 由 linker.ld手动布局在代码段 + /// 内核空间和用户空间,他们都会运行这个函数, 插入同样的代码, 把虚拟地址映射为同样的物理地址, 这里内核空间和用户空间执行这段代码的时候, 每个 虚拟地址对应的物理地址是相同的 + /// 所以 (内核空间的 TRAMPOLINE+指令offset) == (用户空间的 TRAMPOLINE+offset) == (strampoline + offset) + /// (内核的 TRAMPOLINE) 和 (用户的 TRAMPOLINE) 和 strampoline 他们都是相等的 + /// 在 trap.S 切换用户空间之后, 取值执行, 切换空间之前和切换空间之后, 他们对应的虚拟地址空间是相同的, 且都映射到了同一个物理页帧 + fn map_trampoline(&mut self) { + // 直接插入到 把 linker.ld 中的符号strampoline, 映射到最高的虚拟页, 他的物理页帧是 链接脚本中我们手动内存布局的符号 + // 如果是用户应用, 这个符号的最下面就是 次高页 即 trap context + self.page_table.map( + VirtAddr::from(TRAMPOLINE).into(), + PhysAddr::from(strampoline as usize).into(), + PTEFlags::R | PTEFlags::X, + ); + } + + // 创建一个内核用的地址空间 + pub fn new_kernel() -> Self { + let mut memory_set = Self::new(); + // 跳板创建 + memory_set.map_trampoline(); + // map kernel sections + println!(".text [{:#x}, {:#x})", stext as usize, etext as usize); + println!(".rodata [{:#x}, {:#x})", srodata as usize, erodata as usize); + println!(".data [{:#x}, {:#x})", sdata as usize, edata as usize); + println!( + ".bss [{:#x}, {:#x})", + sbss_with_stack as usize, ebss as usize + ); + println!("mapping .text section"); + memory_set.push( + MapArea::from( + (stext as usize).into(), + (etext as usize).into(), + MapType::Identical, + MapPermission::R | MapPermission::X, + ), + None, + ); + println!("mapping .rodata section"); + memory_set.push( + MapArea::from( + (srodata as usize).into(), + (erodata as usize).into(), + MapType::Identical, + MapPermission::R, + ), + None, + ); + println!("mapping .data section"); + memory_set.push( + MapArea::from( + (sdata as usize).into(), + (edata as usize).into(), + MapType::Identical, + MapPermission::R | MapPermission::W, + ), + None, + ); + println!("mapping .bss section"); + memory_set.push( + MapArea::from( + (sbss_with_stack as usize).into(), + (ebss as usize).into(), + MapType::Identical, + MapPermission::R | MapPermission::W, + ), + None, + ); + println!("mapping physical memory"); + memory_set.push( + MapArea::from( + (ekernel as usize).into(), + MEMORY_END.into(), + MapType::Identical, + MapPermission::R | MapPermission::W, + ), + None, + ); + memory_set + } +} diff --git a/ch4/os/src/mm/mod.rs b/ch4/os/src/mm/mod.rs index 11b2e82..b373767 100644 --- a/ch4/os/src/mm/mod.rs +++ b/ch4/os/src/mm/mod.rs @@ -2,6 +2,7 @@ pub mod heap_allocator; pub mod address; pub mod frame_allocator; pub mod page_table; +pub mod memory_set;