rCore第五章
在这一章中将进一步解释进程的动态的概念,提供创建、销毁、等待、信息等系统调用,并开发一个shell作为user_app之一。我们会重新设计TaskControlBlock的结构,来满足我们对于进程创建,销毁的一系列需求。
进程ID
进程ID的分配采用了和前面一样的栈式分配,不断向后移动current标志,表示最大已分配id号。如果要进行回收,则直接回收到recycled中,同时如果recycled中如果存在已经回收的id,则不会向后移动current。最后在这个模块还要说明如何获取kernel stack,和之前保持一致,不过app_id换成了pid。
进程控制块及其操作
对于进程控制块,分成初始化后不变和可变的两部分。结构如下:
pub struct TaskControlBlock {
pub pid: PidHandle,
pub kernel_stack: KernelStack,
inner UPSafeCell<TaskControlBlockInner>,
}
pub struct TaskControlBlockInner {
pub trap_cx_ppn: PhysPageNum,
pub base_size: usize,
pub task_cx: TaskContext,
pub task_status: TaskStatus,
pub memory_set: MemorySet,
pub parent: Option<Weak<TaskControlBlock>>,
pub children: Vec<Arc<TaskControlBlock>>,
pub exit_code: i32,
}
new方法增加了pid的申请。同时增加了fork和exec方法,用于拷贝和替换上下文,达到再创建一个进程和执行一个程序的效果。
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).into())
.unwrap()
.ppn();
let mut inner = self.inner_exclusive_access();
inner.memory_set = memory_set;
inner.trap_cx_ppn = trap_cx_ppn;
inner.base_size = user_sp;
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,
);
}
这里直接将trap上下文和memory_set即映射空间进行替换,这样返回时会直接restore我们刚刚放入的上下文,达到执行一个程序的目的。
pub fn fork(self: &Arc<Self>) -> Arc<Self> {
let mut parrent_inner = self.inner_exclusive_access();
let memory_set = MemorySet::from_existed_user(&parent_inner.memory_set);
let trap_cx_ppn = memory_set
.translate(VirtAddr::from(TRAP_CONTEXT).into())
.unwrap()
.ppn()
let pid_handle = pid_alloc();
let kernel_stack = KernelStack::new(&pid_handle);
let kernel_stack_top = kernel_stack.get_top();
let task_control_block = Arc::new(TaskControlBlock{
pid: pid_handle,
kernel_stack,
inner: unsafe {
UPSafeCell::new(TaskControlBlockInner {
trap_cx_ppn,
base_size: parent_inner.base_size,
task_cx: TaskContext::goto_trap_return(kernel_stack_top),
task_status: TaskStatus::Ready,
memory_set,
parent: Some(Arc::downgrade(self)),
children: Vec::new(),
exit_code: 0,
})
},
});
parent_inner.children.push(task_control_block.clone());
let trap_cx = task_control_block.inner_exclusive_access().get_trap_cx();
trap_cx.kernel_sp = kernel_stack_top;
task_control_block
}
这里调用了from_existed_user函数对于父进程的地址空间进行拷贝,同时获取新获取空间的root_ppn分配的物理帧,非陪内核占空间,重新申请一个task_control_block。同时设置父子进程关系,更换fork生成的trap上下文中的内核栈指针位置,这个指针的位置应该由新生成的pid决定。
数据结构关系

如上图所示,这是进程调度时的框架图,处理结构体内部只会维护一个idle_task_cx和current。idle_task_cx会不断循环让后只是调度下一个程序,schedule函数本身运行在操作系统的启动栈,切换的进程各自运行在自己的内核栈,进行切换时的调度对于两个进程的trap执行流就是不可知的。处理器结构体的任务就是维护当前正在运行的任务,
TaskManager内部就是一个VecDeque一个双向队列,可以从末尾加入一个进程控制块,从队首去取出进程控制块。
运行过程

这里起始于右上角的find_app_data_by_name,这个函数会根据app的名字来获取app在整个存储队列中的位置,TaskControlBlock的new方法中会调用from_elf解析数据,生成控制块后将这个结构压入Manager的队列。processor中会调度schedule启动这个进程控制块,执行initproc后,会执行fork系统调用,根据from_existed_user拷贝一份地址映射,并生成新的进程控制块,这个控制块也会被加入队列。initproc返回后会执行yield,这是idle会被用于调度下一个进程,user_shell会被调度执行,get_char会不断获取字符,如果已经键入一个命令,会先进行一次fork,根据结果,主进程进行wait,子进程执行结束后将状态设置为zombie,并设置exit_code。wait系统调用在检查到子进程执行完毕后,会结束loop。