Preemptive Multitasking and Inter-Process communication (IPC)
完成了前面的实验后,我们通过用户进程 user/spin 发现其创建的子进程一旦运行,一 直死循环,父进程和内核都不能得到 CPU 继续运行了,这显然不是操作系统应该有的情况。 为了允许内核抢占一个正在运行的进程,在实验 4 的最后部分,我们需要修改内核,使其在 时钟中断中调用调度函数,实现可剥夺的调度,并完成进程间的消息通信。
可剥夺调度也就是抢占调度方式,在这种调度方式中,进程调度程序可根据某种原则停 止正在执行的进程,将已分配给当前进程的处理机收回,重新分配给另一个处于就绪状态的 进程。在 JOS 中我们的抢占原则是时间片原则:即各进程按系统分配给的一个时间片运行, 当该时间片用完或由于该进程等待某事件发生而被阻塞时,系统就停止该进程的执行而重新 进行调度。此处需要用到外部中断,时钟中断。 外部中断(如:设备中断)一般是指 IRQs,在每台 PC 的系统中,是由一个中断控制 器 8259 或是 8259A 的芯片来控制系统中每个硬件的中断。目前共有 16 组 IRQ,去掉其中 用来作桥接的一组 IRQ,实际上只有 15 组 IRQ 可供硬件调用。在 picirq.c 中 JOS 系统将 IRQs0-15 映射到了 IDT 表中的 IRQ_OFFSET 到 IRQ_OFFSET+15(即 32-47)。 在 JOS 系统中,在内核态时一直是禁止外部中断的,外部中断是由 eflags 标志寄存器 的 FL_IF 位控制的,在 JOS 中我们只在进入和离开用户模式时保存和恢复 eflags 寄存器。 并确保 FL_IF 标志在用户进程运行时置位,这样当时钟中断时,内核可以得到处理器继续 调度其他进程。 我们需要修改 kern/trapentry.S 和 kern/trap.c 来初始化 IDT 中的外部中断,所有的外部中 断统一调用调度函数,系统中每隔 10HZ 发生一次时钟中断,这样,内核可以获得处理器来 调度其他进程。
在前面,JOS 系统主要是考虑操作系统隔离性,即每一个进程都好像独占一台机器一样, 而操作系统另一个重要的服务,进程之间相互通信即 IPC(inter-process communication):进 程间通信。允许进程通信,进程间可以合作完成一个整体的任务。 传统上有许多进程间通信的方式,比如信号量,管道等,我们在 JOS 系统中使用简单的消息传递来实现进程间通信。在 JOS 的 IPC 机制中传送和几首的消息包括两类:一个是 单字和一个页的映射;后者可以高效传送大量的数据。
实现 IPC 我们首先需要完成 kern/syscall.c 中的 sys_ipc_recv 和 sys_ipc_can_send 系统调用,其说明如下:
static int sys_ipc_recv(void *dstva)
阻塞调用进程直到接收到一个消息,然后设置 env 中相应项,调用调度函数。
注意:此处要设置返回值 eax 为 0,即永远不执行 return 语句,直接调用调度函数即可。
static int sys_ipc_try_send(envid_t envid, uint32_t value, void *srcva, unsigned perm)
发送一个消息到 envid 进程,当 srcva 为 0 时,传送一个单字,否则传送一页,即将当 前进程 srcva 地址处的页映射到接收进程同一地址处。 注意:在以上函数中当调用 envid2env 时设置 checkperm 为 0,这样任何进程都可以发 送消息给任何其他进程,内核仅仅检查目标进程是否存在。 一个进程可以调用 sys_ipc_recv 接收一个消息,这个系统调用将会阻塞当前进程,除非 它接收到一个消息。单一个进程等待接收一个消息时,其他任何进程都可以发送消息给它, 在 JOS 系统中没有特殊的要求。与接收一个消息类似,一个进程调用 sys_ipc_can_send 发送 一个消息给指定的进程,如果指定的进程正在阻塞等待消息,则发送消息并返回 0。 在完成了系统调用中的 IPC 之后,为了使用户进程可以相互通信,我们需要完成用户 态下的 IPC,即 lib/ipc.c 文件
int32_t ipc_recv(envid_t *from_env_store, void *pg, int *perm_store)
使用 sys_ipc_revc 系统调用接收消息,并返回接收的 value。 void ipc_send(envid_t to_env, uint32_t val, void *pg, int perm) 使用 sys_ipc_try_send 系统调用发送 val 到进程 to_env,直到发送成功。