实验完善代码 LAB2-4下载链接 提取码:79t8
1、Part C : 抢先多任务和进程间通信
LAB 4 的最后一部分将修改代码以实现抢先多任务,并实现进程间的通信。
(1) 时钟中断和抢占
运行程序 user/spin
,这个程序中,父进程fork()
子进程,然后子进程一旦得到CPU 的使用权,将永远自旋,内核和父进程将永远不会再得到控制权。很明显,目前的操作系统代码是有缺陷的,任何用户进程都可能会通过自旋夺取系统的控制权,内核将不会得到控制权。为了让内核抢占一个正在运行的进程,强迫重新获得CPU 的控制权,我们必须扩展JOS 系统使其支持外部的时钟硬件中断。
(2) 时钟中断设置
外部中断用IRQ 表示,一共有16中外部中断分别用标号0-15 表示。目前,外部中断与中断描述符表之间的映射关系还没有建立。picirq.c
文件中的 pic_init()
函数 将外部中断0-15 映射到 IDT 的 IRQ_OFFSET
到 IRQ_OFFSET + 15
.
在inc/trap.h
中,IRQ_OFFSET
被定义为32,因此IRQ 的中断向量为32-47,例如时钟中断的IRQ 编号为0,则它的中断描述符为IDT[IRQ_OFFSET+0]
, 这样的设置可以避免外部中部和系统中断之间的重叠。
在JOS 系统中,我们进行了一个重要的简化,内核态下外部设备中断总是关闭的。外部中断被 %eflags
寄存器的 FL_IF
位 控制。当这一位为 1 的时候表示外部中断启用,为0 表示外部中断禁用。由于我们简化为只在用户态启用,那么当系统从用户态切换到内核态的时候,将该位 置 1;反之置0。
我们需要确保在用户态 FL_IF
位 是1,这样当用户态运行的时候可以接受这个中断,并处理这个中断。现在的代码中,在 boot loader 启动的时候就关闭了中断,并且到目前为止都没有再开启中断。
- Exercise 13
/ kern/trapentry.S///
TRAPHANDLER_NOEC(Trap_IRQ_0, IRQ_OFFSET + 0);//32, 0号外部中断
TRAPHANDLER_NOEC(Trap_IRQ_1, IRQ_OFFSET + 1);//33, 外部中断
TRAPHANDLER_NOEC(Trap_IRQ_2, IRQ_OFFSET + 2);//34, 外部中断
TRAPHANDLER_NOEC(Trap_IRQ_3, IRQ_OFFSET + 3);//35, 外部中断
TRAPHANDLER_NOEC(Trap_IRQ_4, IRQ_OFFSET + 4);//36, 外部中断
TRAPHANDLER_NOEC(Trap_IRQ_5, IRQ_OFFSET + 5);//37, 外部中断
TRAPHANDLER_NOEC(Trap_IRQ_6, IRQ_OFFSET + 6);//38, 外部中断
TRAPHANDLER_NOEC(Trap_IRQ_7, IRQ_OFFSET + 7);//39, 外部中断
TRAPHANDLER_NOEC(Trap_IRQ_8, IRQ_OFFSET + 8);//40, 外部中断
TRAPHANDLER_NOEC(Trap_IRQ_9, IRQ_OFFSET + 9);//41, 外部中断
TRAPHANDLER_NOEC(Trap_IRQ_10, IRQ_OFFSET + 10);//42,外部中断
TRAPHANDLER_NOEC(Trap_IRQ_11, IRQ_OFFSET + 11);//43,外部中断
TRAPHANDLER_NOEC(Trap_IRQ_12, IRQ_OFFSET + 12);//44,外部中断
TRAPHANDLER_NOEC(Trap_IRQ_13, IRQ_OFFSET + 13);//45,外部中断
TRAPHANDLER_NOEC(Trap_IRQ_14, IRQ_OFFSET + 14);//46,外部中断
TRAPHANDLER_NOEC(Trap_IRQ_15, IRQ_OFFSET + 15);//47,外部中断
// kern/trap.c
void Trap_IRQ_0();
void Trap_IRQ_1();
void Trap_IRQ_2();
void Trap_IRQ_3();
void Trap_IRQ_4();
void Trap_IRQ_5();
void Trap_IRQ_6();
void Trap_IRQ_7();
void Trap_IRQ_8();
void Trap_IRQ_9();
void Trap_IRQ_10();
void Trap_IRQ_11);
void Trap_IRQ_12();
void Trap_IRQ_13();
void Trap_IRQ_14();
void Trap_IRQ_15();
SETGATE(idt[IRQ_OFFSET + 0], 0, GD_KT, Trap_IRQ_0, 0 );
SETGATE(idt[IRQ_OFFSET + 1], 0, GD_KT, Trap_IRQ_1, 0 );
SETGATE(idt[IRQ_OFFSET + 2], 0, GD_KT, Trap_IRQ_2, 0 );
SETGATE(idt[IRQ_OFFSET + 3], 0, GD_KT, Trap_IRQ_3, 0 );
SETGATE(idt[IRQ_OFFSET + 4], 0, GD_KT, Trap_IRQ_4, 0 );
SETGATE(idt[IRQ_OFFSET + 5], 0, GD_KT, Trap_IRQ_5, 0 );
SETGATE(idt[IRQ_OFFSET + 6], 0, GD_KT, Trap_IRQ_6, 0 );
SETGATE(idt[IRQ_OFFSET + 7], 0, GD_KT, Trap_IRQ_7, 0 );
SETGATE(idt[IRQ_OFFSET + 8], 0, GD_KT, Trap_IRQ_8, 0 );
SETGATE(idt[IRQ_OFFSET + 9], 0, GD_KT, Trap_IRQ_9, 0 );
SETGATE(idt[IRQ_OFFSET + 10], 0, GD_KT, Trap_IRQ_10, 0 );
SETGATE(idt[IRQ_OFFSET + 11], 0, GD_KT, Trap_IRQ_11, 0 );
SETGATE(idt[IRQ_OFFSET + 12], 0, GD_KT, Trap_IRQ_12, 0 );
SETGATE(idt[IRQ_OFFSET + 13], 0, GD_KT, Trap_IRQ_13, 0 );
SETGATE(idt[IRQ_OFFSET + 14], 0, GD_KT, Trap_IRQ_14, 0 );
SETGATE(idt[IRQ_OFFSET + 15], 0, GD_KT, Trap_IRQ_15, 0 );
env_alloc()///
e->env_tf.tf_eflags |= FL_IF;
sched_halt()/
"pushl $0\n"
// Uncomment the following line after completing exercise 13
"sti\n"
"1:\n"
"hlt\n"
(3) 处理时钟中断
函数 user/spin
中子系统获取运行之后就不将控制权交给内核,我们需要编写时钟中断程序,迫使将控制权交给内核,然后我们才可以运行其他的进程。
系统中已经有的lapic_init
和 pic_init
函数,初始化时钟中断,我们仅需要完善发生中断之后的处理函数。
- Exercise 14
case (IRQ_OFFSET + IRQ_TIMER):
lapic_eoi();
sched_yield();
break;
这里强调一下,前面的实验一定要不出错,我之前因为在LAB 4 中,为了测试多CPU 的时候,从内核态切换到用户态解锁位置不同带来的影响,修改了
env_run()
中解锁的位置,但是没有修改过来,刚好从那个实验之后我都没有测试过make qemu CPUS=4
类似的命令,导致在这边make grade
的时候,stresssched 一直不通过,报错误的原因是在fork ()
函数中的pgfault(0
函数中的判读是否是写时复制 页面的时候出错,我一直以为是我这部分的内容错误了,来回检查,包括系统调用页面分配等,后来只能无意中发现,如果我单CPU 运行的话结果和理想的一样,可是我之前多CPU 是可以运行的,回去看我之前的实验笔记,看到 锁的部分,想起来我当时做的实验验证。当时想验证解锁位置不同的带来的影响,当时还没有得出很重要的结论,貌似上下一两条代码区别不大,可是在这个实验的时候,问题就很大了。我当时将解锁的位置放在了 代码lcr3(PADDR(curenv->env_pgdir));
前面是错误的,这里解锁的位置很关键,一定要注意。
2、进程间通信
- Exercise 15
(1)sys_ipc_recv
static int sys_ipc_recv(void *dstva)
{
// LAB 4: Your code here.
if((uintptr_t)dstva < UTOP && ((uintptr_t)dstva & 0xFFF)!= 0)
return -E_INVAL;
curenv->env_ipc_recving = 1;
curenv->env_ipc_from = 0;
curenv->env_ipc_dstva = dstva;
curenv->env_status = ENV_NOT_RUNNABLE;
sched_yield();
//panic("sys_ipc_recv not implemented");
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.
struct Env * recv = NULL;
//int r = envid2env(envid, &recv, 0);
if ((envid2env(envid, &recv, 0)) < 0)
return -E_BAD_ENV;
if(!recv->env_ipc_recving || recv->env_ipc_from != 0)
return -E_IPC_NOT_RECV;
if ((uintptr_t)srcva < UTOP) {
if((uintptr_t)srcva & 0xFFF)
return -E_INVAL;
pte_t *pte = NULL;
struct PageInfo *page = page_lookup(curenv->env_pgdir, srcva, &pte);
if(!page)
return -E_INVAL;
if ((*pte & (PTE_U | PTE_P)) == 0)
return -E_INVAL;
//if ((*pte & ~) != 0)
if((perm & ~(PTE_U | PTE_P | PTE_AVAIL | PTE_W)) > 0)
return -E_INVAL;
if (!(*pte & PTE_W) && (perm & PTE_W))
return -E_INVAL;
if (page_insert(recv->env_pgdir, page, recv->env_ipc_dstva, perm) < 0)
return -E_NO_MEM;
recv->env_ipc_perm = perm;
}
recv->env_ipc_recving = 0;
recv->env_ipc_from = curenv->env_id;
recv->env_ipc_value = value;
recv->env_status = ENV_RUNNABLE;
recv->env_tf.tf_regs.reg_eax = 0;
return 0;
//panic("sys_ipc_try_send not implemented");
}
(3)ipc_recv
int32_t ipc_recv(envid_t *from_env_store, void *pg, int *perm_store)
{
// LAB 4: Your code here.
int err;
if ((err=sys_ipc_recv(pg)) < 0) {
if (from_env_store) *from_env_store = 0;
if (perm_store) *perm_store = 0;
return err;
}
if (from_env_store)
*from_env_store = thisenv->env_ipc_from;
if (perm_store) {
if (pg) *perm_store = thisenv->env_ipc_perm;
else *perm_store = 0;
}
return thisenv->env_ipc_value;
}
(4)ipc_send
void ipc_send(envid_t to_env, uint32_t val, void *pg, int perm)
{
int err;
while((err = sys_ipc_try_send(to_env, val, pg != NULL ? pg :(void *) UTOP, perm)) < 0 )
{
if(err != -E_IPC_NOT_RECV)//E_IPC_NOT_RECV
panic("ipc_send error: %e", err);
sys_yield();
}
//panic("ipc_send not implemented");
}
(5) make grade
实验完善代码 LAB2-4下载链接 提取码:79t8