## OS课程 ucore_lab5实验报告
练习零:填写已有实验
vmm.c trap.c default_pmm.c pmm.c proc.c
将这几个文件复制过去
下面为补充的代码:
proc.c:
static struct proc_struct * alloc_proc(void) {
proc->state = PROC_UNINIT;
proc->pid = -1;
proc->runs = 0;
proc->kstack = 0;
proc->need_resched = NULL;
proc->parent = NULL;
proc->mm = NULL;
memset(&(proc->context), 0, sizeof(struct context));
proc->tf = NULL;
proc->cr3 = boot_cr3;
proc->flags = 0;
memset(&(proc->name), 0, PROC_NAME_LEN);
// 新添加: 初始化进程等待状态 初始化进程相关指针
proc->wait_state = 0;
proc->cptr = proc->yptr = proc-> optr = NULL;
| cptr: proc is parent |
| yptr: proc is younger sibling |
| optr: proc is older sibling |
}
int do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf) {
if ((proc = alloc_proc()) == NULL) {
goto fork_out;
}
proc->parent = current;
// 添加这行 确保 当前进程正在等待
assert(current->wait_state == 0);
if (setup_kstack(proc) != 0) {
goto bad_fork_cleanup_proc;
}
if (copy_mm(clone_flags, proc) != 0) {
goto bad_fork_cleanup_kstack;
}
copy_thread(proc, stack, tf);
bool intr_flag;
local_intr_save(intr_flag);
{
proc->pid = get_pid();
hash_proc(proc);
// 删除此行 nr_process++ 和 加入链表那行 添加下面那行;
// 将原来的简单 计数 改成设置进程的相关链接
set_links(proc);
}
local_intr_restore(intr_flag);
wakeup_proc(proc);
ret = proc->pid;
}
trap.c:
static void trap_dispatch(struct trapframe *tf) {
// 时间片用完 设置进程 为 需要被调度
if (++ticks % TICK_NUM == 0) {
assert(current != NULL);
current->need_resched = 1;
}
}
void idt_init(void) {
int i;
for (i = 0; i < sizeof(idt) / sizeof(struct gatedesc); i++) {
SETGATE(idt[i], 0, GD_KTEXT, __vectors[i], DPL_KERNEL);
}
// 添加下面这行
// 设置给用户态用的中断门 让用户态能够进行系统调用
SETGATE(idt[T_SYSCALL], 1, GD_KTEXT, __vectors[T_SYSCALL], DPL_USER);
lidt(&idt_pd);
}
练习1: 加载应用程序并执行(需要编码)
首先清空进程原先的中断帧;
然后再将中断帧中的代码段和数据段修改为用户态的段选择子,栈指针设置为用户栈顶 ,eip设置为用户程序的入口地址;
最后,确保在用户进程中能够响应中断
static int load_icode(unsigned char *binary, size_t size) {
tf->tf_cs = USER_CS;
tf->tf_ds = tf->tf_es = tf->tf_ss = USER_DS;
tf->tf_esp = USTACKTOP;
tf->tf_eip = elf->e_entry;
tf->tf_eflags = FL_IF;
}
A:
创建一个用户态进程并加载了应用程序之后,调度器 schedule 调用 proc_ru;
设置指针 current 为当前执行的 PCB 并加载该进程的内核栈和页目录表;
调用 switch_to 因为当前进程的 context 其中的 eip 被设置为 forkret, 因此 switch_to ret 后会跳转到 forkret 处, forkret 又会将栈设置为当前进程的trapframe 然后跳到 _trapret, 此时 _trapret 会根据当前进程的trapframe 恢复上下文
最后,退出中断 iret 从系统调用的函数调用路径, 返回切换到用户进程 hello 第一句语句 _start 处开始执行
练习2: 父进程复制自己的内存空间给子进程(需要编码)
int copy_range(pde_t *to, pde_t *from, uintptr_t start, uintptr_t end, bool share) {
// 找到父进程的页虚拟内存地址和子进程的页虚拟内存地址,将父进程的页拷贝到子进程的页
void* src_kvaddr = page2kva(page);
void* dst_kvaddr = page2kva(npage);
memcpy(dst_kvaddr, src_kvaddr, PGSIZE);
ret = page_insert(to, npage, start, perm);
}
练习3: 阅读分析源代码,理解进程执行 fork/exec/wait/exit 的实现,以及系统调用的实现(不需要编码)
fork 创建新的 PCB,进程状态为 UNINIT
exec 将当前进程的内存布局清除,再调用 load_icode 读出ELF映像中的内存布局并填写,进程状态不改变
wait 当前进程若无子进程,则返回错误;若有子进程,则判定是否为 ZOMBIE 子进程,有则释放子进程的资源,并返回子进程的返回状态码; 若无 ZOMBIE 状态子进程, 则进入 SLEEPING 状态,等子进程唤醒
exit 清除当前进程几乎所有资源(PCB和内核栈不清除), 将所有子进程(如果有的话)设置为 init 进程(内核), 将当前进程状态设置为 ZOMBIE; 若有父进程在等待当前进程
RUNNING----------------+
jiantou | |
| | |
proc_run() exit()
| | |
| ↓ ↓
--alloc_page()--> UNINIT --wakeup_proc()--> RUNNABLE --exit()--> ZOMBIE
↑ ↑
| |
子进程exit() exit()
| |
| |
SLEEPING----------------+
结果:
参考链接:
[1].https://yuerer.com/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-uCore-Lab-5/