PART A: 多处理器支持
Exercise 1:void *
mmio_map_region(physaddr_t pa, size_t size)
{
// Where to start the next region. Initially, this is the
// beginning of the MMIO region. Because this is static, its
// value will be preserved between calls to mmio_map_region
// (just like nextfree in boot_alloc).
static uintptr_t base = MMIOBASE;
// Reserve size bytes of virtual memory starting at base and
// map physical pages [pa,pa+size) to virtual addresses
// [base,base+size). Since this is device memory and not
// regular DRAM, you'll have to tell the CPU that it isn't
// safe to cache access to this memory. Luckily, the page
// tables provide bits for this purpose; simply create the
// mapping with PTE_PCD|PTE_PWT (cache-disable and
// write-through) in addition to PTE_W. (If you're interested
// in more details on this, see section 10.5 of IA32 volume
// 3A.)
//
// Be sure to round size up to a multiple of PGSIZE and to
// handle if this reservation would overflow MMIOLIM (it's
// okay to simply panic if this happens).
//
// Hint: The staff solution uses boot_map_region.
//
// Your code here:
uintptr_t nextBase=base+ROUNDUP(size,PGSIZE);
uintptr_t i;
if(nextBase>=MMIOLIM) panic(" mmio_map_region: mmio is out of limit\n");
boot_map_region(kern_pgdir,base,ROUNDUP(size,PGSIZE),pa,PTE_PCD|PTE_PWT|PTE_W);
i=base;
base=nextBase;
return (void *)i;
//panic("mmio_map_region not implemented");
}
Exercise 2:
void
page_init(void)
{
size_t i;
uint32_t j=((uint32_t)boot_alloc(0)-KERNBASE)/PGSIZE;
uint32_t j2=IOPHYSMEM/PGSIZE;
uint32_t j3=MPENTRY_PADDR/PGSIZE;
for (i = npages-1; i>=1; i--) {
//the physical memory pages that has been used by pages and kernel
//cprintf("rrrrrrrrrrrrrrrrr");
if(!((i>=j2&&i<j)||i==0||i==j3)){
pages[i].pp_ref = 0;
pages[i].pp_link = page_free_list;
page_free_list = &pages[i];
}
}
}
Question 1:
MPBOOTPHYS(start32)是取得start32的物理地址
为什么ljmpl $(PROT_MODE_CSEG),$(MPBOOTPHYS(start32),中需要取得start32的物理地址?
在boot/boot.s中,0-4M线性地址空间被映射到0-4M物理内存,start的线性地址与实际的物理地址一样,使用相对地址跳转后,还是跳转到protcseg, 所以不需要求物理地址;
而在这里,mpentry.s从别的地方拷贝过来的,其中start32是相对于mpentry_start的地址,这个地址与启用用保护模式后start32所在的物理地址不同,所以跳转不到start32指令处。
记住ljmpl后,就是跳转到cs+EIP的地址去,这里的内容就是cs+EIP转化为物理地址后,这个物理内存的内容。
深入理解跳转的含义!!!
Exercise 3:
static void
mem_init_mp(void)
{
// LAB 4: Your code here:
uint32_t i,stacki_beginV,stacki_beginPh;
for(i=0;i<NCPU;i++){
stacki_beginV=(uint32_t)(KSTACKTOP-i*(KSTKSIZE + KSTKGAP)-KSTKSIZE);
//stacki_beginPh=(uint32_t)(bootstacktop-i*(KSTKSIZE + KSTKGAP)-KSTKSIZE);
boot_map_region(kern_pgdir,stacki_beginV,KSTKSIZE,PADDR(percpu_kstacks[i]),PTE_W|PTE_P);
}
}
Exercise 4:
void
trap_init_percpu(void)
{
// LAB 4: Your code here:
// Setup a TSS so that we get the right stack
// when we trap to the kernel.
//ts.ts_esp0 = KSTACKTOP;
//ts.ts_ss0 = GD_KD;
uint8_t i=thiscpu->cpu_id;
thiscpu->cpu_ts.ts_esp0=KSTACKTOP-i*(KSTKSIZE+KSTKGAP);
thiscpu->cpu_ts.ts_ss0=GD_KD;
// Initialize the TSS slot of the gdt.
//gdt[GD_TSS0 >> 3] = SEG16(STS_T32A, (uint32_t) (&ts),
// sizeof(struct Taskstate), 0);
//gdt[GD_TSS0 >> 3].sd_s = 0;
gdt[(GD_TSS0>>3)+i]=SEG16(STS_T32A,(uint32_t)(&thiscpu->cpu_ts),sizeof(struct Taskstate),0);
gdt[(GD_TSS0>>3)+i].sd_s = 0;
// Load the TSS selector (like other segment selectors, the
// bottom three bits are special; we leave them 0)
ltr(GD_TSS0+(i<<3));
// Load the IDT
lidt(&idt_pd);
}
Exercise 5: 很简单
Question 2: 为什么呢?user->kernel,使用kernel栈? 使用完后,下一次再进入kernel,栈中数据清空了!
考虑极端情况,如果某个cpu A在使用kernel, cpu B用户程序运行时产生中断,这时cpu B直接将数据保存到内核堆栈,而这时还没有申请big kernel lock呢!!!
Challenge 1:这个很难呀,很难!
Exercise 6:这个花了我好长的时间,kernel总是崩掉,后来经过仔细调试,发现有两点影响这个问题:1. 以前lab所编写的函数env_setup_vm没有将这次试验中用到的MMIOBASE-MMIOLIM之间的内用拷贝到enviroment的页目录中去。2.就是trap_init_percpu中thiscpu->cpu_ts.ts_esp0的赋值,我一开始给出的是percpu_kstacks[i],不知为什么使用这个总是出现问题,必须使用KSTACKTOP-i*(KSTKSIZE+KSTKGAP), 可能有别的地方用到了这个地址了
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);
memmove(e->env_pgdir, kern_pgdir, PGSIZE);
memset(e->env_pgdir, 0, PDX(UTOP)*sizeof(pde_t));
p->pp_ref ++;
// 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;
}
void
sched_yield(void)
{
struct Env *idle;
// Implement simple round-robin scheduling.
//
// Search through 'envs' for an ENV_RUNNABLE environment in
// circular fashion starting just after the env this CPU was
// last running. Switch to the first such environment found.
//
// If no envs are runnable, but the environment previously
// running on this CPU is still ENV_RUNNING, it's okay to
// choose that environment.
//
// Never choose an environment that's currently running on
// another CPU (env_status == ENV_RUNNING). If there are
// no runnable environments, simply drop through to the code
// below to halt the cpu.
// LAB 4: Your code here.
int i;
if(thiscpu->cpu_env != NULL) {
int cur_env_id = thiscpu->cpu_env->env_id;
cur_env_id = ENVX(cur_env_id);
for(i = (cur_env_id+1)%NENV; i != cur_env_id; i = (i+1)%NENV) {
if(envs[i].env_status == ENV_RUNNABLE)
break;
}
if(i != cur_env_id)
env_run(&envs[i]);
if(thiscpu->cpu_env->env_status == ENV_RUNNING)
env_run(thiscpu->cpu_env);
}else{
for(i = 0; i<NENV; i++){
if(envs[i].env_status == ENV_RUNNABLE)
break;
}
if(i!=NENV) env_run(&envs[i]);
}
// sched_halt never returns
sched_halt();
}
Question 3: 所有envrioment中的kernel线性地址部分都一样,和没有进入environment前的kernel一样. 所以按理说exericse6中使用percpu_kstacks[i]也正确,但是不知为什么总是报中断问题!!
Question 4: 使用sys_yield(),从用户模式进入kernel模式,trap.c中就保存在envs中了
Challenge 2: 添加一个系统调用,设置priority, 这样可以设置每个env的优先级
Challenge 3: env添加一些保存的项目
Exercise 7:
static envid_t
sys_exofork(void)
{
// Create the new environment with env_alloc(), from kern/env.c.
// It should be left as env_alloc created it, except that
// status is set to ENV_NOT_RUNNABLE, and the register set is copied
// from the current environment -- but tweaked so sys_exofork
// will appear to return 0.
// LAB 4: Your code here.
struct Env *newenv_store=NULL;
int r=env_alloc(&newenv_store,curenv->env_id);
if(r<0) {cprintf("env_alloc error: %e", r);return r;}
newenv_store->env_tf=curenv->env_tf;
//memmove((void *)&(newenv_store->env_tf),(const void *)&(curenv->env_tf), sizeof(struct Trapframe));
newenv_store->env_status=ENV_NOT_RUNNABLE;
newenv_store->env_tf.tf_regs.reg_eax=0;
return newenv_store->env_id;
//panic("sys_exofork not implemented");
}
static int
sys_env_set_status(envid_t envid, int status)
{
// Hint: Use the 'envid2env' function from kern/env.c to translate an
// envid to a struct Env.
// You should set envid2env's third argument to 1, which will
// check whether the current environment has permission to set
// envid's status.
// LAB 4: Your code here.
struct Env *newenv_store=NULL;
int i=envid2env(envid,&newenv_store,1);
if(i<0) return -E_BAD_ENV;
if((status==ENV_RUNNABLE)||(status==ENV_NOT_RUNNABLE)){
newenv_store->env_status=status;
return 0;
}else{
cprintf("xxxxxxxxxxx status %d\n",status);
return -E_INVAL;
}
//panic("sys_env_set_status not implemented");
}
static int
sys_page_alloc(envid_t envid, void *va, int perm)
{
// Hint: This function is a wrapper around page_alloc() and
// page_insert() from kern/pmap.c.
// Most of the new code you write should be to check the
// parameters for correctness.
// If page_insert() fails, remember to free the page you
// allocated!
// LAB 4: Your code here.
if(((uint32_t)va>=UTOP)||((uint32_t)va%PGSIZE!=0))return -E_INVAL;
if(!((perm&PTE_U)&&(perm&PTE_P)))return -E_INVAL;
if(perm&(~PTE_SYSCALL))return -E_INVAL;
struct Env *newenv_store=NULL;
int i=envid2env(envid,&newenv_store,1);
if(i<0) return -E_BAD_ENV;
struct PageInfo *pageinfo=page_alloc(ALLOC_ZERO);
if(pageinfo==NULL) return -E_NO_MEM;
i=page_insert(newenv_store->env_pgdir,pageinfo,va,perm);
if(i<0) {
page_free(pageinfo);
return -E_NO_MEM;
}
return 0;
//panic("sys_page_alloc not implemented");
}
static int
sys_page_map(envid_t srcenvid, void *srcva,
envid_t dstenvid, void *dstva, int perm)
{
// Hint: This function is a wrapper around page_lookup() and
// page_insert() from kern/pmap.c.
// Again, most of the new code you write should be to check the
// parameters for correctness.
// Use the third argument to page_lookup() to
// check the current permissions on the page.
// LAB 4: Your code here.
if(((uint32_t)srcva>=UTOP)||((uint32_t)srcva%PGSIZE!=0))return -E_INVAL;
if(((uint32_t)dstva>=UTOP)||((uint32_t)dstva%PGSIZE!=0))return -E_INVAL;
if(!((perm&PTE_U)&&(perm&PTE_P)))return -E_INVAL;
if(perm&(~PTE_SYSCALL))return -E_INVAL;
struct Env *newenv_store=NULL;
int i=envid2env(srcenvid,&newenv_store,1);
if(i<0) return -E_BAD_ENV;
pte_t *ptet=NULL;
struct PageInfo *pageinfo=page_lookup(newenv_store->env_pgdir,srcva,&ptet);
if(pageinfo==NULL)return -E_INVAL;
if(perm&PTE_W){
if(!(*ptet&PTE_W))return -E_INVAL;
}
i=envid2env(dstenvid,&newenv_store,1);
if(i<0) return -E_BAD_ENV;
i=page_insert(newenv_store->env_pgdir,pageinfo,dstva,perm);
if(i<0)return -E_NO_MEM;
return 0;
//panic("sys_page_map not implemented");
}
static int
sys_page_unmap(envid_t envid, void *va)
{
// Hint: This function is a wrapper around page_remove().
// LAB 4: Your code here.
if(((uint32_t)va>=UTOP)||((uint32_t)va%PGSIZE!=0))return -E_INVAL;
struct Env *newenv_store=NULL;
int i=envid2env(envid,&newenv_store,1);
if(i<0) return -E_BAD_ENV;
pte_t *ptet=NULL;
struct PageInfo *pageinfo=page_lookup(newenv_store->env_pgdir,va,&ptet);
if(pageinfo==NULL)return 0;
page_remove(newenv_store->env_pgdir,va);
return 0;
//panic("sys_page_unmap not implemented");
}
int32_t syscall中添加:
case SYS_exofork:
r=sys_exofork();
break;
case SYS_page_alloc:
r=sys_page_alloc((envid_t)a1,(void *) a2,(int)a3);
break;
case SYS_page_map:
r=sys_page_map((envid_t)a1,(void*)a2,(envid_t)a3,(void*)a4,(int)a5);
break;
case SYS_page_unmap:
r=sys_page_unmap((envid_t)a1,(void*)a2);
break;
case SYS_env_set_status:
r=sys_env_set_status((envid_t)a1,(int)a2);
break;
注意:sys_page_umap中我一开始未使用page_remove,而使用*ptet=0,哈哈,这样不行呀,导致系统崩溃!!!
Challenge 4: 这个和进程干冻差不多,哈哈,未做!
PART B: Copy-on-write fork
Exercise 9:
Exercise 10:
Exercise 11:
Challenge 4:怎么要处理user environment所有的异常呢?现代OS中都是这么搞的?为什么是用户程序来处理这些呢??
Exercise 12: 这几个exercises没认真做,看明白了ruizhe huang的代码, pfentry.s的代码注意,如何实现user unexception-> user normal模式和user->unexception? 将eip保存到空白中,esp指到这个空白处,然后pop %esp,然后ret(pop %eip),这样就跳转过去了,真的很tricky,很tricky
Challenge 5,6:未做
PART C: Preemptive Multitasking and IPC
Exersice 13:很简单, 使用SEGGATE时,设置为中断门,而不是异常门。Exersice 14:时钟中断为IRQ0,对应处理函数为routine_irq0
trap_dispatch中添加:
case (IRQ_OFFSET+IRQ_TIMER):
lapic_eoi();
sched_yield();
return;
Exercise 15: 具体实现IPC,就是实现两个系统调用,send和recv,来实现进程通信。具体没做,看huangrui zhe的实验报告跑了跑
最后几个Challenge也未做
2013.7.29-2012.8.4