6.828 Lab4 PartC (Preemptive Multitasking and Inter-Process communication) Writeup
Preemptive Multitasking and Inter-Process communication
PartC 任务是实现一个抢占式的多任务处理和进程间通信。
Concepts: interrupt
, ipc
, preemptive_multitasking
.
Exercise 13,14
Something need to konw
为了防止一个进程长时间的占用CPU而不释放,所以需要能够让kernel剥夺一个正在运行的进程占用CPU,为此,JOS kernel 支持外部硬件中断。在JOS 中,外部中断在inc/trap.h
中有定义。在JOS中,对IRQ做了一些化简,当在内核态下的时候,外部中断被禁止,在用户态下的时候,外部中断被允许。禁止和允许外部中断是通过设置EFLAG
寄存器中的IF
标志位实现的(详见intel x86的手册)。
Trap 和 Interrupt 的区别:
- 对于Interrupt,在通过Interrupt Gate的时候,会把
EFLAGS
中的IF
标志位清零,不允许中断。 - 对于Trap,不会修改
IF
标志位。
在JOS中,通过SETGATE中的istrap可以设置。
#define STS_CG32 0xC // 32-bit Call Gate
#define STS_IG32 0xE // 32-bit Interrupt Gate
#define STS_TG32 0xF // 32-bit Trap Gate
// Set up a normal interrupt/trap gate descriptor.
// - istrap: 1 for a trap (= exception) gate, 0 for an interrupt gate.
// see section 9.6.1.3 of the i386 reference: "The difference between
// an interrupt gate and a trap gate is in the effect on IF (the
// interrupt-enable flag). An interrupt that vectors through an
// interrupt gate resets IF, thereby preventing other interrupts from
// interfering with the current interrupt handler. A subsequent IRET
// instruction restores IF to the value in the EFLAGS image on the
// stack. An interrupt through a trap gate does not change IF."
// - sel: Code segment selector for interrupt/trap handler
// - off: Offset in code segment for interrupt/trap handler
// - dpl: Descriptor Privilege Level -
// the privilege level required for software to invoke
// this interrupt/trap gate explicitly using an int instruction.
#define SETGATE(gate, istrap, sel, off, dpl) \
{ \
(gate).gd_off_15_0 = (uint32_t) (off) & 0xffff; \
(gate).gd_sel = (sel); \
(gate).gd_args = 0; \
(gate).gd_rsv1 = 0; \
(gate).gd_type = (istrap) ? STS_TG32 : STS_IG32; \
(gate).gd_s = 0; \
(gate).gd_dpl = (dpl); \
(gate).gd_p = 1; \
(gate).gd_off_31_16 = (uint32_t) (off) >> 16; \
}
Exercise 13
初始化IDT使得JOS能够handle外部中断。在kern/trapentry.S
中添加相应的条目,同时在kern/trap.c
中初始化interrupt gate, 注意在SETGATE的时候,istrap要置位0表示这是一个Interrupt,在进入内核态之后,外部中断被禁止。同时在kern/env.c
中的env_alloc
中初始化eflags。当在进入trap的时候,保存进程的trapframe
,其中包含eflags
,这样在中断处理结束之后,从内核态转到用户态的时候,通过trapframe
恢复eflags
,这样就保证了在恢复到用户态的时候,允许外部中断。
// Enable interrupts while in user mode.
// LAB 4: Your code here.
// Set FL_IF, so when return in user mode, eflags will be enabled.
e->env_tf.tf_eflags = e->env_tf.tf_eflags | FL_IF;
Exercise 14
为了能够让kernel拿到CPU的控制权,需要每隔一段时间差生一个外部中断,在内核初始化的时候,通过调用lapic_init
和pic_init
设置好了硬件会每隔一段时间产生外部中断,这个Exercise要做的就是在产生中断的时候,通过IDT找到中断处理函数,让内核拿到CPU的控制权,调度其他进程。
// Handle clock interrupts. Don't forget to acknowledge the
// interrupt using lapic_eoi() before calling the scheduler!
// LAB 4: Your code here.
if (tf->tf_trapno == IRQ_OFFSET + IRQ_TIMER){
lapic_eoi();
sched_yield();
return ;
}
Exercise 15
Something need to know
这部分是实现IPC,在JOS中,实现IPC,需要实现sys_ipc_recv
和sys_ipc_try_send
系统调用,和两个lib的包装函数ipc_recv
和ipc_send
。IPC的简要过程:
- sendenv 通过调用 ipc_send, 因为可能recvenv还没有执行到ipc_recv,所以recvenv的
env_ipc_recving
字段还是false
,ipc_send就需要不断的调用sys_ipc_try_send来发送消息 - recvenv 通过调用 ipc_recv,ipc_recv调用 sys_ipc_recv系统调用,修改自己env 中的状态信息。
- 当recvenv的
env_ipc_recving
为true
的时候,kernel负责将32bit value 和 page mapping 传递给recvenv,同时将recvenv的状态修改。使得recvenv可以被调度。
Exercise 15
IPC传送的消息包含两部分:32 bit value 和 single page mapping。 page mapping 作为消息的一部,可以实现任意两个进程共享一块内存,同时,如果传送的消息比较大,可以通过page mapping 实现消息传送。
为了实现IPC,在struct Env
中,多出了以下字段:
// Lab 4 IPC
bool env_ipc_recving; // Env is blocked receiving,表示env等待消息中。如果env_ipc_recving位false,那么任何发送进程都不能将消息发送到这个env中。
void *env_ipc_dstva; // VA at which to map received page. 消息中的page mapping 部分
uint32_t env_ipc_value; // Data value sent to us, 消息中的 32bit value 部分
envid_t env_ipc_from; // envid of the sender,如果收到消息,记录消息的发送者
int env_ipc_perm; // Perm of page mapping received, 消息中的page mapping 部分
注意在env_alloc
中,初始化对应的struct Env
字段。
sys_ipc_recv : 进程通过调用这个系统调用,实现接受消息。当进程调用这个系统调用的时候,内核会阻塞这个进程,将env的状态设置位ENV_NOT_RUNNABLE
,直到接受到任何进程发送的消息为止,这个进程才能得到调度。注意,对发送消息的进程没有parent 进程的限制,任何进程都能发送消息给任何进程(在JOS中)。注意对参数的检查。如果不接受传递的page mapping,只需要将dstva
置为UTOP
就可以了,
// 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)
{
// LAB 4: Your code here.
// check dstva
if ((uintptr_t)dstva < UTOP && (PGOFF(dstva) != 0))
return -E_INVAL;
// if dstva >= UTOP , do nothing.
// Record this env want to receive
curenv->env_ipc_recving = true;
curenv->env_ipc_dstva = dstva;
// Block this env, and giveup CPU
curenv->env_status = ENV_NOT_RUNNABLE;
return 0;
}
sys_ipc_try_send : 检查参数,同时传递32bit value 和 page mapping。这里有个trick,因为srcva的范围必须在UTOP
之下,所以如何判断是否进行page mapping 呢?只要传递srcva >= UTOP 就可以了,只要让系统调用对这种情况不做处理,不抛出-E_INVAL
。
static int
sys_ipc_try_send(envid_t envid, uint32_t value, void *srcva, unsigned perm)
{
// LAB 4: Your code here.
int r;
struct Env * dstenv;
pte_t * pte;
struct PageInfo *pp;
// Check parameters
// Check envid and env status
r = envid2env(envid, &dstenv, 0);
if (r < 0)
return -E_BAD_ENV;
if (!dstenv->env_ipc_recving)
return -E_IPC_NOT_RECV;
// Check srcva and perm. notice : if srcva >= UTOP, do nothing.
if ((uintptr_t)srcva < UTOP) {
if (PGOFF(srcva) != 0)
return -E_INVAL;
if ((perm & (PTE_U | PTE_P)) != (PTE_U | PTE_P))
return -E_INVAL;
if (((perm | PTE_SYSCALL) != PTE_SYSCALL))
return -E_INVAL;
// Check physical page exist
pp = page_lookup(curenv->env_pgdir, srcva, &pte);
if ((uintptr_t)srcva < UTOP && !pp)
return -E_INVAL;
// Check perm write conflict
if ((perm & PTE_W) && !(*pte & PTE_W))
return -E_INVAL;
// Send mapping
if (dstenv->env_ipc_dstva) {
// Do page map
r = page_insert(dstenv->env_pgdir, pp, dstenv->env_ipc_dstva, perm);
if (r < 0)
return -E_NO_MEM;
// Make page perm
dstenv->env_ipc_perm = perm;
}
}
// If srcva >= UTOP, no mapping transfered and no errors.
dstenv->env_ipc_recving = false;
dstenv->env_ipc_value = value;
dstenv->env_ipc_from = curenv->env_id;
dstenv->env_status = ENV_RUNNABLE;
return 0;
}
lib 库对ipc的包装
ipc_recv : 如果不需要page mapping,那么在系统调用的时候,dstva
传递UTOP
。
// Receive a value via IPC and return it.
// If 'pg' is nonnull, then any page sent by the sender will be mapped at
// that address.
// If 'from_env_store' is nonnull, then store the IPC sender's envid in
// *from_env_store.
// If 'perm_store' is nonnull, then store the IPC sender's page permission
// in *perm_store (this is nonzero iff a page was successfully
// transferred to 'pg').
// If the system call fails, then store 0 in *fromenv and *perm (if
// they're nonnull) and return the error.
// Otherwise, return the value sent by the sender
//
// Hint:
// Use 'thisenv' to discover the value and who sent it.
// If 'pg' is null, pass sys_ipc_recv a value that it will understand
// as meaning "no page". (Zero is not the right value, since that's
// a perfectly valid place to map a page.)
int32_t
ipc_recv(envid_t *from_env_store, void *pg, int *perm_store)
{
// LAB 4: Your code here.
int r;
// Send recv system call
if (pg)
r = sys_ipc_recv(pg);
else
r = sys_ipc_recv((void*)UTOP);
if (r < 0) {
if (from_env_store)
*from_env_store = 0;
if (perm_store)
*perm_store = 0;
return r;
}
// At this time, thisenv is blocked, waiting for an env to send msg to
// it and change ENV_STATUS. So it can be scheduled.
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;
}
ipc_send : 如果返回-E_IPC_NOT_RECV
,表示接受消息的进程还没有struct Env
中的env_ipc_recving
,需要继续try。
// Send 'val' (and 'pg' with 'perm', if 'pg' is nonnull) to 'toenv'.
// This function keeps trying until it succeeds.
// It should panic() on any error other than -E_IPC_NOT_RECV.
//
// Hint:
// Use sys_yield() to be CPU-friendly.
// If 'pg' is null, pass sys_ipc_recv a value that it will understand
// as meaning "no page". (Zero is not the right value.)
void
ipc_send(envid_t to_env, uint32_t val, void *pg, int perm)
{
// LAB 4: Your code here.
int r = -E_IPC_NOT_RECV;
while(r < 0){
if (r != -E_IPC_NOT_RECV)
panic("ipc_send : sys_ipc_try_send error : %e.\n",r );
if (pg)
r = sys_ipc_try_send(to_env, val, pg, perm);
else
r = sys_ipc_try_send(to_env, val, (void*)UTOP, perm);
}
}
– EOF –