MIT_6.828_Lab_3: User Environments Part A

项目地址:Lab 3

Environment State

在文件inc/env.h文件中,定义了环境(进程)结构体:

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

不同于xv6,JOS里面的每个环境都有自己的页目录,内核也有自己的页目录。

Allocating the Environments Array

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.

有了Lab 2的积累之后,做这道题会轻松很多。
首先在内核空间里面声明env数组:

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

接着对其进行映射即可:

	// LAB 3: Your code here.
	boot_map_region(kern_pgdir,
					UENVS,
					PTSIZE,
					PADDR(envs),
					PTE_U);

之后进行make;make qemu,如果从Lab 2继承下来,会出现kernel panic at kern/pmap.c:152: PADDR called with invalid kva 00000000的错误!
究其原因,链接器提供的end变量并没有指向数据区域的最后,而是指向数据区域内。

错误分析

使用objdump -h obj/kern/kernel,得到如下信息:

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00005379  f0100000  00100000  00001000  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .rodata       000016b0  f0105380  00105380  00006380  2**5
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .stab         000088c9  f0106a30  00106a30  00007a30  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .stabstr      00002a60  f010f2f9  0010f2f9  000102f9  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .data         0007a014  f0112000  00112000  00013000  2**12
                  CONTENTS, ALLOC, LOAD, DATA
  5 .got          0000000c  f018c014  0018c014  0008d014  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  6 .got.plt      0000000c  f018c020  0018c020  0008d020  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  7 .data.rel.local 0000100e  f018d000  0018d000  0008e000  2**12
                  CONTENTS, ALLOC, LOAD, DATA
  8 .data.rel.ro.local 000000cc  f018e020  0018e020  0008f020  2**5
                  CONTENTS, ALLOC, LOAD, DATA
  9 .bss          00000f14  f018e100  0018e100  0008f100  2**5
                  CONTENTS, ALLOC, LOAD, DATA
 10 .comment      0000002b  00000000  00000000  00090014  2**0
                  CONTENTS, READONLY

可以看出.bss段的范围为:0xf018e100-0xf018f014,大小为0xf14。将end变量输出,得到:end=0xf018f000。可以看到end在数据段之间。

解决办法

修改链接脚本kern/kernel.ld:


	.bss : {
		PROVIDE(edata = .);
		*(.bss)
		*(COMMON) 
		PROVIDE(end = .);
		BYTE(0)
	}

将COMMON添加在end之前即可。
参考链接:MIT 6.828 labs walkthroughs

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

参考了别人的做法后,现在依次给出实现。

env_init()

	// LAB 3: Your code here.
    env_free_list = NULL;
    for (int i = NENV - 1; i >= 0; i--) {   //前插法构建链表
        envs[i].env_id = 0;
        envs[i].env_link = env_free_list;
        env_free_list = &envs[i];
    }

对环境数组进行初始化,这个不复杂。

env_setup_vm()

设置用户的页表,进行虚拟内存的内存的设置。

	 // LAB 3: Your code here.
    p->pp_ref++;
    e->env_pgdir = (pde_t *) page2kva(p);  //刚分配的物理页作为页目录使用
    memcpy(e->env_pgdir, kern_pgdir, PGSIZE); //继承内核页目录

每个环境(进程)的内核部分映射应该是相同的,所以直接将内核的页表复制过来就行了。刚开始我还试图模仿pmap.c里面的实现一项一项地映射,但发现那样实在是事倍功半。

region_alloc()

	void *start=ROUNDDOWN(va,PGSIZE),*end=ROUNDUP(va+len,PGSIZE);
	for (void * addr=start;addr<end;addr+=PGSIZE){
		struct PageInfo* p=page_alloc(0);
		if(p==NULL){
			panic("region alloc failed: No more page to be allocated.\n");
		}
		else {
			if(page_insert(e->env_pgdir,p,addr, PTE_U | PTE_W)==-E_NO_MEM){
				panic("region alloc failed: page table couldn't be allocated.\n");
			}
		}
	}

load_icode()

	// LAB 3: Your code here.
    struct Elf *ELFHDR = (struct Elf *) binary;
    struct Proghdr *ph;             //Program Header
    int ph_num;                     //Program entry number
    if (ELFHDR->e_magic != ELF_MAGIC) {
        panic("binary is not ELF format\n");
    }
    ph = (struct Proghdr *) ((uint8_t *) ELFHDR + ELFHDR->e_phoff);
    ph_num = ELFHDR->e_phnum;

    lcr3(PADDR(e->env_pgdir));          //这步别忘了,虽然到目前位置e->env_pgdir和kern_pgdir除了PDX(UVPT)这一项不同,其他都一样。
                                        //但是后面会给e->env_pgdir增加映射关系

    for (int i = 0; i < ph_num; i++) {
        if (ph[i].p_type == ELF_PROG_LOAD) {        //只加载LOAD类型的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); //应该有如下关系:ph->p_filesz <= ph->p_memsz。搜索BSS段
        }
    }

    lcr3(PADDR(kern_pgdir));
    e->env_tf.tf_eip = ELFHDR->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);

