2022-3-17 MIT 6.828 Lab 3: User Environments | Part A: User Environments and Exception Handling

补充一个知识点:MMU (一)
从 lab2 切换到 lab3 的方法
在这里插入图片描述
Lab 3 是为了设置用户在运行的时候适应的上下文环境
Allocating the Environments Array

Like a Unix process, a JOS environment couples the concepts of “thread” and “address space”.

Exercise 1. Modify mem_init() in kern/pmap.c to allocate and map the envs array. This array consists of exactly NENV instances of the Env structure allocated much like how you allocated the pages array. Also like the pages array, the memory backing envs should also be mapped user read-only at UENVS (defined in inc/memlayout.h) so user processes can read from this array.

You should run your code and make sure check_kern_pgdir() succeeds.
这个直接照葫芦画瓢就行
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Creating and Running Environments
由于运行环境没有搭建好,因此内核需要设置成能够加载内核中的静态二进制文件。

Exercise 2. In the file env.c, finish coding the following functions:

env_init()
Initialize all of the Env structures in the envs array and add them to the env_free_list. Also calls env_init_percpu, which configures the segmentation hardware with separate segments for privilege level 0 (kernel) and privilege level 3 (user).
env_setup_vm()
Allocate a page directory for a new environment and initialize the kernel portion of the new environment’s address space.
region_alloc()
Allocates and maps physical memory for an environment
load_icode()
You will need to parse an ELF binary image, much like the boot loader already does, and load its contents into the user address space of a new environment.
env_create()
Allocate an environment with env_alloc and call load_icode to load an ELF binary into it.
env_run()
Start a given environment running in user mode.
As you write these functions, you might find the new cprintf verb %e useful – it prints a description corresponding to an error code. For example,

r = -E_NO_MEM;
panic("env_alloc: %e", r);

will panic with the message “env_alloc: out of memory”.

1.env_init()
初始化所有结构体。将 envs_id 字段设置为0,env 结构体的顺序是从头到尾,使用头插法就好了。

// Mark all environments in 'envs' as free, set their env_ids to 0,
// and insert them into the env_free_list.
// Make sure the environments are in the free list in the same order
// they are in the envs array (i.e., so that the first call to
// env_alloc() returns envs[0]).
//
void
env_init(void)
{
	// Set up envs array
	// LAB 3: Your code here.
	env_free_list = envs;
	envs->env_id = 0;
	for (int i = 1; i < NENV; i++) {
		(envs + (i - 1))->env_link = (envs + i);
		(envs + i)->env_id = 0;
	}
	// Per-CPU part of the initialization
	env_init_percpu();
}

2.env_setup_vm()
说实话我一直不清楚静态函数是干什么用的。是用来访问静态的成员变量吗?
内部函数(又称静态函数)
如果在一个源文件中定义的函数,只能被本文件中的函数调用,而不能被同一程序其它文件中的函数调用,这种函数称为内部函数。定义一个内部函数,只需在函数类型前再加一个“static”关键字即可,

话说回来,这个函数的作用是
每个进程都需要有自己的进程地址空间,地址空间分为用户空间和内核空间两部分。用户空间不需要初始化,地址空间需要进程初始化。

//
// Initialize the kernel virtual memory layout for environment e.
// Allocate a page directory, set e->env_pgdir accordingly,
// and initialize the kernel portion of the new environment's address space.
// Do NOT (yet) map anything into the user portion
// of the environment's virtual address space.
//
// Returns 0 on success, < 0 on error.  Errors include:
//	-E_NO_MEM if page directory or table could not be allocated.
//
static int
env_setup_vm(struct Env *e)
{
	int i;
	struct PageInfo *p = NULL;

	// Allocate a page for the page directory
	if (!(p = page_alloc(ALLOC_ZERO)))
		return -E_NO_MEM;

	// Now, set e->env_pgdir and initialize the page directory.
	//
	// Hint:
	//    - The VA space of all envs is identical above UTOP
	//	(except at UVPT, which we've set below).
	//	See inc/memlayout.h for permissions and layout.
	//	Can you use kern_pgdir as a template?  Hint: Yes.
	//	(Make sure you got the permissions right in Lab 2.)
	//    - The initial VA below UTOP is empty.
	//    - You do not need to make any more calls to page_alloc.
	//    - Note: In general, pp_ref is not maintained for
	//	physical pages mapped only above UTOP, but env_pgdir
	//	is an exception -- you need to increment env_pgdir's
	//	pp_ref for env_free to work correctly.
	//    - The functions in kern/pmap.h are handy.

	// LAB 3: Your code here.
	p->pp_ref++;
	e->env_pgdir = (pde_t *) page2kva(p);
	memcpy(e->env_pgdir, kern_pgdir, PGSIZE);
	// UVPT maps the env's own page table read-only.
	// Permissions: kernel R, user R
	e->env_pgdir[PDX(UVPT)] = PADDR(e->env_pgdir) | PTE_P | PTE_U;

	return 0;
}

