关于ucore lab1 challenge1 的思考

      这个扩展练习是要求设置两个中断处理程序。一个是可以实现从内核态转换为用户态的程序。另一个是实现从用户态转换到内核态的程序。其实,这个练习,我们只要弄明白int指令和iret指令到底做了什么,以及cpu是如何表示特权级状态的就可以做了。

     int 指令进行下面一些步骤:(这些步骤来自xv6中文文档)
            1. 从 IDT 中获得第 n 个描述符,n 就是 int 的参数。 
            2.检查 %cs 的域 CPL <= DPL,DPL 是描述符中记录的特权级。 
            3. 如果目标段选择符的 PL < CPL,就在 CPU 内部的寄存器中保存 %esp 和 %ss 的值。(这里的目标段选择符我不太清楚是什么,我觉得可能是相应的GDT表项里特区级或者就是这个中断描述符里的CS所记录的特权级)
            4.从一个任务段描述符中加载 %ss 和 %esp。(这里的任务段就是TSS,一般设定为每个CPU一个,且在固定的段表项)
            5.将 %ss 压栈。
            6.将 %esp 压栈。
            7.将 %eflags 压栈。
            8.将 %cs 压栈。
            9.将 %eip 压栈。
            10.清除 %eflags 的一些位。

            11.设置 %cs 和 %eip 为描述符中的值。

    从上面的内容可以看出,int这短短的一条指令其实做了非常多的事(这可能表明实现这条指令的电路非常复杂吧)。不过,如果步骤3不为真的话,3,4,5,6这几个步骤都是不会执行的。因此,对于没有发生特权级转换的中断,其实是没有栈切换的。一直都是用那个栈在处理中断。

      至于iret指令的动作也是类似的,因为int指令和iret指令是一对的,就像call指令与ret指令。其指令的步骤如下:

             1.将 %eip 弹栈。

            2.将 %cs 弹栈。

            3.将 %eflags 弹栈。
            4.将 %esp 弹栈。

            5.将 %ss 弹栈。

          从上面的内容可以看出,iret其实就是int的逆过程。相应的,如果弹栈过程中,发现栈中%cs和当前%cs的特权级是一样的,那么步骤4,5是不会发生的。因为cpu会认为int指令并没有把这两个内容压栈。

      至于cpu如何表示特权级:就是通过%cs寄存器的最低两位来表示,表示为数字即0~3,其中数字越小特权级越高。一般实现只用两个状态,内核态为0,用户态为3。

      好了,有了这些知识储备,这个练习就可以做了。

      因为,内核初始化完之后,是处于内核态的。要想通过中断来实现转换到用户态,就必须在一个中断里把当前中断的trapframe里的各种段寄存器强行改为用户态的内容,然后通过iret指令弹栈就实现了特权级的转换了。不过,根据上面的知识,这里有点坑人的地方就是:因为这个中断是在内核态陷入的,所以int指令并没有将%ss和%esp压栈!!!但是,我们在这个中断例程里却要修改它的值(%ss),所以,如果我们在int指令调用前,不做点处理的话,就是写入到错误的位置,从而破坏了栈结构。因此,解决办法就是:int指令没有为我们提供足够的位置,那我们就自己提供。于是,在int指令前,我们将%esp的值减8就为%esp和%ss预留了空间了。这里还有一个小细节就是,用户态返回后要进行cprintf调用,这个函数使用了in和out指令。但是如果不设置好eflags的值的话,在用户态执行这两条指令是会产生13号中断错误的。于是,要在系统调用里修改以下trapframe的eflags的值。最后,中断返回后,通过汇编指令,把ebp的值传给esp就使栈顶位置正确归位,于是就完成了。

     至于从用户态转为内核态也是大同小异。坑人的地方就是,这次int指令的确把%ss和%esp压栈了,但是这回是iret指令没有把他们弹出,因为我们强行改为内核态,会让cpu认为没有发生特权级转换。于是,%esp的值就不对了(%ss的值是对的)。但是,解决办法很简单,因为%ebp的值是正确的,而执行int指令前,%ebp和%esp的值是一样的。因为,只要把%ebp的值传给%esp就会使栈顶位置正确归位,于是就顺利完成了调用。



猜你喜欢

转载自blog.csdn.net/sinat_30955745/article/details/80976997