项目地址:Lab 3
在文件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里面的每个环境都有自己的页目录,内核也有自己的页目录。
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
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”.
参考了别人的做法后,现在依次给出实现。
// 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];
}
对环境数组进行初始化,这个不复杂。
设置用户的页表,进行虚拟内存的内存的设置。
// LAB 3: Your code here.
p->pp_ref++;
e->env_pgdir = (pde_t *) page2kva(p); //刚分配的物理页作为页目录使用
memcpy(e->env_pgdir, kern_pgdir, PGSIZE); //继承内核页目录
每个环境(进程)的内核部分映射应该是相同的,所以直接将内核的页表复制过来就行了。刚开始我还试图模仿pmap.c里面的实现一项一项地映射,但发现那样实在是事倍功半。
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");
}
}
}
// 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);
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;
// 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这条指令。
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.