JOS 用户态page fault保护处理机制分析
常常会在用户态触发page fault,如果直接让其因为page fault跌入内核触发panic目测是不是"太残忍了"
你想想,一个刚学会写C程序的童鞋,就经常干 *(int *)0x00.
当然,我只是比较赤果果的指出这种问题而已,这位同学可能经常用各种指针,然后指针为初始化亦或等于NULL的时候,对其进行赋值或解引用.总不至于让一个刚学C的人就把整个系统都给挂了吧?恩.我们需要一种保护机制.
可以在用户态触发page fault,跌入内核时,我们可以做点"手脚",不要让他触发panic.
内核稍作处理,程序执行流返回到用户,并提示用户"同学,对非法的地址这么读写都是不对的哇"[ core dump大家一定都很熟悉了...哈哈]
好吧需求背景说完了,进入正题.
faultdie.c用户程序
集中精力关注,umain函数.
无非就是在对一个非法地址赋值前进行了一点"小动作"--set_pgfault_handler.
这里传入了一个普通的函数地址handler.我们看看set_pgfault_handler 做了什么
全局变量 _pgfault_handler是个函数指针.初始全局变量被初始化为0.
这个函数第一次运行的时候,会为当前进程 thisenv申请一页的内存,把这一页内存,映射到虚拟地址 va.
而后,通过sys_env_set_pgfault_upcall设置了_pgfault_upcall(一个汇编代码的入口)为返回地址.
最后把 _pgfault_handler设置为 handler.
用户触发page fault异常之后,跌入内核态,
struct Trapframe储存在内核栈上.
_alltraps会把寄存器各种压栈,然后调用trap(),进而调用trap_dispatch()
于是就会根据触发trap的是什么类型的异常来进行相依的处理,这里是Page fault.
于是,开始调用page_fault_handler.
tf指针还是指向刚刚我们压栈好的内核栈上struct Trapframe的首地址处.
在page_fault_handler内部.首先会检测我们是不是已经申请了用户异常栈的地址(翻到这篇博客看前面set_pgfault_handler里怎么申请的).
只要申请好了用户异常栈,于是就开始各种寄存器的拷贝.把当前内核栈上面的struct Trapframe拷贝到异常栈上去.
反正内核态有权限嘛~
细心的话你会发现,这里的拷贝是夸段的,内核堆栈段的数据拷贝到用户的异常栈上.因为内核有读写权限~
把utf指向的用户异常栈的栈顶指针赋值给当前内核栈内struct Trapframe的tf_esp成员,这个家伙会在之后内核弹栈切换进程的时候赋值给esp寄存器,进而设置好用户态的栈顶指针!那时候栈的切换就完成了.
env_run开始重新运行当前进程.env_run会调用 env_pop_tf()把内核栈上的struct Trapframe弹栈.切换进程!
我们来测试,就是切换进程后的栈顶是不是在UXSTACKTOP ( 0xeec00000)下面
测试:
我们把断点设置在handler的入口.看 esp在 UXSTACKTOP下面.