MIT6.828学习之Lab3

it2022-05-05  110

自问自答

1.如何知道写对还是写错?错在哪里?这里不想lab2一样有很多check 这个我还是不太懂,但是你可以跟踪console的输出,找到每一行输出对应的函数,以及下一行应该输出什么,在哪个函数里。这样就可以找到是哪个函数错了,再设个断点b *fucntion_name,再单步跟踪si就行

2如何把IDT中条目指向中断处理程序入口地址,如何把中断处理函数名与中断处理函数联系起来?. 通过#define SETGATE(gate, istrap, sel, off, dpl)宏可以将IDT中的条目执行handler的入口地址。 通过kern/trapentry.S里定义的两个宏将处理函数名与中断向量(trapno)联系起来,至于具体怎么调用那个处理函数我还是比较懵逼。难道说用这个宏,函数名就变成num对应的处理函数入口地址了?

3入口地址通过调用trapentry.S中的两个宏给出,具体怎么给的,给出了什么.?

#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

我只看到它声明了下name是个function,然后push了num,这样就可以根据num来找到处理函数入口地址嘛? 答:这两个宏的主要作用只是err跟trapno入栈完善Trapframe结构的一部分,然后调用_alltraps。上面有个name:这个下面就是函数内容啊,它把err跟trapno入栈了啊,这里就是处理程序的入口地址啊!!!

4_alltraps的具体作用是什么?它做的事情在后面哪里用到了?

按我的理解_alltraps是把栈内的trapframe弄完整、将内核数据段(GD_KD)给段寄存器(段选择子)DS,ES、call trap。 后面的trap(tf)就马上用到了,因为call之后,会把返回地址跟old ebp入栈,然后esp给ebp,然后0x8(new ebp)给trap()做参数,即tf=0x8(new ebp)=old esp=trapframe由于栈向下生长,所以正过来正好跟Trapframe的结构一模一样。(不确定)

+--------------------+ //stack向下增长,留心“-”号 | 0x00000 | old SS | " - 4 <---- ESP | old ESP | " - 8 | old EFLAGS | " - 12 | 0x00000 | old CS | " - 16 | old EIP | " - 20 iret将上面出栈----> +--------------------+ <---- 以上在陷入发生时由硬件完成 | err | " - 24 | trapno | " - 28 +--------------------+ <----以上由TRAPHANDLER宏完成 | ds | " - 32 | es | " - 36 (trapframe)old esp-> | regs | " - 不知道多少 | old esp | +--------------------+ <----以上由_alltraps完成 | ret addr | new ebp----> | old ebp | <----esp +--------------------+ <----以上是call调用完成

5.整个Lab3 PartA部分的代码的流程是怎样的.?具体的说一遍

env_init()所有env加入env_free_list,env[0]在表头、per-cpu(Load GDT and 段选择器) -->trap_init()让IDT条目指向对应处理函数入口地址、trap_init_percpu -->env_create(binary_obj_user_hello_start, type)这里面又包括两个函数 env_alloc(&e,0);通过env_setup_vm初始化虚拟内存(页表)、初始化env各信息包括'e->env_tf'、从env_free_list中取出env[0] load_icode(e,binary)从binary中加载程序段到e对应的内存空间 -->env_run(&env[0]);curenv=env[0],改好状态 env_pop_tf(&curenv->env_tf)&env_tf为起始地址的一段空间'当成栈',逐步popa、pop、iret到相应寄存器。且iret结束后进入'user mode' -->寄存器都赋值了,开始执行eip=env_tf.tf_eip=0x800020 lib/entry.S/_start,然后call libmain -->lib/libmain.c/libmain()调用umain() -->user/hello.c/umain(),里面有lib/cprintf()函数,cprintf函数由许多'系统调用sys_cputs()'组成 cprintf()调用vcprintf()调用vprintmt()调用putch()调用cputchar()调用lib/sys_cputs()调用syscall() -->syscall(...)包含int %T_SYSCALL,中断向量48号,从IDT[48]中找到对应的处理函数入口地址syscall_handler(),陷入'内核态' -->kern/trapentry.S/TRAPHANDLER_NOEC(syscall_handler, T_SYSCALL); _alltraps 完善栈,让栈看上去像Trapframe结构,请看上面问题4 call trap -->trap(tf) 重设方向标志位DF、从栈里copy trap frame trap_dispatch(tf); print_trapframe(tf);打印tf内信息 print_regs(&tf->tf_regs); env_destroy(curenv);由于tf.tf_cs != GD_KT所以destroy了本该继续执行的environment env_free(e); -->while(1) monitor(NULL);

