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
。