基于JOS 80x86 的堆栈切换简要分析

基于JOS 80x86 的堆栈切换简要分析


这个问题一直困扰很久,发现还是有点粗心,源头--堆栈初始化没怎么搞明白.


这里首先强调,一定一定要搞清楚分段和分页保护的机制.

现有分段,后有分页,分页可有可无,看寄存器cr0是否开启PE位(page enable. 在JOS系统的boot.S里就已经开启了)


文章从三个方面对栈进行分析

0. GDT 全局段寻址描述表

1. 栈的初始化.

2.用户栈到内核栈的切换

3.内核栈到用户栈的切换



0. GDT 全局段寻址描述表

基于JOS 80x86 的堆栈切换简要分析_第1张图片

你能看见第0个段这个时候是不允许访问的,GD_KT右移三位变成 (0x8 >> 3 == 1),第一个段是内核的代码段.可读可执行.第二个是GD_KD 右移三位 (0x10 >> 3 ==  2)第二段.是内核数据段.

第三个GD_UT右移三位(0x18 >> 3 == 3) 第三个段是用户代码段.

第四个GD_UD 右移三位(0x20 >> 3 == 4) 第四个段是用户数据段.

后面的TSS段和我们的主题关系不大,只是任务切换的时候有用.

看过这里之后,嘛嘛再也不用担心别人装逼的时候说"代码和数据是分离的",我听不懂了.

纸老虎!



1.栈的初始化.

首先开机系统开始运行的时候,在boot.S阶段,还没有开启之前,就立马设置好了栈.怎么做的呢?

基于JOS 80x86 的堆栈切换简要分析_第2张图片

首先,把ax寄存器异或置0.然后把ax寄存器的值赋值给ds es ss寄存器.


初始的时候,数据段,额外段,堆栈段,都指向第0个段.这时候还没有什么分页机制

段寻址 address == segment : offset == (segment << 4 bits ) + offset 就直接得到物理地址了

而这里选择的是第0个段啊!同志啊,...在这个"原始的荒野",你用的地址都是物理地址

接着立马就开启了分页机制,

lgdt指令马上加载我们之前介绍的GDT全局段描述表.

开启分页机制,我们也就进入了保护模式.

基于JOS 80x86 的堆栈切换简要分析_第3张图片



接着在bootloader阶段各种段 ds cs都指向$ PROT_MODE_DSEG 0x10指向的内核数据段

基于JOS 80x86 的堆栈切换简要分析_第4张图片


重要的事情说三遍,

JOS中堆栈段和数据段指向同一个段,

JOS中堆栈段和数据段指向同一个段,

JOS中堆栈段和数据段指向同一个段,

: )


到后来初始化CPU的时候,也是把ss指向 GD_KD


OK ,到这里栈的初始化就算讲明白了(至少我自我感觉非常良好哈哈哈)



2. 用户栈切换到内核栈.

这里有各种方式可以切换,我们集中分析一种Trap Gate触发的切换就好了(其余的还有Call Gate, Interrupt Gate,Task Gate)可以去看赵炯的0.11 Linux源代码分析那本书,对于80x86的介绍非常的详细,也可以读Intel的手册...

重点放在*(int *)0xDeadBeef = 0就好,其他的可以无视,和我们这一小节的主题无关,我们关注的是栈的切换.

基于JOS 80x86 的堆栈切换简要分析_第5张图片

由于这里尝试对一个非法地址写入,那么直接page fault,有米有! 

由于触发的异常,那么CPU会帮我们直接把堆栈段进行切换(注意,很多其他寄存器不会自动切换,但是cs ss会!)

口说无凭,我们来测试

下面是刚好在这句坑爹的指令执行之前,各种寄存器的状态

基于JOS 80x86 的堆栈切换简要分析_第6张图片

常规寄存器都不需要怎么关注,集中看 cs ss ds es fs gd eflags就好


下面我们看对比图.触发page fault前后的对比.


基于JOS 80x86 的堆栈切换简要分析_第7张图片


你会发现 cs ss 变了其他的 ds es fs gs都没变,而且这时候 eflags的IF标识没啦,中断这个时候是被屏蔽的.

结论: 触发异常的时候,CPU是会自动切换 代码段和堆栈段寄存器的,而数据段没有自动切换,以至于我们需要手动的在汇编代码中切换 ds .当ds都完成切换的时候,就完成了所谓的从用户态到内核态切换.


3. 内核态到用户态的切换(这里不讨论用户异常栈的情况).

真正切换的地方在这里.从内核栈切换到普通用户栈.实质上是前面用户态到内核态的一个逆向过程.

寄存器pop的顺序都是完全相反的...

基于JOS 80x86 的堆栈切换简要分析_第8张图片

这里把tf指针指向的 struct Trapframe设置成栈顶指针,很巧妙的把各种恢复各种寄存器的值.

直到最后 iret由于返回地址不再内核代码段内,发生堆栈切换.


这是切换前后的寄存器对比图:


基于JOS 80x86 的堆栈切换简要分析_第9张图片


切换之后,eflags立马有了 IF,允许了中断调用.







基于JOS 80x86 的堆栈切换简要分析_第10张图片


你可能感兴趣的:(栈,X86,JOS)