在用户态应用程序看来,内核提供了一系列功能强大的 API(系统调用)。本节将分析这些系统调用是如何从用户态传递到内核,并在内核中完成进程管理的。
系统调用的基本流程:
ecall (User) -> trap_handler (Kernel) -> syscall(...) -> sys_* -> task management -> schedule
此处介绍几个典型的对进程的操作:
TaskControlBlock::fork 就是创建另一个 TaskControlBlock, 而这个子进程和父进程拥有初始一致但独立的地址空间和文件描述符表,当然 pid 必然不同。
设计的难点在于 MemorySet::from_existed_user, 可以理解为 MemorySet 的 clone 方法,这复制一个完全相同的地址空间,通过对 MapArea 下 vpn_range 对应 ppn 数据的修改,将原地址空间的数据复制到另一个地址空间中. sys_fork 在创建子进程时唯一的区别是,子进程的返回值为 0. 因此需要取 trap_cx 并将 a0 寄存器赋 0.
impl MemorySet {
pub fn from_existed_user(user_space: &Self) -> Self {
let mut memory_set = Self::new_bare();
// map trampoline
memory_set.map_trampoline();
// copy data sections/trap_context/user_stack
for area in user_space.areas.iter() {
let new_area = MapArea::from_another(area);
memory_set.push(new_area, None);
// copy data from another space
for vpn in area.vpn_range {
let src_ppn = user_space.translate(vpn).unwrap().ppn();
let dst_ppn = memory_set.translate(vpn).unwrap().ppn();
dst_ppn
.get_bytes_array()
.copy_from_slice(src_ppn.get_bytes_array());
}
}
memory_set
}
}
TaskControlBlock::exec 加载一个新的 ELF 文件并替换原有应用地址空间的内容. sys_exec 先通过 translated_str 将该地址所对应的内容以 char 类型翻译后传回 String,然后返回 ELF 格式的数据并执行 exec 替换当前进程的应用地址空间.
pub fn sys_exec(path: *const u8) -> isize {
trace!("kernel:pid[{}] sys_exec", current_task().unwrap().pid.0);
let token = current_user_token();
let path = translated_str(token, path);
if let Some(data) = get_app_data_by_name(path.as_str()) {
let task = current_task().unwrap();
task.exec(data);
0
} else {
-1
}
}
由于执行 sys_exec 时原有的地址空间会被销毁,各物理页帧都会被回收,为了应对失效的上下文 cx, 在 TaskControlBlock::exec 中需要对 trap_handler 重建 cx.
/// Load a new elf to replace the original application address space and start execution
pub fn exec(&self, elf_data: &[u8]) {
let (memory_set, user_sp, entry_point) = MemorySet::from_elf(elf_data);
let trap_cx_ppn = memory_set
.translate(VirtAddr::from(TRAP_CONTEXT_BASE).into())
.unwrap()
.ppn();
let mut inner = self.inner_exclusive_access();
...
// initialize trap_cx
let trap_cx = inner.get_trap_cx();
*trap_cx = TrapContext::app_init_context(
entry_point,
user_sp,
KERNEL_SPACE.exclusive_access().token(),
self.kernel_stack.get_top(),
trap_handler as usize,
);
}
而 sys_waitpid 寻找当前进程子进程中符合 pid 且为僵尸进程的进程,将其回收,附带有对 exit_code 的处理和对旧 pid 的返回.