OS 简单教程十OS 简单教程十
🩻

OS 简单教程十

首先,为什么要虚拟化 CPU 呢?
CPU 是用来计算的,当应用程序越来越复杂,交互性也越来越强,应用要更多的跟外设交互,产生 IO 。产生 IO 的时候 CPU 处于等待状态,浪费掉了,如果这时候切换到另一个需要计算的应用,这样切换的成本低于浪费掉的就是划算的。
而不能因为 CPU 的切换就需要应用改变,这种切换最好对应用来讲是透明的。这就分为协作式和抢占式,协作式需要主动调用 yield 来放弃 CPU ,抢占式通过时钟来被动切换进而达到对应用透明的目的。抢占式通过 CPU 的快速切换来让应用以为自己独占 CPU 。
应用程序在运行时被抽象成任务,那些 CPU 运行需要的信息保存在任务信息中。

任务信息

任务信息是 TaskControlBlock
 pub struct TaskControlBlock {      pub task_cx_ptr: usize,      pub task_status: TaskStatus,      pub memory_set: MemorySet,      pub trap_cx_ppn: PhysPageNum,      pub base_size: usize,  }
  • task_cx_ptr 是任务上下文的指针
  • task_status 是任务状态
  • memory_set 是读取 elf 获取的地址空间
  • trap_cx_ppn 是 trap 上下文的物理页
rust 通过操纵 TaskControlBlock 来间接操纵任务,我们从总体视角看看
  • 定时器每隔一段时间产生一个中断,进入 trap
  • trap.asm 在虚拟内存的跳板上,每个应用和内核都是相同的映射 stvec::write(TRAMPOLINE as usize, TrapMode::Direct); 通过这行代码,trap 的处理设定为从跳板的开头开始
  • 跳板的开头是 __alltraps ,保存寄存器后跳到 rust 代码的 trap_handler
   Trap::Interrupt(Interrupt::SupervisorTimer) => {              set_next_trigger();              suspend_current_and_run_next();         }
  • 分支切换到下个任务,suspend_current_and_run_next 调用 run_next_task 。
     fn run_next_task(&self) {          if let Some(next) = self.find_next_task() {              let mut inner = self.inner.borrow_mut();              let current = inner.current_task;              inner.tasks[next].task_status = TaskStatus::Running;              inner.current_task = next;              let current_task_cx_ptr2 = inner.tasks[current].get_task_cx_ptr2();              let next_task_cx_ptr2 = inner.tasks[next].get_task_cx_ptr2();              core::mem::drop(inner);              unsafe {                  __switch(current_task_cx_ptr2, next_task_cx_ptr2);             }         } else {              panic!("All applications completed!");         }     }
  • run_next_task 具体调用 __switch 切换任务
  • __switch 其实就是一个函数调用
    • 保存被调用寄存器
    • 切换 sp 和 ra 来达到切换应用到目的
  • 最后调用 trap_return 间接调用 __restore
再一个个的看,处理 trap 和上节相同,重点是如何切换任务,也就是 __switch
首先,为什么要虚拟化 CPU 呢?
CPU 是用来计算的,当应用程序越来越复杂,交互性也越来越强,应用要更多的跟外设交互,产生 IO 。产生 IO 的时候 CPU 处于等待状态,浪费掉了,如果这时候切换到另一个需要计算的应用,这样切换的成本低于浪费掉的就是划算的。
而不能因为 CPU 的切换就需要应用改变,这种切换最好对应用来讲是透明的。这就分为协作式和抢占式,协作式需要主动调用 yield 来放弃 CPU ,抢占式通过时钟来被动切换进而达到对应用透明的目的。抢占式通过 CPU 的快速切换来让应用以为自己独占 CPU 。
应用程序在运行时被抽象成任务,那些 CPU 运行需要的信息保存在任务信息中。

任务信息

任务信息是 TaskControlBlock
 pub struct TaskControlBlock {      pub task_cx_ptr: usize,      pub task_status: TaskStatus,      pub memory_set: MemorySet,      pub trap_cx_ppn: PhysPageNum,      pub base_size: usize,  }
  • task_cx_ptr 是任务上下文的指针
  • task_status 是任务状态
  • memory_set 是读取 elf 获取的地址空间
  • trap_cx_ppn 是 trap 上下文的物理页
rust 通过操纵 TaskControlBlock 来间接操纵任务,我们从总体视角看看
  • 定时器每隔一段时间产生一个中断,进入 trap
  • trap.asm 在虚拟内存的跳板上,每个应用和内核都是相同的映射 stvec::write(TRAMPOLINE as usize, TrapMode::Direct); 通过这行代码,trap 的处理设定为从跳板的开头开始
  • 跳板的开头是 __alltraps ,保存寄存器后跳到 rust 代码的 trap_handler
   Trap::Interrupt(Interrupt::SupervisorTimer) => {              set_next_trigger();              suspend_current_and_run_next();         }
  • 分支切换到下个任务,suspend_current_and_run_next 调用 run_next_task 。
     fn run_next_task(&self) {          if let Some(next) = self.find_next_task() {              let mut inner = self.inner.borrow_mut();              let current = inner.current_task;              inner.tasks[next].task_status = TaskStatus::Running;              inner.current_task = next;              let current_task_cx_ptr2 = inner.tasks[current].get_task_cx_ptr2();              let next_task_cx_ptr2 = inner.tasks[next].get_task_cx_ptr2();              core::mem::drop(inner);              unsafe {                  __switch(current_task_cx_ptr2, next_task_cx_ptr2);             }         } else {              panic!("All applications completed!");         }     }
  • run_next_task 具体调用 __switch 切换任务
  • __switch 其实就是一个函数调用
    • 保存被调用寄存器
    • 切换 sp 和 ra 来达到切换应用到目的
  • 最后调用 trap_return 间接调用 __restore
再一个个的看,处理 trap 和上节相同,重点是如何切换任务,也就是 __switch