目录
抢占式多任务和进程间通信 (IPC)
时钟中断和抢占
当一个进程分叉出一个子进程,一旦它获得对 CPU 的控制,它就会在一个紧密的循环中永远旋转。父环境和内核都不会重新获得 CPU。因此为了让内核抢占运行进程,强行夺回对 CPU 的控制,必须支持来自时钟硬件的外部硬件中断。
中断纪律
外部中断(即设备中断)称为 IRQ。有 16 个可能的 IRQ,编号为 0 到 15。从 IRQ 编号到 IDT 条目的映射不是固定的。 pic_init
在picirq.c映射的IRQ 0-15到IDT入口IRQ_OFFSET
通过IRQ_OFFSET+15
。IRQ_OFFSET
选择此选项是为了使设备中断不会与处理器异常重叠。
在 JOS 中,外部设备中断在内核中禁用,在用户空间中启用。外部中断由寄存器%eflags
的FL_IF
标志位控制。当该位被设置时,外部中断被使能。虽然可以通过多种方式修改该位,但由于我们的简化,仅通过在%eflags
进入和离开用户模式时保存和恢复寄存器的过程来处理它。
引导加载程序的第一条指令屏蔽了中断,到目前为止我们还没有重新启用它们。
为IRQ向IDT中添加中断处理,与LAB3中的方式相同,不做赘述。SETGATE(idt[IRQ_OFFSET+0],0,GD_KT,irq0,3);//istrap必须为0,因为i中断门会重置IF标志位 SETGATE(idt[IRQ_OFFSET+1],0,GD_KT,irq1,3); SETGATE(idt[IRQ_OFFSET+2],0,GD_KT,irq2,3); SETGATE(idt[IRQ_OFFSET+3],0,GD_KT,irq3,3); SETGATE(idt[IRQ_OFFSET+4],0,GD_KT,irq4,3); SETGATE(idt[IRQ_OFFSET+5],0,GD_KT,irq5,3); SETGATE(idt[IRQ_OFFSET+6],0,GD_KT,irq6,3); SETGATE(idt[IRQ_OFFSET+7],0,GD_KT,irq7,3); SETGATE(idt[IRQ_OFFSET+8],0,GD_KT,irq8,3); SETGATE(idt[IRQ_OFFSET+9],0,GD_KT,irq9,3); SETGATE(idt[IRQ_OFFSET+10],0,GD_KT,irq10,3); SETGATE(idt[IRQ_OFFSET+11],0,GD_KT,irq11,3); SETGATE(idt[IRQ_OFFSET+12],0,GD_KT,irq12,3); SETGATE(idt[IRQ_OFFSET+13],0,GD_KT,irq13,3); SETGATE(idt[IRQ_OFFSET+14],0,GD_KT,irq14,3); SETGATE(idt[IRQ_OFFSET+15],0,GD_KT,irq15,3); SETGATE(idt[IRQ_OFFSET+19],0,GD_KT,irq19,3);
保证进程在开启中断的情况下运行:e->env_tf.tf_eflags|=FL_IF;
最后,取消注释sched_halt() 中的sti指令,以便空闲 CPU 取消屏蔽中断。
处理时钟中断
对硬件进行编程以定期生成时钟中断,这将强制控制回到内核,在那里可以将控制切换到不同的用户环境。
lapic_init
:初始化多处理器时钟。
pic_init
:初始化8259A中断控制器。
中断处理:
case (IRQ_OFFSET+IRQ_TIMER): lapic_eoi();//确认中断 sched_yield();
进程间通信(IPC)
操作系统利用虚拟地址使每个程序都拥有一台机器。操作系统的另一个重要服务是允许程序在需要时相互通信,进行交互。Unix 管道模型是典型的例子。
JOS中的IPC
一个简单的进程间通信机制---通过实现两个系统调用,sys_ipc_recv
与 sys_ipc_try_send
. 然后现用户调用函数ipc_recv
和ipc_send
.JOS 的 IPC 机制可以相互发送的两种消息:单个 32 位值和可选的单个页面映射。
发送和接收消息
要接收消息,环境调用 sys_ipc_recv
. 这个系统调用取消了当前环境的调度,并且在收到消息之前不会再次运行它。当一个环境正在等待接收消息时, 任何其他环境都可以向它发送消息 - 不仅仅是特定环境,一个环境不能仅通过向其发送消息而导致另一个环境发生故障(除非目标环境也有问题)。
为了尝试发送一个值,环境调用 sys_ipc_try_send
接收者的环境 ID 和要发送的值。如果命名环境实际上正在接收(它已经调用 sys_ipc_recv
但尚未获得值),则发送将传递消息并返回 0。否则发送返回-E_IPC_NOT_RECV
以指示目标环境当前不期望接收值。
ipc_recv
用户空间中的 库函数将在当前环境负责调用sys_ipc_recv
,ipc_send
将负责重复调用sys_ipc_try_send
直到发送成功。
传输页面
当进程使用有效dstva
参数(UTOP
之下)调用sys_ipc_recv
时,进程表示它愿意接收页面映射。如果发送方发送一个页面,那么该页面应该被映射到接收方的dstva
中。如果接收方已经在dstva
处映射了一个页面,则该前一个页面将被取消映射。
当进程使用有效srcva
(如下UTOP
)调用sys_ipc_try_send
时,这意味着发送方希望将当前映射srcva
到的页面发送给接收方,并具有权限perm
。IPC 成功后,发送方srcva
在其地址空间中保留其对页面的原始映射,但接收方也在dstva
接收方地址空间中的接收方最初指定的同一物理页面上获得了该物理页面的映射。因此,此页面在发送方和接收方之间共享。
如果发送方或接收方未指示应传送页面,则不传送页面。在任何 IPC 之后,内核将接收者Env
结构中的env_ipc_perm
设置为接收到的页面的权限,如果没有接收到页面,则为零。
IPC实现
envid2env
标志设置为 0,这意味着任何环境都可以向任何其他环境发送 IPC 消息,并且内核除了验证目标环境是否有效之外,不会进行特殊的权限检查。
在实现通信之前,首先看一下Env新的字段:
struct Env { //…………………… // Lab 4 IPC bool env_ipc_recving; // 进程被阻塞接收 void *env_ipc_dstva; // 进程映射页面的va uint32_t env_ipc_value; // 进程发送的数据 envid_t env_ipc_from; // 发送信息的进程 int env_ipc_perm; // 页面映射的权限 };
sys_ipc_try_send:
// Try to send 'value' to the target env 'envid'. // If srcva < UTOP, then also send page currently mapped at 'srcva', // so that receiver gets a duplicate mapping of the same page. // // The send fails with a return value of -E_IPC_NOT_RECV if the // target is not blocked, waiting for an IPC. // // The send also can fail for the other reasons listed below. // // Otherwise, the send succeeds, and the target's ipc fields are // updated as follows: // 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; // env_ipc_perm is set to 'perm' if a page was transferred, 0 otherwise. // The target environment is marked runnable again, returning 0 // from the paused sys_ipc_recv system call. (Hint: does the // sys_ipc_recv function ever actually return?) // // If the sender wants to send a page but the receiver isn't asking for one, // then no page mapping is transferred, but no error occurs. // The ipc only happens when no errors occur. // // Returns 0 on success, < 0 on error. // Errors are: // -E_BAD_ENV if environment envid doesn't currently exist. // (No need to check permissions.) // -E_IPC_NOT_RECV if envid is not currently blocked in sys_ipc_recv, // or another environment managed to send first. // -E_INVAL if srcva < UTOP but srcva is not page-aligned. // -E_INVAL if srcva < UTOP and perm is inappropriate // (see sys_page_alloc). // -E_INVAL if srcva < UTOP but srcva is not mapped in the caller's // address space. // -E_INVAL if (perm & PTE_W), but srcva is read-only in the // current environment's address space. // -E_NO_MEM if there's not enough memory to map srcva in envid's // address space. static int sys_ipc_try_send(envid_t envid, uint32_t value, void *srcva, unsigned perm) { // LAB 4: Your code here. struct Env* e; int r; // envid_t pid=sys_getenvid(); pte_t* pte; struct PageInfo* pp; if((r=envid2env(envid,&e,0))<0){ panic("envid not exsit!"); return -E_BAD_ENV;} if(e->env_ipc_recving!=true)//如果目标进程不在接收信息的状态 return -E_IPC_NOT_RECV; if((uintptr_t)srcva>=UTOP){ perm=0; goto RUN; } if((uintptr_t)srcva<UTOP&&((uintptr_t)srcva%PGSIZE!=0)) return -E_INVAL; if((perm&(PTE_P|PTE_U))!=(PTE_P|PTE_U)) return -E_INVAL; if(!(pp=page_lookup(curenv->env_pgdir,srcva,&pte))&&(uintptr_t)srcva<UTOP)//共享页面的权限,找出物理页 return -E_INVAL; if((uintptr_t)srcva<UTOP&&(perm&PTE_W)&&!(*pte&PTE_W)) return -E_INVAL; if((uintptr_t)srcva<UTOP){ if((perm&(PTE_P|PTE_U))!=(PTE_P|PTE_U)) return -E_INVAL; if((perm&~PTE_SYSCALL)!=0) return -E_INVAL; } // if((uintptr_t)srcva<UTOP&&(r=sys_page_map(pid,srcva,envid,e->env_ipc_dstva,perm))!=0){ // e->env_ipc_perm=perm; // return -E_INVAL; // } if((uintptr_t)(e->env_ipc_dstva)<UTOP){//目标页面愿意接受 if((r=page_insert(e->env_pgdir,pp,e->env_ipc_dstva,perm))<0)//增加物理页面的映射,物理页面的映射可以有多个 return r; } else perm=0; // if((r=sys_page_alloc(envid,srcva,1))<0) // return -E_NO_MEM; RUN: e->env_ipc_from=curenv->env_id; e->env_ipc_recving=false;//已经接收 // e->env_ipc_from=pid; e->env_ipc_perm=perm;//页面权限 e->env_ipc_value=value;//页面值 e->env_status=ENV_RUNNABLE; //目标进程返回值 e->env_tf.tf_regs.reg_eax=0; return 0; // panic("sys_ipc_try_send not implemented"); }
sys_ipc_recv:
// Block until a value is ready. Record that you want to receive // using the env_ipc_recving and env_ipc_dstva fields of struct Env, // mark yourself not runnable, and then give up the CPU. // // If 'dstva' is < UTOP, then you are willing to receive a page of data. // 'dstva' is the virtual address at which the sent page should be mapped. // // This function only returns on error, but the system call will eventually // return 0 on success. // Return < 0 on error. Errors are: // -E_INVAL if dstva < UTOP but dstva is not page-aligned. static int sys_ipc_recv(void *dstva) { if((uintptr_t)dstva<UTOP&&((uintptr_t)dstva%PGSIZE!=0)) return -E_INVAL; curenv->env_ipc_recving=true;//准备接收 curenv->env_ipc_dstva=dstva;//接受页面的映射地址 curenv->env_status=ENV_NOT_RUNNABLE;//将进程阻塞 curenv->env_ipc_from=0; sched_yield();//这个进程阻塞后,系统运行其他进程 return 0; }
void ipc_send(envid_t to_env, uint32_t val, void *pg, int perm) { int r; // int cnt=0; if(!pg) pg=(void*)UTOP;//如果发送的不是页面,那么映射到UTOP处 while((r=sys_ipc_try_send(to_env,val,pg,perm))<0){//需要一直检查接收方是否准备好接收信息 if(r!=-E_IPC_NOT_RECV) panic("error happened when send value!%e\n",r); sys_yield();//调度进程 } }
int32_t ipc_recv(envid_t *from_env_store, void *pg, int *perm_store) { // LAB 4: Your code here. int r; if(!pg){ pg=(void*)UTOP; } r=sys_ipc_recv(pg); if(from_env_store) *from_env_store=r<0?0:thisenv->env_ipc_from; if(perm_store) *perm_store=r<0?0:thisenv->env_ipc_perm; if(r<0) return r; return thisenv->env_ipc_value;//返回接收值 }