按照上面的理解,觉得下面这个代码更加靠谱。

static int
env_setup_vm(struct Env *e)
{
    int i;
    struct PageInfo *p = NULL;

    // Allocate a page for the page directory
    if (!(p = page_alloc(ALLOC_ZERO)))
        return -E_NO_MEM;

    // LAB 3: Your code here.
    e->env_pgdir = (pde_t *)page2kva(p);
    p->pp_ref++;

    //Map the directory below UTOP.
    for(i = 0; i < PDX(UTOP); i++) {
        e->env_pgdir[i] = 0;
    }

    //Map the directory above UTOP
    for(i = PDX(UTOP); i < NPDENTRIES; i++) {
        e->env_pgdir[i] = kern_pgdir[i];
    }

    // UVPT maps the env's own page table read-only.
    // Permissions: kernel R, user R
    e->env_pgdir[PDX(UVPT)] = PADDR(e->env_pgdir) | PTE_P | PTE_U;

    return 0;
}

3、region_alloc()
为进程建立地址映射

//
// Allocate len bytes of physical memory for environment env,
// and map it at virtual address va in the environment's address space.
// Does not zero or otherwise initialize the mapped pages in any way.
// Pages should be writable by user and kernel.
// Panic if any allocation attempt fails.
//
static void
region_alloc(struct Env *e, void *va, size_t len)
{
	// LAB 3: Your code here.
	// (But only if you need it for load_icode.)
	void *start = ROUNDDOWN(va, PGSIZE);
	void *end = ROUNDUP(va + len, PGSIZE);
	struct PageInfo * pp;
	while (start < end) {
		pp = page_alloc(0);
		if (!pp) {
			cprintf("region_alloc: out of free memory.\n");
			return;
		}

		page_insert(e->env_pgdir, pp, start, PTE_U | PTE_W);
		start += PGSIZE;
	}
	// Hint: It is easier to use region_alloc if the caller can pass
	//   'va' and 'len' values that are not page-aligned.
	//   You should round va down, and round (va + len) up.
	//   (Watch out for corner-cases!)
}

4、load_icode()功能是为每一个用户进程设置它的初始代码区,堆栈以及处理器标识位。每个用户程序都是ELF文件,所以我们要解析该ELF文件。

//
// Set up the initial program binary, stack, and processor flags
// for a user process.
// This function is ONLY called during kernel initialization,
// before running the first user-mode environment.
//
// This function loads all loadable segments from the ELF binary image
// into the environment's user memory, starting at the appropriate
// virtual addresses indicated in the ELF program header.
// At the same time it clears to zero any portions of these segments
// that are marked in the program header as being mapped
// but not actually present in the ELF file - i.e., the program's bss section.
//
// All this is very similar to what our boot loader does, except the boot
// loader also needs to read the code from disk.  Take a look at
// boot/main.c to get ideas.
//
// Finally, this function maps one page for the program's initial stack.
//
// load_icode panics if it encounters problems.
//  - How might load_icode fail?  What might be wrong with the given input?
//
static void
load_icode(struct Env *e, uint8_t *binary)
{
	// Hints:
	//  Load each program segment into virtual memory
	//  at the address specified in the ELF segment header.
	//  You should only load segments with ph->p_type == ELF_PROG_LOAD.
	//  Each segment's virtual address can be found in ph->p_va
	//  and its size in memory can be found in ph->p_memsz.
	//  The ph->p_filesz bytes from the ELF binary, starting at
	//  'binary + ph->p_offset', should be copied to virtual address
	//  ph->p_va.  Any remaining memory bytes should be cleared to zero.
	//  (The ELF header should have ph->p_filesz <= ph->p_memsz.)
	//  Use functions from the previous lab to allocate and map pages.
	//
	//  All page protection bits should be user read/write for now.
	//  ELF segments are not necessarily page-aligned, but you can
	//  assume for this function that no two segments will touch
	//  the same virtual page.
	//
	//  You may find a function like region_alloc useful.
	//
	//  Loading the segments is much simpler if you can move data
	//  directly into the virtual addresses stored in the ELF binary.
	//  So which page directory should be in force during
	//  this function?
	//
	//  You must also do something with the program's entry point,
	//  to make sure that the environment starts executing there.
	//  What?  (See env_run() and env_pop_tf() below.)

	// LAB 3: Your code here.
	struct Elf *Elf = (struct Elf *) binary;
    struct Proghdr *ph;             //Program Header
    int ph_num;                     //Program entry number
    if (Elf->e_magic != ELF_MAGIC) {
        panic("binary is not ELF format\n");
    }
    ph = (struct Proghdr *) (binary+ Elf->e_phoff);
    //e_phoff, (32位4字节,64位8字节),program header table的offset,如果文件没有PH,这个值是0。
    ph_num = Elf->e_phnum;
//e_phnum, 2字节。如果文件没有program header table, e_phnum的值为0。e_phentsize乘以e_phnum就得到了整个program header table的大小。
    lcr3(PADDR(e->env_pgdir)); 
    //转换cr3寄存器里面的内容。cr3寄存器里面放置的是当前任务的页表位置的。这个就是将寄存器的内容更换成为进程自个儿的页表。
    for (int i = 0; i < ph_num; i++) {
        if (ph[i].p_type == ELF_PROG_LOAD) { 
        //需要加载每一个被标记为ELF_PROG_LOAD的program header对应的segment
        //这个有英语提示
            region_alloc(e, (void *)ph[i].p_va, ph[i].p_memsz);
			memset((void *)ph[i].p_va, 0, ph[i].p_memsz); // 鍒濆鍖?
            memcpy((void *)ph[i].p_va, binary + ph[i].p_offset, ph[i].p_filesz); 
        }
    }

    lcr3(PADDR(kern_pgdir));
    //恢复cr3寄存器里面的内容,让页表重新指向内核页目录的页表
    e->env_tf.tf_eip = Elf->e_entry;
//e_entry,(32位4字节,64位8字节),执行入口点,如果文件没有入口点,这个域保持0。设置environment执行入口是在e->env_tf.tf_eip中
	// Now map one page for the program's initial stack
	// at virtual address USTACKTOP - PGSIZE.

	// LAB 3: Your code here.
	region_alloc(e, (void *) (USTACKTOP - PGSIZE), PGSIZE);
}