env_create()

	struct Env * e;
	int r=env_alloc(&e,0);
	if(r!=0){
		cprintf("%e\n",r);
		panic("env_create:error");
	}
	load_icode(e,binary);
	e->env_type=type;

env_run()

	// LAB 3: Your code here.  
	if (e->env_status == ENV_RUNNING)
	{
		e->env_status = ENV_RUNNABLE;	
	}
	curenv = e;
	e->env_status = ENV_RUNNING;
	e->env_runs ++;
	lcr3(PADDR(e->env_pgdir));

	env_pop_tf(&(e->env_tf));
	//panic("env_run not yet implemented");

make;make qemu之后出现:

Triple fault.  Halting for inspection via QEMU monitor.

即正确。因为此时还未设置中断向量表,所以会出现上述的信息。使用gdb进行调试:

(gdb) b env_pop_tf
Breakpoint 1 at 0xf0103a1f: file kern/env.c, line 558.
(gdb) c
Continuing.
The target architecture is assumed to be i386
=> 0xf0103a1f <env_pop_tf>:	push   %ebp

Breakpoint 1, env_pop_tf (tf=0xf01d1000) at kern/env.c:558
558	{
(gdb) 

设置断点 ,进入函数env_pop_tf.

=> 0xf0103a2b <env_pop_tf+12>:	add    $0x885f5,%ebx
0xf0103a2b	558	{
(gdb) 
=> 0xf0103a31 <env_pop_tf+18>:	mov    0x8(%ebp),%esp
559		asm volatile(
(gdb) 
=> 0xf0103a34 <env_pop_tf+21>:	popa   
0xf0103a34	559		asm volatile(
(gdb) 
=> 0xf0103a35 <env_pop_tf+22>:	pop    %es
0xf0103a35 in env_pop_tf (
    tf=<error reading variable: Unknown argument list address for `tf'.>)
    at kern/env.c:559
559		asm volatile(
(gdb) 
=> 0xf0103a36 <env_pop_tf+23>:	pop    %ds
0xf0103a36	559		asm volatile(
(gdb) 
=> 0xf0103a37 <env_pop_tf+24>:	add    $0x8,%esp
0xf0103a37	559		asm volatile(
(gdb) 
=> 0xf0103a3a <env_pop_tf+27>:	iret   
0xf0103a3a	559		asm volatile(
(gdb) 

一直执行到iret语句。查看此时的寄存器:

eax            0x0	0
ecx            0x0	0
edx            0x0	0
ebx            0x0	0
esp            0xf01d1030	0xf01d1030
ebp            0x0	0x0
esi            0x0	0
edi            0x0	0
eip            0xf0103a3a	0xf0103a3a <env_pop_tf+27>
eflags         0x96	[ PF AF SF ]
cs             0x8	8
ss             0x10	16
ds             0x23	35
es             0x23	35
fs             0x23	35
gs             0x23	35

单步执行后iret后:

(gdb) si
=> 0x800020:	cmp    $0xeebfe000,%esp
0x00800020 in ?? ()
(gdb) 

进入到hello程序里面。

查看此时的寄存器值:

eax            0x0	0
ecx            0x0	0
edx            0x0	0
ebx            0x0	0
esp            0xeebfe000	0xeebfe000
ebp            0x0	0x0
esi            0x0	0
edi            0x0	0
eip            0x800020	0x800020
eflags         0x2	[ ]
cs             0x1b	27
ss             0x23	35
ds             0x23	35
es             0x23	35
fs             0x23	35
gs             0x23	35

此时的cs为GD_UT | 0X3 = 0x1b,也即此时进入用户态执行程序。esp为USTACKTOP这个值。

查看obj/user/hello.asm:

  800b7c:	cd 30                	int    $0x30

可以看到此处有中断语句。设置断点在此处:

(gdb) b *0x800b7c
Breakpoint 1 at 0x800b7c
(gdb) c
Continuing.
The target architecture is assumed to be i386
=> 0x800b7c:	int    $0x30

Breakpoint 1, 0x00800b7c in ?? ()
(gdb) si
=> 0x800b7c:	int    $0x30

Breakpoint 1, 0x00800b7c in ?? ()
(gdb) 
=> 0x800b7c:	int    $0x30

Breakpoint 1, 0x00800b7c in ?? ()
(gdb) 
=> 0x800b7c:	int    $0x30

Breakpoint 1, 0x00800b7c in ?? ()
(gdb) 

由于此时没有建立中断向量表,所以一直会执行int $0x30这条指令。

Handling Interrupts and Exceptions

Exercise 4. 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.
Your _alltraps should:
push values to make the stack look like a struct Trapframe
load GD_KD into %ds and %es
pushl %esp to pass a pointer to the Trapframe as an argument to trap()
call trap (can trap ever return?)
Consider using the pushal instruction; it fits nicely with the layout of the struct Trapframe.
Test your trap handling code using some of the test programs in the user directory that cause exceptions before making any system calls, such as user/divzero. You should be able to get make grade to succeed on the divzero, softint, and badsegment tests at this point.
*

首先,宏

#define TRAPHANDLER(name, num)						\
	.globl name;		/* define global symbol for 'name' */	\
	.type name, @function;	/* symbol type is function */		\
	.align 2;		/* align function definition */		\
	name:			/* function starts here */		\
	pushl $(num);							\
	jmp _alltraps

和宏

#define TRAPHANDLER_NOEC(name, num)					\
	.globl name;							\
	.type name, @function;						\
	.align 2;							\
	name:								\
	pushl $0;							\
	pushl $(num);							\
	jmp _alltraps


为我们定义了中断服务程序的入口形式。
根据Error Code Summary可以确定哪些中断号使用哪个宏。
我们可以写出下列内容:

TRAPHANDLER_NOEC(handler_divide,T_DIVIDE)
TRAPHANDLER_NOEC(handler_debug,T_DEBUG)
TRAPHANDLER_NOEC(handler_brkpt,T_BRKPT)
TRAPHANDLER_NOEC(handler_oflow,T_OFLOW)
TRAPHANDLER_NOEC(handler_bound,T_BOUND)
TRAPHANDLER_NOEC(handler_illop,T_ILLOP)
TRAPHANDLER_NOEC(handler_device,T_DEVICE)
TRAPHANDLER(handler_dblflt,T_DBLFLT)
TRAPHANDLER_NOEC(handler_tss,T_TSS)
TRAPHANDLER(handler_segnp,T_SEGNP)
TRAPHANDLER(handler_stack,T_STACK)
TRAPHANDLER(handler_gpflt,T_GPFLT)
TRAPHANDLER(handler_pgflt,T_PGFLT)
TRAPHANDLER_NOEC(handler_fperr,T_FPERR)
TRAPHANDLER_NOEC(handler_syscall,T_SYSCALL)

这里需要注意不要写错。

接着写_alltraps:

_alltraps:
	pushl %ds
	pushl %es
	pushal
	
	movw $GD_KD,%ax
	movw %ax,%ds
	movw %ax,%es
	pushl %esp
	call trap

最后,在kern/trap.c里面注册各个中断服务程序:

void
trap_init(void)
{
	extern struct Segdesc gdt[];

	// LAB 3: Your code here.
	void handler_divide();
	void handler_debug();
	void handler_brkpt();
	void handler_oflow();
	void handler_bound();
	void handler_illop();
	void handler_device();
	void handler_dblflt();
	void handler_tss();
	void handler_segnp();
	void handler_stack();
	void handler_gpflt();
	void handler_pgflt();
	void handler_fperr();
	void handler_syscall();
	
	SETGATE(idt[T_DIVIDE],0,GD_KT,handler_divide,0);
	SETGATE(idt[T_DEBUG],0,GD_KT,handler_debug,0);
	SETGATE(idt[T_BRKPT],0,GD_KT,handler_brkpt,0);
	SETGATE(idt[T_OFLOW],0,GD_KT,handler_oflow,0);
	SETGATE(idt[T_BOUND],0,GD_KT,handler_bound,0);
	SETGATE(idt[T_ILLOP],0,GD_KT,handler_illop,0);
	SETGATE(idt[T_DEVICE],0,GD_KT,handler_device,0);
	SETGATE(idt[T_DBLFLT],0,GD_KT,handler_dblflt,0);
	SETGATE(idt[T_TSS],0,GD_KT,handler_tss,0);
	SETGATE(idt[T_SEGNP],0,GD_KT,handler_segnp,0);
	SETGATE(idt[T_STACK],0,GD_KT,handler_stack,0);
	SETGATE	(idt[T_GPFLT],0,GD_KT,handler_gpflt,0);
	SETGATE(idt[T_PGFLT],0,GD_KT,handler_pgflt,0);
	SETGATE(idt[T_FPERR],0,GD_KT,handler_fperr,0);
	SETGATE(idt[T_SYSCALL],0,GD_KT,handler_syscall,3);

	// Per-CPU setup 
	trap_init_percpu();
}

使用make grade,可以查看分数:

divzero: OK (1.0s) 
softint: OK (0.9s) 
badsegment: OK (1.1s) 
Part A score: 30/30

至此Part A完成。

END.

你可能感兴趣的:(操作系统,内核)