现在有很多进程间通信的模式,但是我们选择一个简单的IPC机制(共享内存)来实现,并让它工作起来。
简单来讲我们实现了两个系统调用(不可避免地需要我们完善IDT),发送方查看接受方是否接收,并选择是否发送消息。本质是修改目标进程的页表,实现同一块物理内存的映射。
这两个函数命为sys_ipc_recv()和sys_ipc_try_send(),顾名思义,一个负责发送,一个负责接收。
发送函数sys_ipc_try_send
static int
sys_ipc_try_send(envid_t envid, uint32_t value, void *srcva, unsigned perm) // 接收消息方进程为envid
{// LAB 4: Your code here.struct Env *env;if (envid2env(envid, &env, 0) < 0) return -E_BAD_ENV; // 指定进程要存在,并将其赋值给envif (!env->env_ipc_recving) return -E_IPC_NOT_RECV; // 对方是否要接收if ((size_t) srcva < UTOP) {if (((size_t) srcva % PGSIZE) != 0) {return -E_INVAL;}if ((perm & PTE_U) != PTE_U || (perm & PTE_P) != PTE_P) return -E_INVAL; // 权限检查pte_t *pte;struct PageInfo *pp = page_lookup(curenv->env_pgdir, srcva, &pte);if (!pp) return -E_INVAL;if ((perm & PTE_W) && ((size_t) *pte & PTE_W) != PTE_W) return -E_INVAL; // 权限检查if ((size_t) env->env_ipc_dstva < UTOP) { // 接收消息方虚拟地址要合理if (page_insert(env->env_pgdir, pp, env->env_ipc_dstva, perm) < 0) return -E_NO_MEM; // 补充目标进程的页表env->env_ipc_perm = perm;}} else {env->env_ipc_perm = 0;}env->env_ipc_from = curenv->env_id;env->env_ipc_recving = 0; // 不再等待env->env_ipc_value = value; // 消息值是多少env->env_status = ENV_RUNNABLE; // 就绪态env->env_tf.tf_regs.reg_eax = 0;return 0;
}
接收函数sys_ipc_recv
static int
sys_ipc_recv(void *dstva)
{// LAB 4: Your code here.// 要映射的目的虚拟地址必须小于UTOPif ((size_t) dstva < UTOP && ((size_t) dstva % PGSIZE) != 0) return -E_INVAL;curenv->env_ipc_recving = 1; // 我正在等待接收消息curenv->env_ipc_dstva = dstva; // 希望将页面映射到这个虚拟地址中curenv->env_status = ENV_NOT_RUNNABLE; // 因为我在等待,所以我阻塞自己,这样做不浪费cpu资源sys_yield(); // 主动阻塞自己,操作系统调度其他进程// 内核函数当然可以直接调用内核函数return 0;
}
除此之外需要包装两个函数
int32_t
ipc_recv(envid_t *from_env_store, void *pg, int *perm_store)
{// LAB 4: Your code here.if (pg == NULL) {pg = (void *)-1;}int r = sys_ipc_recv(pg);if (r < 0) { //系统调用失败if (from_env_store) *from_env_store = 0;if (perm_store) *perm_store = 0;return r;}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;
}void
ipc_send(envid_t to_env, uint32_t val, void *pg, int perm)
{// LAB 4: Your code here.if (pg == NULL) {pg = (void *)-1;}int r;while(1) {r = sys_ipc_try_send(to_env, val, pg, perm);if (r == 0) { //发送成功return;} else if (r == -E_IPC_NOT_RECV) { //接收进程没有准备好sys_yield();} else { //其它错误panic("ipc_send():%e", r);}}
}
本质其实就是实现地址映射,在网上看到一个很好的图,记录一下