【xv6学习之番外篇】详解struct Env 与 struct Trapframe

鉴于我们有必要对struct Env和struct Trapframe 这两个用户环境的关键结构体有个细致的了解,这篇博文应运而生。主要借鉴张弛的report。(内联汇编参见 http://grid.hust.edu.cn/zyshao/OSEngineering.htm 第二章)


首先是 Env 结构体:

inc/env.h

【xv6学习之番外篇】详解struct Env 与 struct Trapframe_第1张图片


这其中的 env_tf 存储了各寄存器内容

inc/trap.h


其他的都很好理解,某些padding开头的变量是为了让数据补齐4Byte,因为寄存器都是32比特的。


Trapframe保存的都是一些系统关键的寄存器。这里我们只需要特别关注4个寄存器,涉及到程序执行的控制流问题:

EFLAGS:状态寄存器,这个我们暂时用不到

EIP:Instruction Pointer,当前执行的汇编指令的地址

ESP:当前的栈底

EBP:栈顶,当前过程的帧在栈中的开始地址(高地址)


现在我们可以来看看在程序调用call时具体是如何修改EIP的。通过查询IA-32 Intel Architecture Software Developer’s Manuals 中
的Volume 2A: Instruction Set Reference,A-M 中的CALL指令,我们可以看到其详细的执行流程:


这个是简化后最关键的部分,实际上指令的流程涉及到32位、64位、访问权限、以及长跳转和短跳转的各种问题,不过那不是我们关心的。我们只需要知道它对 EIP 和 ESP 做了什么就。

同样的,看看 RET


看着很简单,如果涉及保护模式和实模式的切换,那么还有相应段寄存器CS的保存切换问题,在IRET中我们就可以看到相应的逻辑,现在我们先可以不管。


接着我们看看env_alloc()里面关于Trapframe的设置:

 kern/env.c 

【xv6学习之番外篇】详解struct Env 与 struct Trapframe_第2张图片


tf_esp:初始化为USTACKTOP,表示当前用户栈为空

tf_cs: 初始化为user text segment selector,权限为用户可访问

tf_es,tf_ds,tf_ss: 初始化为 user data segment selector,权限为用户可访问

tf_eip: 这里没有设置,但是注释告诉我们了该由我们设置,很显然,这里eip的值就是我们在 load_icode() 里设置的用户程序入口地址


接着看 env_run() ,这个函数最主要的任务是 env_pop_tf() 函数,它是真正负责切换到用户的过程:

kern/env.c

【xv6学习之番外篇】详解struct Env 与 struct Trapframe_第3张图片


我们来尝试理解这段内联汇编:


先是: movl %0, %%esp

这里出现了占位符%0,通过后面的参数可以看到这里的占位符代表的是memory中的变量tf,即Trapframe的指针地址。这里把它传给esp是什么意思?看到后面的各种pop命令,就可以知道,这里的想法是把Trapframe看作一个存储了很多内容的栈,然后利用pop命令一个一个输出到我们想要重置的寄存器里。因为我们知道弹栈的时候栈指针是不断加的过程(栈的生长是栈指针不断减),所以将ESP设置为Trapframe所在内存的首地址,就可以以内存中的排布顺序释放出所有的内容了。非常的巧妙!

接着: popal

通过查询手册,可以得到popal的执行明细:


一句就输出了这么多寄存器,这里每一次Pop (),就是从ESP指向的Trapframe里拿出4个Byte,我们来看看Trapframe的前8个DWORD是什么:

【xv6学习之番外篇】详解struct Env 与 struct Trapframe_第4张图片

可以看到前8个DWORD为一个struct PushRegs,这里面的定义顺序和 popal 里设置的顺序是完全对应的 ! 可见PushRegs的定义也是经过了缜密的思考的,非常的巧妙,利用一句汇编指令就完成了这么多寄存器的设置。


最后是 iret

再次求助INTEL的指令手册,可以看到IRET和RET的不同:


因为IRET涉及到中断返回的各种控制,所以在保护模式以及实模式切换中会涉及段寄存器切换以及访问控制的问题,实际的控制流非常非常非常复杂,有兴趣的同学可以参考手册里的详细说明。

这个时候执行的IRET语句,会把Trapframe里的下面三个成员放入相应的寄存器


这些成员我们在 env_alloc() 以及 load_icode() 中都设置好了,其中EIP为用户程序入口地址,CS为用户程序代码段段基址。那么执行完这条语句以后,CPU再往下执行的第一条语句,应该就是用户程序的第一条指令了。

所以说 env_run() 和 env_pop_tf() 都是没有返回的。

你可能感兴趣的:(【xv6学习之番外篇】详解struct Env 与 struct Trapframe)