这篇看看怎么实现基于地址空间的分时多任务
其实也就是把原书的东西搬过来
os从bios,bootloader或sbi什么的启动之后,是没有开启分页模式的.
我们在内核初始化时,手动开启分页模式:
1 2 3 4 5 6 7 8 9 10 11
| lazy_static! { pub static ref KERNEL_SPACE: Arc<UPSafeCell<MemorySet>> = Arc::new(unsafe { UPSafeCell::new(MemorySet::new_kernel() )}); }
pub fn init() { heap_allocator::init_heap(); frame_allocator::init_frame_allocator(); KERNEL_SPACE.exclusive_access().activate(); }
|
打断一下,思考为什么不把trap段放到用户地址空间管理
原文:
另外的一种设计思路 B 是:让每个应用都有一个包含应用和内核的地址空间,并将其中的逻辑段分为内核和用户两部分,分别映射到内核/用户的数据和代码,且分别在 CPU 处于 S/U 特权级时访问。此设计中并不存在一个单独的内核地址空间。
设计方式 B 的优点在于: Trap 的时候无需切换地址空间,而在任务切换的时候才需要切换地址空间。相对而言,设计方式B比设计方式A更容易实现,在应用高频进行系统调用的时候,采用设计方式B能够避免频繁地址空间切换的开销,这通常源于快表或 cache 的失效问题。但是设计方式B也有缺点:即内核的逻辑段需要在每个应用的地址空间内都映射一次,这会带来一些无法忽略的内存占用开销,并显著限制了嵌入式平台(如我们所采用的 K210 )的任务并发数。此外,设计方式 B 无法防御针对处理器电路设计缺陷的侧信道攻击(如 熔断 (Meltdown) 漏洞 ),使得恶意应用能够以某种方式间接“看到”内核地址空间中的数据,使得用户隐私数据有可能被泄露。将内核与地址空间隔离便是修复此漏洞的一种方法。
熔断漏洞是什么?
我觉得这里值得开另一篇文章.这里就不细说了.
跳板
即在trap后 用于从用户地址空间切换到内核地址空间的一段代码.
(或是从内核地址空间到用户的)
跳板是在trap段上面的.
首先要扩展trap, 使其记录程序对应的内核栈:
1 2 3 4 5 6 7 8 9
| #[repr(C)] 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入口点的虚拟地址 }
|
然后是跳板实现:
trampoline放在text顶端:
1 2 3 4 5 6 7 8
| .text : { *(.text.entry) . = ALIGN(4K); strampoline = .; *(.text.trampoline); . = ALIGN(4K); *(.text .text.*) }
|
跳板在用户态和内核态中 映射到物理页的方式是相同的.
映射实现:
1 2 3 4 5 6 7 8
| fn map_trampoline(&mut self) { // 建立映射 self.page_table.map( VirtAddr::from(TRAMPOLINE).into(), PhysAddr::from(strampoline as usize).into(), PTEFlags::R | PTEFlags::X, ); }
|
加载和执行应用程序
这部分主要是对代码打注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| pub struct TaskControlBlock { pub task_status: TaskStatus, // 任务状态,只是一个枚举 pub task_cx: TaskContext, // 任务上下文 pub memory_set: MemorySet, // 此任务的地址空间. // 关于地址的管理结构: TaskControlBlock -> MemorySet -> PageTable -> FrameTracker pub trap_cx_ppn: PhysPageNum, // Trap上下文所在的物理页号 pub base_size: usize, // 应用使用了多少内存(不包括堆) } impl TaskControlBlock { pub fn new(elf_data: &[u8], app_id: usize) -> Self { // memory_set with elf program headers/trampoline/trap context/user stack let (memory_set, user_sp, entry_point) = MemorySet::from_elf(elf_data); // 获取elf头 let trap_cx_ppn = memory_set .translate(VirtAddr::from(TRAP_CONTEXT).into()) // 找到trap上下文对应的虚拟页表项 .unwrap() .ppn(); // 再找到其物理页 let task_status = TaskStatus::Ready; // 映射一段内核栈 let (kernel_stack_bottom, kernel_stack_top) = kernel_stack_position(app_id); KERNEL_SPACE.exclusive_access().insert_framed_area( kernel_stack_bottom.into(), kernel_stack_top.into(), MapPermission::R | MapPermission::W, ); let task_control_block = Self { task_status, task_cx: TaskContext::goto_trap_return(kernel_stack_top), memory_set, trap_cx_ppn, base_size: user_sp, }; // 初始化trap上下文 let trap_cx = task_control_block.get_trap_cx(); *trap_cx = TrapContext::app_init_context( entry_point, user_sp, KERNEL_SPACE.exclusive_access().token(), // 内核上下文 kernel_stack_top, // 内核栈顶 trap_handler as usize, // trap入口点的虚拟地址 ); task_control_block } }
|
剩下的内容是关于改进的,这里略.