MIT OS LAB3 pA

mit os lab3 pA

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

Our struct Env is analogous to struct proc in xv6. Both structures hold the environment's (i.e., process's) user-mode register state in a Trapframe structure. In JOS, individual environments do not have their own kernel stacks as processes do in xv6. There can be only one JOS environment active in the kernel at a time, so JOS needs only a single kernel stack.

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.

    //////////////////////////////////////////////////////////////////////
    // Make 'envs' point to an array of size 'NENV' of 'struct Env'.
    // LAB 3: Your code here.
    envs = (struct Env *) boot_alloc(NENV * sizeof(struct Env));
    memset(envs, 0, NENV * sizeof(struct Env));
    //....
    // 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, PTSIZE, PADDR(envs), PTE_U);

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

// 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.
        struct Env* env_p = envs + NENV - 1;
        int i;
        for(i = NENV; i > 0; i--, env_p--) {
                env_p->env_id = 0;
                env_p->env_link = env_free_list;
                env_free_list = env_p;
        }
        // Per-CPU part of the initialization
        env_init_percpu();
}

这个简单,只需要从envs末尾接入env_free_list就能满足要求。

env_setup_vm()
Allocate a page directory for a new environment and initialize the kernel portion of the new environment's address space.

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.
        p->pp_ref++;
        pte_t* pgdir = (pte_t*)page2kva(p);
        e->env_pgdir = pgdir;
        memcpy(pgdir + PDX(UTOP), kern_pgdir + PDX(UTOP), (NPDENTRIES - PDX(UTOP)) * 4);

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

这里我的理解是将目标PDX(va)后的页表项pde直接拷贝至env对象的页表。

region_alloc()
Allocates and maps physical memory for an environment

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!)
        uintptr_t vab = ROUNDDOWN((uintptr_t)va, PGSIZE);
        uintptr_t vae = ROUNDUP((uintptr_t)va + len, PGSIZE);
        size_t pg_n = (vae - vab) / PGSIZE;
        int i; 
        struct PageInfo* pp; 
        for(i = 0; i < pg_n; i++, vab += PGSIZE) {
                pp = page_alloc(1);
                if(pp == NULL)
                        panic("region_alloc failed!!\n");
                if(page_insert(e->env_pgdir, pp, (void*)vab, PTE_W | PTE_U | PTE_P) < 0||page_insert(kern_pgdir, pp, (void*)vab, PTE_W | PTE_U | PTE_P) < 0)
                        panic("region_alloc failed!!\n");
        }
}

实现这里的时候我有很多疑问,我也不知道这么实现符不符合要求,但这样写可以让程序正常运行。
首先在第一次写执行插入页的操作时我只把物理页插入了e对象的页表中而没有插入到内核页表kern_pgdir中,写这儿的时候我就在想只插入env对象的页表会不会出问题。果不其然在运行时出现了地址错误,具体定位到load_icode函数中的memcpy操作。如果不把新分配的物理页映射到kern_pgdir中,那么在kern_pgdir中是不存在对(pgh->pgh_va)的映射,直接访问会让系统重启.我在这里想到的解决方法就是把映射同样添加到kern_pgdir中。但这题的注释完全没有提到,只是说将其映射添加到环境地址空间中,我也不知道这样做会不会在后续的Lab中产生问题。


在参考了其他人的报告后,我发现这里可以直接使用lcr3()加载指定页表进行mencpy后再用lcr3()加载回kern_pgdir,唉,简单的弯没转过来。

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.

