现在有很多进程间通信的模式,但是我们选择一个简单的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; // 指定进程要存在,并将其赋值给env
if (!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.
// 要映射的目的虚拟地址必须小于UTOP
if ((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);
}
}
}
本质其实就是实现地址映射,在网上看到一个很好的图,记录一下