持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第12天,点击查看活动详情
前言
这篇是后来又重新补上的文章,是整个系列的靠后的一小部分了。在这个实验的最后一部分,您将修改内核以抢占不合作的环境,并允许环境显式地相互传递消息。 紧接着还有 进程间通信(IPC) 就不知道又要来填哪篇文章了
时钟中断和抢占
运行user/spin 测试程序。这个测试程序创建出一个子进程,一旦它接收到CPU的控制,它就会在一个紧密的循环中循环执行。父进程和内核都无法重新获得CPU。就保护系统免受用户模式环境中的错误或恶意代码的影响而言,这显然不是一个理想的情况,因为任何用户模式环境都可以通过进入无限循环而永远不归还CPU,从而使整个系统停止运行。为了允许内核抢占运行环境,从中强制重新获得CPU控制权,我们必须扩展JOS内核以支持时钟硬件。
中断准则
外部中断(即设备中断)称为IRQ。有16个可能的IRQ,编号为0到15。从IRQ编号到IDT条目的映射不固定。picirq.c中的pic_init通过IRQ_OFFSET+15将IRQ 0-15映射到IDT条目IRQ_OFFSET。
在inc/trap.h中 IRQ_OFFSET被定义为十进制32。因此,IDT条目32-47对应于IRQs 0-15。例如,时钟中断为IRQ 0。因此,IDT[IRQ_OFF SET+0](即IDT[32])包含内核中时钟中断处理程序例程的地址。选择此IRQ_OFFSET是为了使设备中断不会与处理器异常重叠,这显然会导致混淆。(事实上,在PC运行MS-DOS的早期,IRQ_OFFSET实际上为零,这确实造成了处理硬件中断和处理处理器异常之间的巨大混淆!)
在JOS中,与xv6 Unix相比,我们做了一个关键的简化。外部设备中断在内核中总是禁用的(和xv6一样,在用户空间中也是启用的)。外部中断由%eflags寄存器的FL_IF标志位控制(见inc/mmu.h)。当该位被设置时,外部中断被启用。虽然位可以通过多种方式修改,但由于我们的简化,我们将只通过在进入和离开用户模式时保存和恢复%eflags寄存器的过程来处理它。
您必须确保在用户环境中运行时设置FL_IF标志,以便在中断到达时,它被传递到处理器并由中断代码处理。否则,中断将被屏蔽或忽略,直到中断重新启用。我们用bootloader的第一条指令屏蔽了中断,到目前为止,我们还没有时间重新启用它们。
Exercise 13. Modify kern/trapentry.S and kern/trap.c to initialize the appropriate entries in the IDT and provide handlers for IRQs 0 through 15. Then modify the code in env_alloc() in kern/env.c to ensure that user environments are always run with interrupts enabled.
TRAPHANDLER_NOEC(irq_0_handler, IRQ_OFFSET + 0);
TRAPHANDLER_NOEC(irq_1_handler, IRQ_OFFSET + 1);
TRAPHANDLER_NOEC(irq_2_handler, IRQ_OFFSET + 2);
TRAPHANDLER_NOEC(irq_3_handler, IRQ_OFFSET + 3);
TRAPHANDLER_NOEC(irq_4_handler, IRQ_OFFSET + 4);
TRAPHANDLER_NOEC(irq_5_handler, IRQ_OFFSET + 5);
TRAPHANDLER_NOEC(irq_6_handler, IRQ_OFFSET + 6);
TRAPHANDLER_NOEC(irq_7_handler, IRQ_OFFSET + 7);
TRAPHANDLER_NOEC(irq_8_handler, IRQ_OFFSET + 8);
TRAPHANDLER_NOEC(irq_9_handler, IRQ_OFFSET + 9);
TRAPHANDLER_NOEC(irq_10_handler, IRQ_OFFSET + 10);
TRAPHANDLER_NOEC(irq_11_handler, IRQ_OFFSET + 11);
TRAPHANDLER_NOEC(irq_12_handler, IRQ_OFFSET + 12);
TRAPHANDLER_NOEC(irq_13_handler, IRQ_OFFSET + 13);
TRAPHANDLER_NOEC(irq_14_handler, IRQ_OFFSET + 14);
TRAPHANDLER_NOEC(irq_15_handler, IRQ_OFFSET + 15);
首先在trapentry.S中仿照之前设置异常处理向量的方式即可,定义0-15这16个中断例程。
SETGATE(idt[IRQ_OFFSET + 0], 0, GD_KT, irq_0_handler, 0);
SETGATE(idt[IRQ_OFFSET + 1], 0, GD_KT, irq_1_handler, 0);
SETGATE(idt[IRQ_OFFSET + 2], 0, GD_KT, irq_2_handler, 0);
SETGATE(idt[IRQ_OFFSET + 3], 0, GD_KT, irq_3_handler, 0);
SETGATE(idt[IRQ_OFFSET + 4], 0, GD_KT, irq_4_handler, 0);
SETGATE(idt[IRQ_OFFSET + 5], 0, GD_KT, irq_5_handler, 0);
SETGATE(idt[IRQ_OFFSET + 6], 0, GD_KT, irq_6_handler, 0);
SETGATE(idt[IRQ_OFFSET + 7], 0, GD_KT, irq_7_handler, 0);
SETGATE(idt[IRQ_OFFSET + 8], 0, GD_KT, irq_8_handler, 0);
SETGATE(idt[IRQ_OFFSET + 9], 0, GD_KT, irq_9_handler, 0);
SETGATE(idt[IRQ_OFFSET + 10], 0, GD_KT, irq_10_handler, 0);
SETGATE(idt[IRQ_OFFSET + 11], 0, GD_KT, irq_11_handler, 0);
SETGATE(idt[IRQ_OFFSET + 12], 0, GD_KT, irq_12_handler, 0);
SETGATE(idt[IRQ_OFFSET + 13], 0, GD_KT, irq_13_handler, 0);
SETGATE(idt[IRQ_OFFSET + 14], 0, GD_KT, irq_14_handler, 0);
SETGATE(idt[IRQ_OFFSET + 15], 0, GD_KT, irq_15_handler, 0);
之后在IDT表中注册这16个中断例程。
e->env_tf.tf_eflags |= FL_IF;
最后在env_alloc中打开FL_IF位以供中断。
注意:还要取消注释sched_halt()中的sti指令,以便空闲CPU取消屏蔽中断。
当调用硬件中断处理程序时,处理器从不推送错误代码。此时,您可能需要重新阅读《80386参考手册》的第9.2节,或《IA-32英特尔体系结构软件开发人员手册》第3卷的第5.8节。
完成此练习后,如果您使用任何运行时间非常长的测试程序(例如spin)运行内核,您应该会看到硬件中断的内核打印异常帧。虽然处理器中现在启用了中断,但JOS还没有处理它们,所以您应该看到它将每个中断错误地归因于当前运行的用户环境并将其销毁。最终,它将耗尽环境,无法销毁并掉入监视器。
处理时钟中断
在user/spin程序中,在第一次运行子进程后,它只是在循环中旋转,内核再也没有得到控制权。我们需要对硬件进行编程,以定期生成时钟中断,这将迫使控制返回内核,在那里我们可以将控制切换到不同的用户环境。我们为您编写的对lapic_init和pic_int(来自init.c中的i386_init)的调用设置了时钟和中断控制器以生成中断。现在需要编写代码来处理这些中断。
Exercise 14. Modify the kernel’s trap_dispatch() function so that it calls sched_yield() to find and run a different environment whenever a clock interrupt takes place.
case IRQ_OFFSET + IRQ_TIMER:
lapic_eoi();
sched_yield();
break;
在trap_dispatch()中添加如下情况,以支持时钟中断的相应。其中lapic_eoi函数用来支持中断,之后调用sched_yield重新调度下一个进程。
现在您应该能够让user/spin 测试工作了:父进程应该将子进程杀死,sys_yield()调用轮转几次,但在每种情况下,都会在一个时间片后重新获得对CPU的控制,最后杀死子进程并优雅地终止。
这是进行回归测试的好时机。确保您没有通过启用中断来破坏该Lab以前工作的任何部分(例如:forktree)。另外,尝试使用make CPUS=2 target运行多个CPU。你现在也应该能stresssched。