static void
load_icode(struct Env *e, uint8_t *binary)
{
        // LAB 3: Your code here.
        struct Elf* elf_ = (struct Elf*)binary;
        size_t ph_num = elf_->e_phnum;
        struct Proghdr* pgh = (struct Proghdr*)((uint32_t)elf_ + elf_->e_phoff);
        int i;
        for(i = 0; i < ph_num; i++,pgh++) {
                if(pgh->p_type == ELF_PROG_LOAD) {
                        region_alloc(e, (void*)pgh->p_va, pgh->p_filesz);
                        memcpy((void*)pgh->p_va, (void*)((uint32_t)binary+pgh->p_offset), pgh->p_memsz);

                }
        }
        e->env_tf.tf_eip = elf_->e_entry;

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

在了解elf文件格式的情况下实现起来还是挺简单的,就是需要注意拷贝前要在kern_pgdir添加映射(见上)。

env_create()
Allocate an environment with env_alloc and call load_icode to load an ELF binary into it.

void
env_create(uint8_t *binary, enum EnvType type)
{
        // LAB 3: Your code here.
        struct Env* ept;
        if(env_alloc(&ept, 0) < 0)
                panic("env_alloc failed!!!");
        ept->env_type = type;
        load_icode(ept, binary);
        //cprintf("the first env was built\n");
}

env_run()
Start a given environment running in user mode.

void
env_run(struct Env *e)
{
        //cprintf("now run env, id: %x\n", e->env_id);

        if(curenv != NULL) {
                if(curenv->env_status == ENV_RUNNING) {
                        curenv->env_status = ENV_RUNNABLE;
                }
                else if(curenv->env_status == ENV_FREE) {
                        env_free(curenv);
                }
        }
        curenv = e;
        curenv->env_status = ENV_RUNNING;
        curenv->env_runs++;
        lcr3(PADDR(curenv->env_pgdir));
        env_pop_tf(&curenv->env_tf);
        //panic("env_run not yet implemented");
}

env_run函数以用户模式进入目标环境。lcr3函数将参数添加到cr3寄存器中以进入环境目标的地址空间。具体一些就是程序在访问地址时参照的页表从kern_pgdir(或是curenv->pgdir)切换到了要进入的环境的目标页表。再通过env_pop_tf函数将寄存器设置成目标环境参数来切换上下文。

Interrupts that vector through either interrupt gates or trap gates cause TF (the trap flag) to be reset after the current value of TF is saved on the stack as part of EFLAGS. By this action the processor prevents debugging activity that uses single-stepping from affecting interrupt response. A subsequent IRET instruction restores TF to the value in the EFLAGS image on the stack.
The difference between an interrupt gate and a trap gate is in the effect on IF (the interrupt-enable flag). An interrupt that vectors through an interrupt gate resets IF, thereby preventing other interrupts from interfering with the current interrupt handler. A subsequent IRET instruction restores IF to the value in the EFLAGS image on the stack. An interrupt through a trap gate does not change IF

interrupt gate 和 trap gate 最本质的区别在于对IF的影响。interrupt gate 通过置位IF来阻止其他的中断再次进入该处理函数的接口。

Exercise.4 && challenge!

Edit trapentry.S and trap.c and implement the features described above. The macros TRAPHANDLER and TRAPHANDLER_NOEC in trapentry.S should help you, as well as the T_ defines in inc/trap.h. You will need to add an entry point in trapentry.S (using those macros) for each trap defined in inc/trap.h, and you'll have to provide _alltraps which the TRAPHANDLER macros refer to. You will also need to modify trap_init() to initialize the idt to point to each of these entry points defined in trapentry.S; the SETGATE macro will be helpful here.*
Challenge! You probably have a lot of very similar code right now, between the lists of TRAPHANDLER in trapentry.S and their installations in trap.c. Clean this up. Change the macros in trapentry.S to automatically generate a table for trap.c to use. Note that you can switch between laying down code and data in the assembler by using the directives .text and .data.
写到这里不知道哪里出了错误,在我觉得写得没什么问题的时候一直跑不通。比如在测试除零错误时,执行完div指令后,系统直接跳到了Bios Rom里陷入死循环,努力好久到最后都没有解决,最后只好借鉴了大佬的想法。
首先创建kern/trapentry.inc文件
TH即不会产生error code,THE则会。后面接的数字则代表第n号中断。

TH(0)
TH(1)
TH(2)
TH(3)
TH(4)
TH(5)
TH(6)
TH(7)
THE(8)
THE(10)
THE(11)
THE(12)
THE(13)
THE(14)
TH(16)
THE(17)
TH(18)
TH(19)
TH(48)

trapentry.c:

#define TH(n) \
TRAPHANDLER_NOEC(handler##n, n)

#define THE(n) \
TRAPHANDLER(handler##n, n)

.text

/*
 * Lab 3: Your code here for generating entry points for the different traps.
 */
#include <kern/trapentry.inc>

定义TH和THE,构成各个中断例程。
_alltraps

_alltraps:
    pushl %ds 
    pushl %es 
    pushal 

    movw $GD_KD, %ax
    movw %ax, %ds 
    movw %ax, %es

    pushl %esp 
    call trap 
trap_spin:
    jmp trap_spin

按照提示写出每一步即可。

trap.c

#define TH(n) extern void handler##n (void);
#define THE(n) TH(n)

#include <kern/trapentry.inc>

#undef THE
#undef TH

#define TH(n) [n] = handler##n,
#define THE(n) TH(n)

static void (* handlers[256])(void) = {
#include <kern/trapentry.inc>
};

#undef THE
#undef TH

这里我还学习了一下define的特殊用法,推荐这篇c define用法总结

trap_init

void
trap_init(void)
{
    extern struct Segdesc gdt[];
    
    // LAB 3: Your code here.
    for (int i = 0; i < 32; ++i) 
        SETGATE(idt[i], 0, GD_KT, handlers[i], 0);
    SETGATE(idt[T_BRKPT], 0, GD_KT, handlers[T_BRKPT], 3);
    SETGATE(idt[T_SYSCALL], 0, GD_KT, handlers[T_SYSCALL], 3);
    // Per-CPU setup 
    trap_init_percpu();
}

Questions

  • What is the purpose of having an individual handler function for each exception/interrupt? (i.e., if all exceptions/interrupts were delivered to the same handler, what feature that exists in the current implementation could not be provided?)*

不同的中断处理不一样,部分需要error_code而部分不需要。如果采用同一个handler会让原来可以对不同的处理产生结构相同的trapframe的特点消失。

  • Did you have to do anything to make the user/softint program behave correctly? The grade script expects it to produce a general protection fault (trap 13), but softint's code says int 14. Why should this produce interrupt vector 13? What happens if the kernel actually allows softint's int 14 instruction to invoke the kernel's page fault handler (which is interrupt vector 14)?*

在用户环境下执行int $14指令发生了越级行为。14号中断对应缺页中断,其DPL为0.因此产生了保护中断,及对应的13号中断。如果内核允许用户态通过int指令触发缺页异常将产生未知行为。

PART A END

太难了。。。痛并快乐

上一篇:springboot 引用 jar 包分离部署


下一篇:【go】Unmarshal时候报错提示proto.Unmarshal: missing method ProtoReflect