Chinaunix首页 | 论坛 | 博客
  • 博客访问: 959571
  • 博文数量: 173
  • 博客积分: 3436
  • 博客等级: 中校
  • 技术积分: 1886
  • 用 户 组: 普通用户
  • 注册时间: 2009-01-07 09:29
文章分类

全部博文(173)

文章存档

2016年(6)

2015年(10)

2014年(14)

2013年(8)

2012年(36)

2011年(63)

2010年(19)

2009年(17)

分类:

2010-06-30 20:47:07

GRUB中实模式与保护模式的切换

      在stage2开始的活动中,我们一定要设置好C环境。GRUB下可以自由输入的指令,像kernel、boot、initrd等,都是由C来实现的。这样对保护模式的需求就呼之欲出了,最起码的,C语言通过保护模式实现了更大范围的硬件支持。 |h6, .#n  
      位于stage2中的asm.s汇编文件更多的是提供了一些基本的汇编子程序,可以称之为GRUB基本函数库。而实模式切换到保护模式的函数ENTRY(real_to_prot)以及保护模式到实模式的ENTRY(prot_to_real)就位于asm.s中。 $W:_ f_%[P  
      如果程序通过调用ENTRY(real_to_prot)函数切入到保护模式下,那么这个子函数ENTRY(real_to_prot)是怎样具体工作的呢?首要的,是要建立合适的全局描述符表GDT,不要忘了,保护模式之所以不同于实模式首先在于寻址模式的变化。保护模式通过GDT来间接寻找内存地址。GDT标号位于asm.s的末尾,内容当然是要包含各种门描述符和段的描述符。其第一个8字节的位置并不使用。GDT的内容如下: [~0x '_048  
***************************************************************** tSpkl 2fs  
gdt: EXZ0|h6?1  
.word 0, 0 -zh6?#8  
.byte 0, 0, 0, 0 ;(GDT的第一个8字节不使用) E6<G!  
.word 0xFFFF, 0 ;代码段描述符,可以看到段界限低16位为0xFFFF  WVLF!56  
.byte 0, 0x9A, 0xCF, 0 ;第6、7字节为1100111110011010,段界限粒度G为1,段界限高四位都为1,可寻址到0xFFFFF*4K,即4G字节;段基址为0;权限为执行/读, mIMrk]1  
.word 0xFFFF, 0 ;数据段描述符 M;)4BP%b  
.byte 0, 0x92, 0xCF, 0 ;权限为读/写 #H |./E0  
.word 0xFFFF, 0 ;16位实模式代码段描述符 w}+?[""{!8  
.byte 0, 0x9E, 0, 0 ;段界限粒度G为0,段界限为0xFFFF,可寻址到0xFFFF,即64K字节;段基址为0;权限为执行/读、一致码段 4EF(e,tT  
.word 0xFFFF, 0 ;16位实模式数据段描述符 4] PvZ8@  
.byte 0, 0x92, 0, 0 ;权限为读/写 ]1 1>U|  
***************************************************************** W9YpFB@  
      可以看到,GDT一共包含了4个有效的段描述符(包含空描述符的话,一共是5个)。一个段描述符会指出段的32位基地址和20位段界限(即段长)。以第一个有效的段描述符为例,它的形式应该是: [!n#zU&  
***************************************************************** 6jUl[<#  
字节             二进制码 >Okk0%K  
0            1 1 1 1 1 1 1 1 a)gYGeiz  
1           1 1 1 1 1 1 1 1 &Lp6AE[vx  
2           0 0 0 0 0 0 0 0 Nf 2Cv;,  
3           0 0 0 0 0 0 0 0 z:58go;g  
4           0 0 0 0 0 0 0 0 V_]up]Z>X  
5           1 0 0 1 1 0 1 0 AGJWwlkj  
6           1 1 0 0 1 1 1 1 n'Uo{Q[e  
7           0 0 0 0 0 0 0 0 RPm/"`s  
***************************************************************** pn)B\IC{  
      它的含义或者它描述了一个段基址为0x00000000(32位)、段长为0xF0000乘以4k(20位长度在此处表示一个段中的页数,此刻相当于4GB)、存在于内存中、特权级为2的、可读的、系统代码段。更详细的情况可以参考段描述符的格式。其中,第6字节的D位表示缺省操作数的大小。这也是我们称后两个段描述符为伪实模式代码段或数据段的原因。 vqp.3FDB  
      ENTRY(real_to_prot)函数还使用48位指针gdtdesc指向该GDT。gdtdesc是在asm.s的最后部分,内容如下: 1n B*  
**************************************************** y3vd3nER  
gdtdesc: X%Ys%<  
.word 0x27 ;GDTR界限,可以描述(0x27+1)/8=5个描述符,和上面一致 @@}{C(_  
.long gdt ;GDTR基地址,指向gdt B8 EhWxm  
**************************************************** >1"votA)  
      因为我们在ENTRY(real_to_prot)函数中使用lgdt gdtdesc指令将这个gdtdesc(GDT descriptor,GDT描述符)装入到GDTR寄存器中,这是一个48位的寄存器,用来保存GDT的32位基地址和16位GDT的界限(也即长度,除以8字节就得到描述符的数目),此处gdtdesc的内容被装载到GDTR中,含义在上面的注释中。 (#C;EG0%  
      那么现在实模式到保护模式的第一步(加载全局描述符表)就完成了,接下来需要设置cr0寄存器。其中,cr0的0位是PE位(protected enable),如果置1,则保护模式启动。很明显的,ENTRY(real_to_prot)函数要这么做。通过movl %cro,%eax;orl $CR0_PE_ON,%eax; Z>}ImWD[  
movl %eax,%cr0来使cr0寄存器变为0。 (#(,1hX{t  
      看样子,保护模式已经ok了。然而,gemfield不得不提醒的是,实模式和保护模式中的段寄存器——虽然都为16位,但含义却是不一样的。实模式下,段寄存器装载的是段基址,而在保护模式下,却装载的是索引,对于全局描述符表GDT的索引。因此,如果不对各段寄存器从新设置的话,怎么能够想象保护模式已经诞生了呢? (I#]T: [  
      首先cs寄存器比较特殊,它不能通过直接赋值的方法来改变值,一般是通过跳转指令来实现。所以在实模式的最后一条指令执行的时候,其实跳转指令已经被预取到队列中了。在执行完实模式的最后一条指令后,程序切换到保护模式,这条跳转指令就是执行的第一条指令。   dug.i` 
      gemfield通过ljmp $PROT_MODE_CSEG,$protcseg指令来改变cs的值,$PROT_MODE_C在share.h中被定义为0x8,此处ljmp的含义是程序跳转到cs:ip=0x8:$protcseg的位置,自然的,cs被设置为0000000000001000(16位),那么这个选择子(或者索引值)的含义就是指向GDT的、特权级为0的、是全局描述符表中第2个描述符。这个描述符正是gemfield在本文第四段介绍的那个。那么新的指令地址不就因此而改变了吗?没错,cs指向的描述符给出了0x00000000的段基址,还要和IP这个32位的偏移量相加($protcseg),这样新的地址重新指向了protcseg标号。 ^^h 6s*y  
      那么程序接着从protcseg处开始执行,对ds、es、fs、gs、ss进行重新设置,赋值0x10,与上一段的含义类似,指向GDT中的保护模式数据段描述符。 bJ|]~O?  
      还有一点很明显的,保护模式并没有对堆栈描述符进行设置,因此对堆栈的操作就显得至关重要了。调用ENTRY(real_to_prot)子函数时程序本来就要进行压栈,现在进入保护模式,就把栈中保存的地址上的数据拿出来放在新栈(protstack)中继续使用,这样ret时原来的数据能继续在保护模式下使用。 Kr&xOg  
      而从保护模式进入实模式的过程和ENTRY(real_to_prot)类似。首先加载gdtdesc为进入伪实模式做准备(类似于飞船的气闸仓)。然后将保护模式下的堆栈esp内容保存至protstack堆栈中,然后将堆栈中内存地址上的数据放在STACKOFF中,然后设置新的堆栈,也就是STACKOFF,而这个堆栈中已经保存了需要的数据。 <+}[+*kd  
      接着通过movw $PSEUDO_RM_DSEG,%ax指令将段寄存器设置为0x20,以及下面指令将cs置为$PSEUDO_RM_CSEG(0x18)来将cs设置为0x18,来进行数据段选择符和代码段选择符的索引。然后再将cr0清零,进入实模式。然后再通过ljmp $0,$realcseg来将cs清零,并将IP指向下面的标号realcseg。到此时,我们已经完全进入真正的实模式了,然后将段寄存器清零。开中断,并返回。 I"p 4FV1-  
      这么一个大气的切换过程确实令人叹为观止,虽然grub中模式的切换有些简单,比如没有加载中断描述符表和局部描述符表等,从而不得不关闭中断。但不管怎样,这样艺术性的代码不可多得,那就让gemfield和大家一起享受这代码之美吧。 z=Uw&7.6]  

阅读(2101) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~