6.整个过程中栈中具体是什么样的?

问题4那个栈时陷入时栈的变化过程,但是,栈陷入之前,从env_create到env_run里的env_pop_tf好像又有些不一样。我感觉是这样的: env_create调用了env_alloc,这个过程中给e->env_tf赋值了,然后在env_pop_tf中把env_tf的地址给了%esp,那系统就把以&env_tf开始的一段地址当成栈,一点点的popa、pop、iret到各个寄存器,这样就可以配好了第一个用户程序执行所需要的环境,iret之后中断结束,正式进入user mode,按照cs:eip找到用户程序的第一条指令_start: cmp $0xeebfe00,%esp 还有个概念很重要,那就是tf的意义,让cpu运行的是那些寄存器的值,tf只是保存的时陷入的environment的信息以便后面能恢复,并不是tf控制cpu的运行!!!

//注释出自这里:http://www.mamicode.com/info-detail-2493874.html void env_pop_tf(struct Trapframe *tf) { asm volatile( "\tmovl %0,%%esp\n" /*将%esp指向tf地址处*/ "\tpopal\n" //弹出Trapframe结构中的tf_regs值到通用寄存器 "\tpopl %%es\n" //弹出Trapframe结构中的tf_es值到%es寄存器 "\tpopl %%ds\n" //弹出Trapframe结构中的tf_ds值到%ds寄存器 "\taddl $0x8,%%esp\n" /* skip tf_trapno and tf_errcode */ "\tiret\n" /* //中断返回指令,具体动作如下:从Trapframe结构中依次弹出tf_eip,tf_cs,tf_eflags,tf_esp,tf_ss到相应寄存器*/ : : "g" (tf) : "memory"); //g是一个通用约束,可以表示使用通用寄存器、内存、立即数等任何一种处理方式 panic("iret failed"); /* mostly to placate the compiler */ }

7.什么时候从内核态进入用户态,什么时候又从用户态进入内核态,又什么时候再次进入用户态的,具体的点在哪里? 在env_pop_tf里的iret之后所有Trapframe值给到相应寄存器,寄存器控制处理器从内核态进入用户态 执行hello.c的时候,lib/cprintf()层层调用,最后调用到syscall,在syscall中int $T_SYSCALL后产生中断,陷入到内核态 在trap中发现tf.tf_cs & 3 == 3即是由用户环境陷入进来的,所以在trap_dispatch中env_destroy了当前用户环境,所以并没有再进入用户态

8.iret是中断返回指令,那什么时候开始中断的呢? 我感觉应该时改变了那些段选择器及其他寄存器的值后,处理器由寄存器们控制着进入用户模式,所以iret之后寄存器值全部赋好,进入用户模式,所以问什么时候开始中断好像没什么意义。

实验过程

Exercise 2

env structure介绍

保存着用户环境的信息

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 大多是ENV_TYPE_USER unsigned env_status; // Status of the environment //ENV_FREE:inactive env, 这个状态下肯定在env_free_list中 //ENV_RUNNABLE:waiting to run //ENV_RUNNING: the currently running environment //ENV_NOT_RUNNABLE:当前active environment,但还没准备好去运行 //ENV_DYING:A zombie environment 下次陷入内核时被释放 uint32_t env_runs; // Number of times environment has run // Address space pde_t *env_pgdir; // Kernel virtual address of page dir };

env_init()

初始化envs数组中的所有Env结构,并将它们添加到env_free_list中。还调用env_init_percpu,它为特权级别0(内核)和特权级别3(用户)使用单独的段配置分段硬件。