5.env_create()

//
// Allocates a new env with env_alloc, loads the named elf
// binary into it with load_icode, and sets its env_type.
// This function is ONLY called during kernel initialization,
// before running the first user-mode environment.
// The new env's parent ID is set to 0.
//
void
env_create(uint8_t *binary, enum EnvType type)
{
	// LAB 3: Your code here.
	struct Env *env;
	if (env_alloc(&env, 0) != 0) {
		cprintf("env_create: error create new env.\n");
		return;
	}

	env->env_type = type;
	load_icode(env, binary);

	// If this is the file server (type == ENV_TYPE_FS) give it I/O privileges.
	// LAB 5: Your code here.
	if (type == ENV_TYPE_FS) {
		env->env_tf.tf_eflags |= FL_IOPL_MASK;
	}
}

6.env_run()

//
// Context switch from curenv to env e.
// Note: if this is the first call to env_run, curenv is NULL.
//
// This function does not return.
//
void
env_run(struct Env *e)
{
	// Step 1: If this is a context switch (a new environment is running):
	//	   1. Set the current environment (if any) back to
	//	      ENV_RUNNABLE if it is ENV_RUNNING (think about
	//	      what other states it can be in),
	//	   2. Set 'curenv' to the new environment,
	//	   3. Set its status to ENV_RUNNING,
	//	   4. Update its 'env_runs' counter,
	//	   5. Use lcr3() to switch to its address space.
	// Step 2: Use env_pop_tf() to restore the environment's
	//	   registers and drop into user mode in the
	//	   environment.

	// Hint: This function loads the new environment's state from
	//	e->env_tf.  Go back through the code you wrote above
	//	and make sure you have set the relevant parts of
	//	e->env_tf to sensible values.

	// LAB 3: Your code here.
	if (curenv && curenv->env_status == ENV_RUNNING) {
		curenv->env_status = ENV_RUNNABLE;
	}

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

全部完成后发现其不断的重写,刷新,为什么会这样?
在这里插入图片描述
We’ll address this problem shortly, but for now we can use the debugger to check that we’re entering user mode. Use make qemu-gdb and set a GDB breakpoint at env_pop_tf, which should be the last function you hit before actually entering user mode. Single step through this function using si; the processor should enter user mode after the iret instruction. You should then see the first instruction in the user environment’s executable, which is the cmpl instruction at the label start in lib/entry.S. Now use b *0x… to set a breakpoint at the int $0x30 in sys_cputs() in hello (see obj/user/hello.asm for the user-space address). This int is the system call to display a character to the console. If you cannot execute as far as the int, then something is wrong with your address space setup or program loading code; go back and fix it before continuing.

break + 函数名称,设置对应函数为断点
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
查看 lib /entry.S的源码可知,两条蓝色线段之间执行的便是这个文件。
现在对开机启动后的各种函数调用关系十分迷糊。
昨天的函数调用后不断的开机重启的原因也不清楚。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值