Lab 4 Part C
Lab4的part c,我们将实现一个进程抢占机制并实现进程间通信。
Clock Interrupts and Preemption
Interrupt discipline
exercise 13:修改kern/trapentry.S和kern/trap.c来初始化IDT中的表项并且为IRQs0-15的外部中断提供处理函数,然后修改kern/env.c中的env_alloc()确保用户进程在开中断状态下运行,还要取消对sched_halt()中sti指令的注释.这样空闲的cpu也不会屏蔽中断,当触发硬件中断处理函数时,处理器并不会压入错误码。
1.将处理函数与中断号对应:
//external interrupts
TRAPHANDLER_NOEC(timer_handler, IRQ_OFFSET + IRQ_TIMER);
TRAPHANDLER_NOEC(kbd_handler, IRQ_OFFSET + IRQ_KBD);
TRAPHANDLER_NOEC(serial_handler, IRQ_OFFSET + IRQ_SERIAL);
TRAPHANDLER_NOEC(spurious_handler, IRQ_OFFSET + IRQ_SPURIOUS);
TRAPHANDLER_NOEC(ide_handler, IRQ_OFFSET + IRQ_IDE);
TRAPHANDLER_NOEC(error_handler, IRQ_OFFSET + IRQ_ERROR);
2.定义中断处理函数
void timer_handler();
void kbd_handler();
void serial_handler();
void spurious_handler();
void ide_handler();
void error_handler();
3.设置门
SETGATE(idt[IRQ_OFFSET + IRQ_TIMER], 0, GD_KT, timer_handler, 3);
SETGATE(idt[IRQ_OFFSET + IRQ_KBD], 0, GD_KT, kbd_handler, 3);
SETGATE(idt[IRQ_OFFSET + IRQ_SERIAL], 0, GD_KT, serial_handler, 3);
SETGATE(idt[IRQ_OFFSET + IRQ_SPURIOUS], 0, GD_KT, spurious_handler, 3);
SETGATE(idt[IRQ_OFFSET + IRQ_IDE], 0, GD_KT, ide_handler, 3);
SETGATE(idt[IRQ_OFFSET + IRQ_ERROR], 0, GD_KT, error_handler, 3);
4.用户态的时候开中断
// Enable interrupts while in user mode.
// LAB 4: Your code here.
e->env_tf.tf_eflags |= FL_IF;
测试结果如下:
测试要求:在做完本练习后,如果使用陷入死循环的测试程序进行测试.你应当看到内核为触发的硬件中断打印出trap frame信息.在完成本练习后,如果使用任何运行的时间不短(例如,spin)的测试程序运行内核,应该会看到内核为硬件中断打印trap frames。中断现在已在处理器中启用,但JOS还没有处理它们,所以应该看到它将每个中断错误地归因于当前正在运行的用户环境并销毁它。最终,它应该销毁所有环境并进入monitor。
从上面的测试图片可以看出,内核打印出了trap frame信息。且打印出了该系统中没有可运行的环境,说明JOS销毁了当前发生中断错误的用户环境。最后进入了monitor环境,因此可以确认exercise13成功完成。
Handling Clock Interrupts
exercise14:修改内核的trap_dispatch()函数使它在时钟中断发生时调用sched_yield()来进行进程切换,修改后spin程序应当按如下流程工作:父进程创建一个子进程,并调用sys_yield()使其上cpu运行,但是每过一个时间片都会重新取得cpu的控制权,最后父进程会杀死子进程然后退出。
根据提示在sched_yield();之前调用lapic_eoi(),因此代码如下:
// Handle clock interrupts. Don't forget to acknowledge the
// interrupt using lapic_eoi() before calling the scheduler!
// LAB 4: Your code here.
if(tf->tf_trapno == IRQ_OFFSET + IRQ_TIMER){
lapic_eoi();
sched_yield();
}
Inter-Process communication (IPC)
该部分要求我们完成一个内部环境的通信机制。
exercise 15:在kern / syscall.c中实现sys_ipc_recv和sys_ipc_try_send。 在实施它们之前,请先阅读它们的注释,因为它们必须协同工作。 当您在这些例程中调用envid2env时,应将checkperm标志设置为0,这意味着允许任何进程将IPC消息发送到任何其他进程,并且除了验证目标envid合法之外,内核不执行任何特殊权限检查。然后在lib / ipc.c中实现ipc_recv和ipc_send函数。
1.sys_ipc_recv函数
static int sys_ipc_recv(void *dstva)
{
// LAB 4: Your code here.
//panic("sys_ipc_recv not implemented");
//if dstva < UTOP but dstva is not page-aligned.
if((uintptr_t)dstva < UTOP && PGOFF(dstva)) return -E_INVAL;
curenv->env_ipc_dstva = dstva;
curenv->env_status = ENV_NOT_RUNNABLE;
curenv->env_ipc_recving = 1;
curenv->env_ipc_from = 0;
sched_yield();
return 0;
}
2.sys_ipc_try_send函数
static int sys_ipc_try_send(envid_t envid, uint32_t value, void *srcva, unsigned perm)
{
// LAB 4: Your code here.
//panic("sys_ipc_try_send not implemented");
struct Env *renv;
int err=0;
if((err = envid2env(envid,&renv,0)) < 0) return err;
//if envid is not currently blocked in sys_ipc_recv, or another environment managed to send first.
if(!renv->env_ipc_recving || renv->env_ipc_from) return -E_IPC_NOT_RECV;
//srcva is the address of the sender; renv->env_ipc_dstva is the address of the receiver
//these two address is both below than UTOP
if((uintptr_t) srcva < UTOP && (uintptr_t) renv->env_ipc_dstva < UTOP){
pte_t* srcpte;
struct PageInfo* srcpg = page_lookup(curenv->env_pgdir,(void *)srcva,&srcpte);
//if srcva < UTOP but srcva is not mapped in the caller's address space.
if(!srcpte || !srcpg) return -E_INVAL;
if( PGOFF(srcva)|| !(perm & PTE_P) || !(perm & PTE_U) || (perm & (~ PTE_SYSCALL)) || (!(*srcpte & PTE_W)&&(perm & PTE_W)))
return -E_INVAL;
//send page currently mapped at 'srcva'(srcpg),so that receiver gets a duplicate mapping of the same page
if((err = page_insert(renv->env_pgdir,srcpg,(void *)renv->env_ipc_dstva,perm)) < 0) return err;
//env_ipc_perm is set to 'perm' if a page was transferred, 0 otherwise.
renv->env_ipc_perm = perm;
}
else{
//0 otherwise.
renv->env_ipc_perm = 0;
}
//env_ipc_recving is set to 0 to block future sends;
//env_ipc_from is set to the sending envid;
//env_ipc_value is set to the 'value' parameter;
renv->env_ipc_from = curenv->env_id;
renv->env_ipc_recving = 0;
renv->env_ipc_value = value;
renv->env_status = ENV_RUNNABLE;
// 返回值
renv->env_tf.tf_regs.reg_eax = 0;
return 0;
}
- ipc_recv()函数
int32_t ipc_recv(envid_t *from_env_store, void *pg, int *perm_store)
{
// LAB 4: Your code here.
//panic("ipc_recv not implemented");
int ret;
//If 'pg' is nonnull, then any page sent by the sender will be mapped at that address.
pg = pg == NULL ? (void *)UVPT : pg;
ret = sys_ipc_recv(pg);
if(from_env_store) *from_env_store = ret ? 0 : thisenv->env_ipc_from;
if(perm_store) *perm_store = ret ? 0 : thisenv->env_ipc_perm;
if(ret < 0) return ret;
return thisenv->env_ipc_value;
}
4.ipc_send()函数
void ipc_send(envid_t to_env, uint32_t val, void *pg, int perm)
{
// LAB 4: Your code here.
//panic("ipc_send not implemented");
int ret;
pg = pg == NULL ? (void *)UVPT : pg;
while(1){
ret=sys_ipc_try_send(to_env,val,pg,perm);
if( !ret){
return;
}
else if(ret != -E_IPC_NOT_RECV){
panic("ipc_send:error");
}else{
sys_yield();
}
}
}
测试:
代码编写中出现了一点小错误,好在最后还是成功通过了。