void env_init(void) { // Set up envs array // LAB 3: Your code here. size_t i=0; for(; i<NENV; i++){ envs[i].env_id = 0; //env in free list so the status is ENV_FREE, You shouldn't forget it! envs[i].env_status = ENV_FREE; if(i==0){ env_free_list = &envs[i]; }else{ envs[i-1].env_link = &envs[i]; } } // Per-CPU part of the initialization env_init_percpu(); }

不要忘了,如果某个user environment在env_free_list中,那么它的状态也得设为ENV_FREE才行

env_setup_vm()

为新environment分配一个页面目录,并初始化新environment地址空间的内核部分。

这里我看半天没看懂,说什么所有envs在UTOP之上的虚拟地址都是相同的(除了UVPT),UTOP之下就初始化为0。还说只初始化new environment地址空间的内核部分,不要映射任何内容到用户部分。

后面想着好像是这样的,之前了解了ULIM之上是内核部分,ULIM往下是用户环境,所以把内核部分再加上pages与envs的内容(即UTOP以上)原封不动的从kern_pgdir中复制到env_pgdir中就叫做初始化内核部分,且所有envs在UTOP之上的虚拟地址都是相同的。

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++; //The hint is just a little bit above size_t i=0; // Do NOT (yet) map anything into the user portion for(; i<PDX(UTOP); i++){ e->env_pgdir[i] = 0; } //initialize the kernel portion of the new environment's address space. for(; i<NENV; 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; }

region_alloc()

为an environment分配和映射物理内存

这是个很简单的问题,只要向虚拟地址va分配并映射物理页就行,但我搞好久都没搞出来,可能对lab2的内容又忘了。。。

static void region_alloc(struct Env *e, void *va, size_t len) { // LAB 3: Your code here. struct PageInfo *p = NULL; void *i; for(i=(void*)ROUNDDOWN(va, PGSIZE); i<(void *)ROUNDUP(va+len, PGSIZE); i+=PGSIZE){ // Allocate a page if (!(p = page_alloc(0))) //allocate a physical page panic("fail to alloc page!\n"); if(page_insert(e->env_pgdir, p, i, PTE_U|PTE_W)!=0) //map the page to virtual address panic("fail to region_alloc!\n"); } }

load_icode()

您将需要解析an ELF binary image,就像the boot loader已经做的那样,并将其内容加载到new environment的用户地址空间中

提示文字也太多了吧。。。

自己写不出来啊! 首先binary应该转成Elf structure指针我不知道 其次我本应该知道设置environment执行入口是在e->env_tf.tf_eip中,结果我没想起来,还傻傻的照着main.c的入口地址那样写,这只是在加载不是执行,只是用户程序又不是内核部分 lcr3(e->env_pgdir)我懂但是不知道居然有这种操作 最后根据the program header加载segments,具体是给ph->p_va分配ph-.memsz大小的物理内存,然后直接把data从binary中move到虚拟地址对应的物理内存中。我写对了region_alloc,也没想到是用memmove跟memset,我就说readseg是读到物理地址,怎么提示里压根没提到过ph->p_pa。

static void load_icode(struct Env *e, uint8_t *binary) { //turn the binary into a Elf structure struct Elf *ELF_Header = (struct Elf*)binary; if (ELF_Header->e_magic != ELF_MAGIC) panic("The binary is not a ELF magic!\n"); //make sure that the environment starts executing from the entry point. if(ELF_Header->e_entry == 0) panic("The program can't be executed because the entry point is invalid!\n"); e->env_tf.tf_eip = ELF_Header->e_entry; // 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? //lcr3(e->env_pgdir);错大发了 lcr3(PADDR(e->env_pgdir));//load this user pgdir //load the segments //ph and eph is the Program header struct Proghdr *ph, *eph; ph = (struct Proghdr *) ((uint8_t *) ELF_Header + ELF_Header->e_phoff); eph = ph + ELF_Header->e_phnum; for (; ph < eph; ph++) // p_pa is the load address of this segment (as well // as the physical address) if(ph->p_type == ELF_PROG_LOAD){ if(ph->p_memsz < ph->filesz) panic("segment out of memory!\n"); //alloc ph->p_memsz bytes of physical memory for the ph->p_va region_alloc(e, (void *)ph->p_va, ph->p_memsz); //You can move data to ph->p_va via the e->env_pgdir /*之前的错误代码 memmove((void *)ph->p_va, (void *)(ELF_Header+ph->p_offset), ph->p_filesz); memset((void *)(ph->p_va+ph->p_filesz), 0, ph->p_memsz-ph->p_filesz);*/ memset((void *)ph->p_va, 0, ph->p_memsz); memcpy((void *)ph->p_va, binary+ph->p_offset, ph->p_filesz);//主要是binary+ph->p_offset } lcr3(PADDR(kern_pgdir));//???居然还得加载回来,不然softint拿不到分 // Now map one page for the program's initial stack // at virtual address USTACKTOP - PGSIZE. region_alloc(e, (void *)(USTACKTOP-PGSIZE), PGSIZE); }

env_create()

使用env_alloc分配an environment ,并调用load_icode将ELF二进制文件加载到其中。

这个还是比较简单,提示得很清楚

void env_create(uint8_t *binary, enum EnvType type) { // LAB 3: Your code here. struct Env *e; if(env_alloc(&e, 0)<0) panic("fail to create a env!\n") load_icode(e, binary); e->env_type = type; }

env_run()

在用户模式下运行的给定environment。

void env_run(struct Env *e) { if(curenv != e){ //是我考虑太不周全了 if(curenv != NULL && curenv->env_status == ENV_RUNNING) curenv->env_status = ENV_RUNNABLE; curenv = e; curenv->env_status = ENV_RUNNING; curenv->env_runs++; //lcr3((uint32_t)curenv->env_pgdir);又错大发了 lcr3(PADDR(curenv->env_pgdir)); env_pop_tf(&curenv->env_tf); } panic("env_run not yet implemented"); }

triple fault还是不太懂,好像是CPU连着三次报错,就算是叫天天不应,叫地地不灵了,只能重启系统?

但是这个系统调用指令不能成功运行,因为到目前为止,JOS还没有设置相关硬件来实现从用户态向内核态的转换功能。当CPU发现,它没有被设置成能够处理这种系统调用中断时,它会触发一个保护异常,然后发现这个保护异常也无法处理,从而又产生一个错误异常,然后又发现仍旧无法解决问题,所以最后放弃,我们把这个叫做”triple fault”。通常来说,接下来CPU会复位,系统会重启。 谢谢bysui的翻译

int指令(软件中断指令) INT(软件中断指令)是CALL指令的一种特殊形式。call指令调用调用的子程序是用户程序的一部分,而INT指令调用的操作系统提供的子程序或者其他特殊的子程序。 int指令后面一般接中断号。

完全搞不清到底对不对。。。不知道进没进user mode,不知道数据加载到user environment没。。。也不知道现在运行了哪个nev。。。(这里在Lab3里已经有说明了。env_pop_tf,它应该是您在实际进入用户模式之前命中的最后一个函数。处理器应该在iret指令之后进入用户模式。然后,您应该看到用户环境的可执行文件中的第一条指令,即lib/entry.S中标签start处的cmpl指令。)

我慢慢来试试看,输出了[00000000] new env 00001000说明进到了env_alloc里面,且curenv->env_id=NULL,new了一个environment成功,其e->env_id=00001000

然后我按Lab3里的提示,设了个断点在 800a9b: cd 30 int $0x30 这是不是在说,我从二进制ELF映像文件中读取数据失败了? 经过仔细的排查,终于发现,load_icode()中lcr3的赋值写的lcr3((uint32_t)e->env_pgdir);应该是lcr3(PADDR(e->env_pgdir));

更加让我震惊的事发生了:

//设个断点在env_pop_tf函数开始,执行到iret后本以到lib/entry.S/0x800020 _start, //结果0x800020处指令都错了!!! 0xf0102e66 <env_pop_tf+15>: iret 0x800020: add %al,(
转载请注明原文地址: https://win8.8miu.com/read-10107.html

最新回复(0)