6.828 Lab 3 User Environments 报告 (待机中)

正文

Introduction

Getting Started

$ git pull
Already up-to-date.
$ git checkout -b lab3 origin/lab3
Branch lab3 set up to track remote branch lab3 from origin.
Switched to a new branch 'lab3'
$ git merge lab2
Auto-merging kern/pmap.c
Auto-merging kern/monitor.c
Auto-merging kern/kdebug.c
Merge made by the 'recursive' strategy.
 kern/console.c |   2 +-
 kern/kdebug.c  |   8 +++--
 kern/monitor.c |  22 ++++++++++++-
 kern/pmap.c    | 196 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
 lib/printfmt.c |   7 ++--
 5 files changed, 214 insertions(+), 21 deletions(-)

Lab Requirements

要求同 lab 2。

Inline Assembly

在这个 lab 中,GCC 的内嵌汇编可能会比较好使,但是也可以不用。

Part A. User Environments and Exception Handling

inc/env.h:

/* See COPYRIGHT for copyright information. */

#ifndef JOS_INC_ENV_H
#define JOS_INC_ENV_H

#include <inc/types.h>
#include <inc/trap.h>
#include <inc/memlayout.h>

typedef int32_t envid_t;

// An environment ID 'envid_t' has three parts:
//
// +1+---------------21-----------------+--------10--------+
// |0|          Uniqueifier             |   Environment    |
// | |                                  |      Index       |
// +------------------------------------+------------------+
//                                       \--- ENVX(eid) --/
//
// The environment index ENVX(eid) equals the environment's index in the
// 'envs[]' array.  The uniqueifier distinguishes environments that were
// created at different times, but share the same environment index.
//
// All real environments are greater than 0 (so the sign bit is zero).
// envid_ts less than 0 signify errors.  The envid_t == 0 is special, and
// stands for the current environment.

#define LOG2NENV                10
#define NENV                    (1 << LOG2NENV)
#define ENVX(envid)             ((envid) & (NENV - 1))

// Values of env_status in struct Env
enum {
        ENV_FREE = 0,
        ENV_DYING,
        ENV_RUNNABLE,
        ENV_RUNNING,
        ENV_NOT_RUNNABLE
};

// Special environment types
enum EnvType {
        ENV_TYPE_USER = 0,
};

struct Env {
        struct Trapframe env_tf;        // Saved registers
        struct Env *env_link;           // Next free Env
        envid_t env_id;                 // Unique environment identifier
        envid_t env_parent_id;          // env_id of this env's parent
        enum EnvType env_type;          // Indicates special system environments
        unsigned env_status;            // Status of the environment
        uint32_t env_runs;              // Number of times environment has run

        // Address space
        pde_t *env_pgdir;               // Kernel virtual address of page dir

};
#endif // !JOS_INC_ENV_H

观察一下可以发现 Environmrnt Index 的取值范围是 $[0,2^{10})$. 这也就是 envs[] 数组的索引范围。
Uniqueifier 唯一符可以在不同次创建时区分环境。
所有的实环境的的符号位都是 0, 即大于 0.

接着是两个枚举类型。一个是表示 struct Env 中的 env_status 域,分别为空闲、死亡、可运行、正在运行、不可运行;第二个用来指示特殊系统环境,对应 struct Env 中的 env_type 域。

In kern/env.c:

struct Env *envs = NULL;                // All environments
struct Env *curenv = NULL;              // The current env
static struct Env *env_free_list;       // Free environment list
                                        // (linked by Env->env_link)

三个 struct Env 指针,第一个是所有环境,目测应该是指向所有环境首节点;第二个指向当前执行的环境(currently executing environment);第三个指向空闲环境的首节点。下面还有行注释,这些节点靠 struct Envenv_link 域链接。

inc/env.h 中定义的 NENV 是 JOS 中同时激活的环境数上限,跟我们上面的猜测是一样的。

Environment State

接着来看对 struct Env 域的介绍:
env_tf: 一个 struct Trapframe 对象,在 inc/trap.h 中定义。在环境未运行时为环境保存寄存器值。这是为了方便在不同环境之间跳转。
struct Trapframe 的定义:
In inc/trap.h:

struct Trapframe {
        struct PushRegs tf_regs;
        uint16_t tf_es;
        uint16_t tf_padding1;
        uint16_t tf_ds;
        uint16_t tf_padding2;
        uint32_t tf_trapno;
        /* below here defined by x86 hardware */
        uint32_t tf_err;
        uintptr_t tf_eip;
        uint16_t tf_cs;
        uint16_t tf_padding3;
        uint32_t tf_eflags;
        /* below here only when crossing rings, such as from user to kernel */
        uintptr_t tf_esp;
        uint16_t tf_ss;
        uint16_t tf_padding4;
} __attribute__((packed));

env_link: 可用环境列表中指向下一项的指针;
env_id: 当前使用的 Env 结构体需要一个唯一的标识符。当用户环境终止之后,kernel 可能会将这个环境重新分配给另外一个环境,这时候只需要改变 env_id 就可以了;
env_parent_id: kernel 用来存储创造了当前环境的环境的 id,也就是它的父环境。使用这种方式可以构建一个“家族树”(类似于回溯路径),家族树算是一个管理权限的方式;
env_type: 用于区分特殊环境。对于大多数环境,该域的值为 ENV_TYPE_USER;
env_status: 表示状态:

env_pgdir: 存放环境页目录的 KVA.

Allocating the Environments Array

在 lab 2 中我们已经编写了 mem_init() 函数去完成 pages[] 数组的分配。增加代码,完成对 envs 的分配。
见 Exercise 1.

Creating and Running Environments

原文提示我们一些静态的二进制镜像嵌入到了内核里面,详见 GNUmakefilekern/Makefrag.
i386_init() 函数中新加入了 env_init()trap_init() 的调用。我们将在 Exercise 2 中实现 env 相关的一些代码。

用户环境代码被调用之前,系统按下列顺序调用了函数:

Exercise

Exercise 1

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.

In kern/pmap.c:

        //////////////////////////////////////////////////////////////////////
        // Make 'envs' point to an array of size 'NENV' of 'struct Env'.
        // LAB 3: Your code here.
        envs = (struct Env*)boot_alloc(sizeof(struct Env) * NENV);
        memset(envs, 0, sizeof(struct Env) * NENV);

        //////////////////////////////////////////////////////////////////////
        // Map the 'envs' array read-only by the user at linear address UENVS
        // (ie. perm = PTE_U | PTE_P).
        // Permissions:
        //    - the new image at UENVS  -- kernel R, user R
        //    - envs itself -- kernel RW, user NONE
        // LAB 3: Your code here.
        boot_map_region(kern_pgdir, UENVS, ROUNDUP(sizeof(struct Env) * NENV, PGSIZE), PADDR(envs), PTE_U);

Exercise 2

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”.

直接开始写代码:
In kern/env.c:

void
env_init(void)
{       
        // Set up envs array
        // LAB 3: Your code here.
        for (int i = 0; i < NENV; ++i) {
                if (i + 1 != NENV)
                        envs[i].env_link = &envs[i+1];
                else
                        envs[i].env_link = NULL;
                envs[i].env_id = 0;
                envs[i].env_status = ENV_FREE;
        }
        env_free_list = envs;
        // Per-CPU part of the initialization
        env_init_percpu();
}

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.
        e->env_pgdir = (pde_t*)page2kva(p);
        p->pp_ref++;

        for (int i = 0; i < PDX(UTOP); ++i)
                e->env_pgdir[i] = 0;

        for (int 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;
}

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.)
        //
        // 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!)
        void *begin = (void *)ROUNDDOWN((uintptr_t)va, PGSIZE);
        void *end = (void *)ROUNDUP((uintptr_t)va + len, PGSIZE);

        struct PageInfo *p = NULL;
        for (void *vai = begin; vai < end; vai += PGSIZE) {
                if ((p = page_alloc(0)) == NULL)
                        panic("In region_alloc: Page alloc failed.");
                if ((page_insert(e->env_pgdir, p, vai, PTE_W | PTE_U)) != 0) {
                        panic("In region_alloc: Page insert failed.");
                }
        }
}

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.
        // 这段代码就像 boot loader 做的那样
        // 我们需要一个 ELF 二进制镜像
        struct Elf *header = (struct Elf*)binary;

        // 然后就像 bootmain 里做的那样
        // 首先判断读取的 binary 是不是合法 ELF
        if (header->e_magic != ELF_MAGIC)
                panic("binary is not a valid ELF.");
        if (header->e_entry == 0)
                panic("binary read failed.");

        // 设置入口点
        e->env_tf.tf_eip = header->e_entry;
        // 加载用户环境页
        lcr3(PADDR(e->env_pgdir));

        // load each program segment
        struct Proghdr *ph, *eph;
        ph = (struct Proghdr*) ((uint8_t *) (header + header->e_phoff));
        eph = ph + header->e_phnum;
        for (; ph < eph; ++ph) {
                if (ph->p_type != ELF_PROG_LOAD)
                        continue;

                uintptr_t va = (uint32_t)binary + ph->p_offset;
                // Allocate ph->p_memsz bytes using the function we finished before
                region_alloc(e, (void *)ph->p_va, ph->p_memsz);
                memmove((void *)ph->p_va, binary + ph->p_offset, ph->p_filesz);
                memset((void *)(ph->p_va + ph->p_filesz), 0, ph->p_memsz - ph->p_filesz);
        }

        // 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);
}

void
env_create(uint8_t *binary, enum EnvType type)
{
        // LAB 3: Your code here.
        struct Env *new_env;
        if (env_alloc(&new_env, 0)) {
                panic("In env_create: env alloc failed");
        }
        load_icode(new_env, binary);
        new_env->env_type = type;
}

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.

        // Step 1
        // 1
        if (curenv != NULL && curenv->env_status == ENV_RUNNING)
                curenv->env_status = ENV_RUNNABLE;
        // 2
        curenv = e;
        // 3
        curenv->env_status = ENV_RUNNING;
        // 4
        ++curenv->env_runs;
        // 5
        lcr3(PADDR(curenv->env_pgdir));

        // Step 2
        env_pop_tf(&curenv->env_tf);

        panic("env_run not yet implemented");
}

未完成,先放着。

引用