文章目录
时间越来越不够了,无法详解,仅记录一些梳理过程。
(一)syscall流程图
(以writef())为例
(二)fork区分进程
(newenvid = syscall_env_alloc()) == 0
这个进程可以分为三个部分:
①sys_env_alloc()->eax(v0)
②eax(v0)->newenvid
③if(newenvid==0)
父进程在①触发了异常,会在syscall.S/lib.c
中修改父进程的env_tf.cp0_epc
,使它记录的是②的位置。
在sys_env_alloc()
中,父进程做了:
- 申请一个子进程e;
- 设置e的状态为
ENV_NOT_RUNNABLE
; - 将环境(①发生之前)复制给e;
e -> env_tf.pc = e -> env_tf.cp0_epc;
让子进程被设置为ENV_NOT_RUNNABLE
的时候从②开始执行;e -> env_tf.regs[2] = 0;
将子进程的v0寄存器设置为0,即子进程的eax(v0)被设置为0;- 设置e的优先级/时间片
return e->env_id;
将父进程的v0寄存器即eax(v0)被设置为0.
当父进程从异常返回时或子进程刚被调用时,它们都是从②开始执行,但是获得了不同的eax(v0)。所以便能有效区分了。
(三)缺页中断
1.所有缺页流程
2.cow类型缺页处理函数流程
在fork产生亲自关系后,需要在子进程runnable之前设置写时复制机制。
- 为父进程注册pgfault_handler
- 复制父进程的页给子进程,并修改双方的权限(按情况增加COW)
- 为子进程申请一页(映射到UXSTACKTOP下面那个BY2PG)作为异常处理栈。权限是PTE_V|PTE_R,有效且只读。
- 为子进程注册pgfault_handler
这里设计了很多长得很像的函数,注意辨析!
(1)注册缺页处理函数
目的:为目标进程设置两个参数env -> env_pgfault_handler
和env -> env_xstacktop
,使得发生COW类型缺页中断时可以在正确的栈里调用正确的处理函数。
①父子不同的注册方式
set_pgfault_handler(pgfault)
与syscall_set_pgfault_handler(newenvid, __asm_pgfault_handler, UXSTACKTOP)
前者是父进程注册方式
- set_pgfault_handler在lib/pgfault.c里
- 初次设置:
- 在UXSTACKTOP下第一个BY2PG处申请了一个异常处理栈
- 调用
syscall_set_pgfault_handler(0, __asm_pgfault_handler, UXSTACKTOP)
- __pgfault_handler = pgfault;
- 再次设置:
- __pgfault_handler = fn;
后者是子进程注册方式
syscall_set_pgfault_handler(newenvid, __asm_pgfault_handler, UXSTACKTOP)
可以发现子进程的异常处理栈是在fork.c申请的,父进程是在注册时申请的。除此之外,父进程在注册过程中多进行的一步就是设置变量__pgfault_handler
。
②__pgfault_handler
函数指针,保存着当前进程的COW类缺页处理函数
③ __asm_pgfault_handler
从内核返回后,此时的栈指针是由内核设置的在异常处理栈的栈指针,而且指向一个由内核复制好的Trapframe 结构体的底部。通过宏TF_BADVADDR 用lw 指令取得了Trapframe 中的cp0_badvaddr 字段的值,这个值也正是发生缺页中断的虚拟地址。将这个地址作为第一个参数去调用了__pgfault_handler 这个字内存储的函数,不难看出这个函数是真正进行处理的函数。函数返回后就是一段类似于恢复现场的汇编,最后非常巧妙地利用了MIPS 的延时槽特性跳转的同时恢复了栈指针(延迟槽里恢复的)。
lw a0, TF_BADVADDR(sp)
lw t1, __pgfault_handler//重点,跳转到处理函数
jalr t1
nop//异常处理结束,恢复现场
lw v1,TF_LO(sp)
mtlo v1
lw v0,TF_HI(sp)
lw v1,TF_EPC(sp)
mthi v0
mtc0 v1,CP0_EPC
lw $31,TF_REG31(sp)
lw $30,TF_REG30(sp)
lw $28,TF_REG28(sp)
lw $25,TF_REG25(sp)
lw $24,TF_REG24(sp)
//……剩下的挨着存
lw $1,TF_REG1(sp)
lw k0,TF_EPC(sp) //atomic operation needed
jr k0 //
lw sp,TF_REG29(sp) /* Deallocate stack */
④sys_set_pgfault_handler
把异常处理栈和处理函数设置到目标进程的属性里。
env -> env_pgfault_handler = func;
env -> env_xstacktop = xstacktop;
(2)遇到COW类型缺页中断
在第一个图里,我们知道遇到COW类型缺页中断会被分发进入page_fault_handler\trap.c\lib
page_fault_handler(struct Trapframe *tf)
{
u_int va;
u_int *tos, d;
struct Trapframe PgTrapFrame;
extern struct Env * curenv;
//printf("^^^^cp0_BadVAddress:%x\n",tf->cp0_badvaddr);
//支持异常处理的重入(处理缺页异常时又发生缺页异常)
bcopy(tf, &PgTrapFrame,sizeof(struct Trapframe));
if(tf->regs[29] >= (curenv->env_xstacktop - BY2PG) && tf->regs[29] <= (curenv->env_xstacktop - 1))
{
//panic("fork can't nest!!");
tf->regs[29] = tf->regs[29] - sizeof(struct Trapframe);
bcopy(&PgTrapFrame, tf->regs[29], sizeof(struct Trapframe));
}
else
{
tf->regs[29] = curenv->env_xstacktop - sizeof(struct Trapframe);
// printf("page_fault_handler(): bcopy(): src:%x\tdes:%x\n",(int)&PgTrapFrame,(int)(curenv->env_xstacktop - sizeof(struct Trapframe)));
bcopy(&PgTrapFrame, curenv->env_xstacktop - sizeof(struct Trapframe), sizeof(struct Trapframe));
}
// printf("^^^^cp0_epc:%x\tcurenv->env_pgfault_handler:%x\n",tf->cp0_epc,curenv->env_pgfault_handler);
//从处理缺页异常的地址开始执行
tf->cp0_epc = curenv->env_pgfault_handler;
return;
}
①流程
在handle_mod里有
NESTED(handle_\exception, TF_SIZE, sp)
//省略了很多
move a0, sp
所以我们可以明确第一个参数$a0,即这里的tf,应该是当前的运行现场TF
- 将当前的运行现场复制到异常处理栈(与
curenv->env_xstacktop
有关) - 设置代码重新开始执行的地址(与
curenv->env_pgfault_handler
有关)
②具体处理函数pgfault()
思路:将va(父子同一个)对应的物理页框,复制完全相同的内容到另一个物理页框,将想修改的父/子的va映射到新的物理页框。
static void
pgfault(u_int va)
{
u_int *temp;
//按页搬运,所以直接对齐
va = ROUNDDOWN(va,BY2PG);
//随便找一个不会冲突的临时虚拟地址
temp = UXSTACKTOP-2*BY2PG;
u_int perm = (*vpt)[VPN(va)]& 0xfff;
if(perm & PTE_COW){
//temp-->new_pa(null)
if(syscall_mem_alloc(syscall_getenvid(),temp,perm & (~PTE_COW))<0)
{
user_panic("sys_mem_alloc error.\n");
}
//temp-->new_pa(old_pa)
user_bcopy((void *)va,(void *)temp,BY2PG);
//temp-->new_pa(old_pa) va-->new_pa(old_pa)
if(syscall_mem_map(syscall_getenvid(),temp,syscall_getenvid(),va,perm & (~PTE_COW))<0)
{
user_panic("sys_mem_map error.\n");
}
//temp-->null va-->new_pa(old_pa)
if(syscall_mem_unmap(syscall_getenvid(),temp)<0)
{
user_panic("sys_mem_unmap error.\n");
}
}
else
{
user_panic("Maximum Limit for ENV Exceeded\n");
}
}