LAB3_Part C: Preemptive Multitasking and Inter-Process communication (IPC)
前言
记录一下自己的学习过程
实验内容翻译:
https://gitee.com/cherrydance/mit6.828
该翻译仅供参考
练习13
修改 kern/trapentry.S 和 kern/trap.c,初始化 IDT 中相应的条目,并为 IRQ 0 到 15 提供处理程序。然后,在 kern/env.c 中的 env_alloc() 函数中修改代码,确保用户环境始终以启用中断的方式运行。
同时取消注释 sched_halt() 函数中的 sti 指令,以便空闲的 CPU 解除中断屏蔽。
处理器在调用硬件中断处理程序时不会推送错误代码。您可能希望此时重新阅读 80386 参考手册的第 9.2 节,或者 IA-32 Intel 架构软件开发者手册第 3 卷的第 5.8 节。
完成此练习后,如果您使用任何运行时间较长的测试程序(例如 spin)运行内核,您应该会看到内核打印出硬件中断的陷阱帧。虽然处理器现在启用了中断,但 JOS 还没有处理它们,所以您应该会看到它错误地将每个中断归属于当前正在运行的用户环境并将其销毁。最终,它将用尽环境并进入监视器模式。
先查看inc/trap.h,里面给出了需要设置的IRQ。
#define IRQ_OFFSET 32 // IRQ 0 corresponds to int IRQ_OFFSET
// Hardware IRQ numbers. We receive these as (IRQ_OFFSET+IRQ_WHATEVER)
#define IRQ_TIMER 0
#define IRQ_KBD 1
#define IRQ_SERIAL 4
#define IRQ_SPURIOUS 7
#define IRQ_IDE 14
#define IRQ_ERROR 19
在kern/trapentry.S中设置
TRAPHANDLER_NOEC(irq_timer, IRQ_OFFSET + IRQ_TIMER)
TRAPHANDLER_NOEC(irq_kbd, IRQ_OFFSET + IRQ_KBD)
TRAPHANDLER_NOEC(irq_serial, IRQ_OFFSET + IRQ_SERIAL)
TRAPHANDLER_NOEC(irq_spurious, IRQ_OFFSET + IRQ_SPURIOUS)
TRAPHANDLER_NOEC(irq_ide, IRQ_OFFSET + IRQ_IDE)
TRAPHANDLER_NOEC(irq_error, IRQ_OFFSET + IRQ_ERROR)
在kern/trap.c中加入函数声明
void irq_timer();
void irq_kbd();
void irq_serial();
void irq_spurious();
void irq_ide();
void irq_error();
然后在idt中注册
SETGATE(idt[IRQ_OFFSET + IRQ_TIMER], 0, GD_KT, irq_timer, 0);
SETGATE(idt[IRQ_OFFSET + IRQ_KBD], 0, GD_KT, irq_kbd, 0);
SETGATE(idt[IRQ_OFFSET + IRQ_SERIAL], 0, GD_KT, irq_serial, 0);
SETGATE(idt[IRQ_OFFSET + IRQ_SPURIOUS], 0, GD_KT, irq_spurious, 0);
SETGATE(idt[IRQ_OFFSET + IRQ_IDE], 0, GD_KT, irq_ide, 0);
SETGATE(idt[IRQ_OFFSET + IRQ_ERROR], 0, GD_KT, irq_error, 0);
在inc/mmu.c中我们可以得到#define FL_IF 0x00000200 // Interrupt Flag
因此我们在env_alloc中设置启用中断 e->env_tf.tf_eflags |= FL_IF;
最后注释掉sched_halt() 函数中的 sti 指令。
练习14
修改内核的 trap_dispatch() 函数,使其在发生时钟中断时调用 sched_yield() 来查找并运行另一个环境。
现在,您应该能够使 user/spin 测试正常工作:父环境应该派生出子环境,对其使用 sys_yield() 函数几次,但在每次时间片后都能重新获得 CPU 的控制权,最后杀死子环境并正常终止。
先判断是否是时间中断,然后使用lapic_eoi确认中断,用sched_yield运行一个新环境。
if (tf->tf_trapno == IRQ_OFFSET + IRQ_TIMER){
lapic_eoi();
sched_yield();
return;
}
实验结果如图:
练习15
在 kern/syscall.c 中实现 sys_ipc_recv 和 sys_ipc_try_send。在实现之前,请阅读它们的注释,因为它们需要一起工作。在这些例程中调用 envid2env 时,应将 checkperm 标志设置为 0,表示允许任何环境向任何其他环境发送 IPC 消息,并且内核除了验证目标 envid 是否有效外,不进行特殊的权限检查。
然后在 lib/ipc.c 中实现 ipc_recv 和 ipc_send 函数。
使用 user/pingpong 和 user/primes 函数来测试您的 IPC 机制。user/primes 会生成每个素数一个新的环境,直到 JOS 用尽环境为止。您可能会发现阅读 user/primes.c 很有趣,以了解其背后的分支和 IPC 过程。
sys_ipc_recv函数:
阻塞,直到接收一个值。其实就是所谓的缺少资源放弃cpu进入阻塞队列,直到资源准备好进入就绪队列,而后获得cpu运行。
1,判断dstva是否小于UTOP且业对齐
2,使用 struct Env 的 env_ipc_recving 和 env_ipc_dstva 字段记录您希望接收的信息,将自己标记为不可运行,然后放弃 CPU。
代码如下
if((uint32_t)dstva < UTOP && PGOFF(dstva)){
return -E_INVAL;
}
curenv->env_ipc_recving = true;
curenv->env_ipc_dstva = dstva;
curenv->env_status = ENV_NOT_RUNNABLE;
sys_yield();
sys_ipc_try_send函数:
尝试将 ‘value’ 发送到目标环境 ‘envid’。
1,获取目标环境并判断该环境是否在等待接收信息
2,判断srcva是否是要映射一个页面
3,对相关参数进行合法性检查
4,获取要映射的页内容,插入到目标环境
5,更新目标的ipc字段
代码如下:
struct Env *renv = NULL;
if(envid2env(envid, &renv, 0) < 0){
return -E_BAD_ENV;
}
if(!renv->env_ipc_recving){
return -E_IPC_NOT_RECV;
}
if((uint32_t)srcva < UTOP){
if(PGOFF(srcva)){
return -E_INVAL;
}
if(!(perm & PTE_P) || !(perm & PTE_P)){
return -E_INVAL;
}
if (perm & (~PTE_SYSCALL)){
return -E_INVAL;
}
pte_t *src_pte;
struct PageInfo *src_page = page_lookup(curenv->env_pgdir, srcva, &src_pte);
if(!src_page){
return -E_INVAL;
}
if((perm & PTE_W) && !(*src_pte & PTE_W)){
return -E_INVAL;
}
if((uint32_t)renv->env_ipc_dstva < UTOP){
if(page_insert(renv->env_pgdir, src_page, renv->env_ipc_dstva, perm) < 0){
return -E_NO_MEM;
}
}
renv->env_ipc_perm = perm;
}
renv->env_ipc_recving = 0; // 获得数据后 取消接受ing状态
renv->env_ipc_value = value;
renv->env_status = ENV_RUNNABLE;
renv->env_ipc_from = curenv->env_id;
renv->env_tf.tf_regs.reg_eax = 0;//参数为0
return 0;
ipc_rev函数:
通过ipc接收一个值并返回它
1,如果pg为空,向 sys_ipc_recv 传递一个特殊的值,表示“没有页面”。
2,如果 from_env_store 非空,则将 IPC 发送方的 envid 存储在 *from_env_store 中,如果 perm_store 非空,则将 IPC 发送方的页面权限存储在 *perm_store 中。
3,如果系统调用失败,则将 0 存储在 *fromenv 和 *perm 中(如果它们非空),然后返回错误。
否则,返回发送方发送的值。
代码如下:
if(!pg){
pg = (void *)UTOP;
}
size_t ret = sys_ipc_recv(pg);
if(ret < 0){
if(from_env_store){
*from_env_store = 0;
}
if(perm_store){
*perm_store = 0;
}
return ret;
}else{
if(from_env_store){
*from_env_store = thisenv->env_ipc_from;
}
if(perm_store){
*perm_store = thisenv->env_ipc_perm;
}
return thisenv->env_ipc_value;
}
ipc_send函数:
将 ‘val’(如果 ‘pg’ 非空,则包括 ‘pg’ 和 ‘perm’)发送给 ‘toenv’。
该函数会一直尝试直到成功为止。
1,pg为空,则给予特殊值表示没有页面
2,循环调用sys_ipc_try_send函数,直至成功或出现特殊错误
3,是要sys_yield()防止一直占用cpu
代码如下:
if(!pg){
pg = (void *)UTOP;
}
size_t ret;
while(1){
ret = sys_ipc_try_send(to_env, val, pg, perm);
if(ret == -E_IPC_NOT_RECV){
sys_yield();
}else{
break;
}
}
if(ret != 0){
panic("ipc_send Error: %e", ret);
}
运行结果:
总结
完成了lab4的所有部分!!!!!
该部分为了防止cpu独占实现了时钟中断,还实现了最基本的进程间通信。