MIT6.828LAB3 (1)

LAB3_Part A User Environments and Exception Handling


前言

记录一下自己的学习过程
实验内容翻译:
https://gitee.com/cherrydance/mit6.828
该翻译仅供参考

先得到lab3的代码:
在这里插入图片描述

练习1

请在kern/pmap.c中修改mem_init()函数,以分配并映射envs数组。这个数组包含了正好NENV个Env结构的实例,分配方式与你分配pages数组的方式非常相似。与pages数组类似,支持envs的内存也应该以只读方式映射到UENVS(在inc/memlayout.h中定义),这样用户进程就可以从这个数组中读取数据。
你应该运行你的代码,确保check_kern_pgdir()函数执行成功。

让我们分配并映射envs数组。这个lab2完成了就很简单,照着写就行了。代码如下:

	envs = (struct Env*) boot_alloc(NENV * sizeof(struct Env));
	memset(envs, 0, NENV * sizeof(struct Env));
	boot_map_region(kern_pgdir, UENVS, PTSIZE, PADDR(envs), PTE_U);

结果如图:
在这里插入图片描述
check_kern_pgdir() succeeded。

练习2

在env.c文件中,完成以下函数的编写:
env_init()
初始化envs数组中的所有Env结构,并将它们添加到env_free_list。还调用env_init_percpu函数,为特权级0(内核)和特权级3(用户)配置分段硬件。
env_setup_vm()
为新环境分配一个页目录,并初始化新环境地址空间的内核部分。
region_alloc()
为环境分配和映射物理内存。
load_icode()
你需要解析一个ELF二进制映像,类似于引导加载程序已经做的那样,并将其内容加载到新环境的用户地址空间中。
env_create()
使用env_alloc分配一个环境,并调用load_icode将ELF二进制文件加载到其中。
env_run()
以用户模式启动给定的环境。
在编写这些函数时,你可能会发现新的cprintf转义符%e很有用——它打印与错误代码相对应的描述。例如,
r = -E_NO_MEM;
panic(“env_alloc: %e”, r);
将会导致panic,并输出消息"env_alloc: out of memory"(内存不足)。

env_init()函数:
初始化envs数组的Env结构体并添加到env_free_list链表中。关键点在于第一个分配的env要是envs[0],其次就是考虑初始化需要初始化哪些参数。
代码如下:

	int i = NENV - 1;
	env_free_list = NULL;
	for(; i >= 0; --i){
		envs[i].env_id = 0;
		envs[i].env_status = ENV_FREE;
		envs[i].env_link = env_free_list;
		env_free_list = &envs[i];
	}

env_setup_vm()函数:
作用是为环境e分配一个页目录即为e->env_pgdir设置值,在提示中有说明需要将页面的pp_ref++,此外,也说明了可以使用kern_pgdir作为模板。

	++p->pp_ref;
	e->env_pgdir = (pde_t *)page2kva(p);
	for(i = 0; i < PDX(UTOP); ++i){//UTOP以下为空
		e->env_pgdir[i] = 0;
	}
	for(i = PDX(UTOP); i < NPDENTRIES; ++i){//以上赋值kern_pgdir
		e->env_pgdir[i] = kern_pgdir[i];
	}

region_alloc()函数:
为环境env分配len字节的物理内存,并将其映射到环境地址空间中的虚拟地址va处。
不以任何方式对映射的页面进行清零或初始化。页面应该由用户和内核可写。您应该将va向下取整,并将(va + len)向上取整。
代码如下:

	va = ROUNDDOWN(va, PGSIZE);//向下取整
	void *end = ROUNDUP(va + len, PGSIZE);//向上取整
	struct PageInfo *new_page = NULL;
	for(; va < end; va += PGSIZE){
		new_page = page_alloc(0);//分配一个页面
		if(!new_page){
			panic("no free page\n");
		}
		//把页面映射到va
		if(page_insert(e->env_pgdir, new_page, va, PTE_U | PTE_W) == -E_NO_MEM){
			panic("page insert fail in region_alloc\n");
		}
	}

load_icode()函数:
解析一个ELF二进制映像,类似于引导加载程序已经做的那样,并将其内容加载到新环境的用户地址空间中。该函数从ELF二进制映像中加载所有可加载的段到环境的用户内存中,从ELF程序头中指示的适当虚拟地址开始。同时,它会将程序头中标记为映射但实际上不在ELF文件中的部分清零 - 即程序的bss段。
关于bootmain函数的解析在前面已经做过了,这里就是仿照bootmain来写代码。
代码如下:

	struct Elf *elf = (struct Elf *)binary;//得到elf文件
	if(elf->e_magic != ELF_MAGIC){
		panic("illegal ELF format");
	}
	lcr3(PADDR(e->env_pgdir));
	//把地址空间转化成当前用户空间,这是在env_run()的第五条提示给出来的。
	struct Proghdr *ph = (struct Proghdr *)((uint8_t *)(elf) + elf->e_phoff);
	struct Proghdr *eph = ph + elf->e_phnum;
	for (; ph < eph; ++ph) {
		if (ph->p_type == ELF_PROG_LOAD) {//如果p_type==ELF_PROG_LOAD才加载
			region_alloc(e, (void *)ph->p_va, ph->p_memsz);
			memmove((void *)ph->p_pa, binary + ph->p_offset, ph->p_filesz); 
			memset((void *)(ph->p_pa + ph->p_filesz), 0, ph->p_memsz - ph-> p_filesz);
		} 
	}
	//tf_eip表示下一条指令的地址,根据bootmain在elf加载之后就是进入e_entry
	e->env_tf.tf_eip = elf->e_entry;
	lcr3(PADDR(kern_pgdir));
	// Now map one page for the program's initial stack
	// at virtual address USTACKTOP - PGSIZE.
	region_alloc(e, (void *)(USTACKTOP - PGSIZE), PGSIZE);

这个函数的实现有点不太明白,关键就是地址空间的切换。什么时候是内核空间,什么时候是用户空间,这点要搞明白。我目前的想法就是如果用到内核的页目录就是内核空间,要用到用户的页目录就是用户地址空间。不是很清楚。
env_create()函数
使用env_alloc分配一个环境,并调用load_icode将ELF二进制文件加载到其中。
首先得清楚env_alloc函数是将分配一个新环境初始化并存储到函数的第一个参数之中。
代码如下:

	struct Env *new_env = NULL;
	int flag = env_alloc(&new_env, 0);
	if(flag == -E_NO_FREE_ENV){
		panic("env_alloc: %e", flag);
	}
	if(flag == -E_NO_MEM){
		panic("env_alloc: %e", flag);
	}
	load_icode(new_env, binary);
	new_env->env_type = type;

env_run()
以用户模式启动给定的环境。代码如下:

	if(curenv && ENV_RUNNING == curenv->env_status){
		curenv->env_status = ENV_RUNNABLE;
	}
	curenv = e;
	curenv->env_status = ENV_RUNNING;
	++curenv->env_runs;
	lcr3(PADDR(e->env_pgdir));
	env_pop_tf(&curenv->env_tf);

以下是代码的调用图,展示了在调用用户代码之前的代码执行过程。确保你理解每个步骤的目的。
在这里插入图片描述
entry.S进行一些初始化操作跳转到c语言执行入口。cons_init初始化窗口,mem_init初始化内核内存进行分页,env_init初始化用户环境,trap_init初始化中断和异常处理。env_create创造一个用户环境,env_run执行用户环境。env_pop_tf是恢复环境的各种寄存器状态,退出内核执行某个环境的代码。

根据练习内容打开gdb,b env_pop_tf设置断点,运行到该函数,接着使用si运行到iret进入到用户模式。
在这里插入图片描述
找到要求的int $0x30指令,设置断点b *0x800bcd
在这里插入图片描述
运行到断点处,程序停在了该指令说明正确,如果无法执行到int指令,那么你的地址空间设置或程序加载代码可能存在问题,请返回并修复它们后再继续。
在这里插入图片描述

总结

完成了lab3 partA的前半部分,这部分主要是完成对用户环境操作的一些函数。

  • 20
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值