操作系统的自我修养-05 从实模式到保护模式的飞跃

作者:K_Linux_Man   转载请注明出处


我们之前一直都工作在16位寄存器的环境下,我们即将要飞跃到32位寄存器 的平台。从16位寄存器操作到32位寄存器操作就是我们今天要讲到的从实模式到保护模式的转变。

这节内容晦涩难懂,为什么呢?因为都是intel们的大叔在设计芯片的时候没有考虑的非常长远,并且必须按照intel大叔们设计出的架构去实现代码。

此节内容废话连篇,请提前拿好板砖!


1.实模式的历史:

实模式,顾名思义,就是按照真实的地址去访问物理空间。我们大致先了解一下intel大叔们设计cpu的历史,有几个重要的节点,那就是intel8086 ,intel80286,intel80386的诞生

  


Intel8086是第一个16位的cpu,拥有16位寄存器,16的数据总线,和20位的地址总线,所以最大寻址达到1MB的空间,这1MB空间包括物理内存空间和BIOS-ROM空间。编写的汇编程序可以直接使用这些真实地址去访问BIOS程序或者外围设备或内存空间。后续又出了intel80286(80286,16位主理器,主频6/8/10/12~25MHZ,运算速度最高2.66MIPs,集成晶体管134,000个,3微米制造工艺,最大寻址内存16MB,生产曰期1982年.)这时是24位地址总线,寻址能力达到16MB,为了能够向下兼容,所以在80286及以后的x86系列兼容处理器仍然是开机启动时工作在实模式下。并且物理地址由段和偏移两部分组成,遵循公式: 物理地址=段地址左移4位+偏移地址,其中段地址和偏移地址都是16位的。

 

  
对于8086/8088来说计算实际地址是用真实地址对1M求模。8086/8088的地址线的物理结构:20根,也就是它可以物理寻址的内存范围为2^20个字节,即1 M空间,但由于8086/8088所使用的寄存器都是16位,能够表示的地址范围只有0-64K,这和1M地址空间来比较也太小了,所以为了在8086/8088下能够访问1M内存,Intel采取了分段寻址的模式:16位段基地址:16位偏移。其真实地址计算方法为:16位基地址左移4位+16位偏移=20位地址。 

比如:DS=1000H EA=FFFFH 那么绝对地址就为:10000H + 0FFFFH = 1FFFFH 地址单元。      通过这种方法来实现使用16位寄存器访问1M的地址空间,    这种技术是处理器内部实现的,通过上述分段技术模式,能够表示的最大内存为:FFFFh: FFFFh=FFFF0h+FFFFh=10FFEFh=1M+64K-16Bytes(1M多余出来的部分被称做高端内存区HMA)。但8086/8088只有20位地址线,只能够访问1M地址范围的数据,所以如果访问100000h~10FFEFh之间的内存(大于1M空间),则必须有第21根地址线来参与寻址(8086/8088没有)。因此,当程序员给出超过1M(100000H-10FFEFH)的地址时,因为逻辑上正常,系统并不认为其访问越界而产生异常,而是自动从0开始计算,也就是说系统计算实际地址的时候是按照对1M求模的方式进行的。


技术发展到了80286,虽然系统的地址总线由原来的20根发展为24根,这样能够访问的内存可以达到2^24=16M,但是Intel在设计80286时是向下兼容的,所以在实模式下,系统所表现的行为应该和8086/8088所表现的完全一样。如果程序员访问100000H-10FFEFH之间的内存,系统将实际访问这块内存,而不是象8086/8088一样从0开始,这样将导致8086/8088的程序代码不能在80286上很好的运行,为此intel和IBM的大叔设计出了A20 Gate.

2.A20 Gate的出现:

A20 Gate其实是为了解决以上兼容问题而提出的。使用键盘控制器上剩余的一些输出线来管理第21根地址线(地址从A0-A19,这里又多出来个A20,哎!)

如果A20 Gate被置为有效,则当程序员给出100000H-10FFEFH之间的地址的时候,系统将真正访问这块内存区域;

如果A20 Gate被禁止,则当程序员给出100000H-10FFEFH之间的地址的时候,系统仍然使用8086/8088的方式。绝大多数IBM PC兼容机默认的A20 Gate是被禁止的。现在许多新型PC上存在直接通过BIOS功能调用来控制A20 Gate的功能.

即使A20 Gate在有效的状态下,当前所在的模式也还是实模式,80286以及后续的PC中,即使A20 Gate被打开,在实模式下所能够访问的内存最大也只能为10FFEFH,尽管它们的地址总线所能够访问的能力都大大超过这个限制。为了能够访问10FFEFH以上的内存,则必须进入保护模式。


3.犹抱琵琶半遮面的“保护模式”(虚拟地址模式)

保护模式又称位虚拟地址模式,说到保护模式咱们不能不提一下intel80386,保护模式真正的在intel80386上运行。虽然在intel80286就提及保护模式,但是也没有真正实现。

从intel80386开始,intel cpu家族进入了32位处理器的时代,80386拥有32位地址总线,寻址能力能够达到4GB.

为了允许80386上的pc机器能够运行起来8086/8088上的操作系统或者程序代码,必须考虑到兼容问题,为此,在80386以及以后的x86体系的cpu上电的一瞬间,还是让其工作在实模式下,如果是运行在8086/8088上的系统,无需跳转到保护模式,直接访问真实地址即可。如果运行在80386及以后的x86体系的cpu上,则由实模式直接跳转到保护模式。

intel8086/8088 是运行在实模式下的,需要强调一点的是:实模式下,处理器中的寄存器是16位的,数据线是16位的,地址线默认是20位的,而intel80386处理器在开机的一瞬间还是运行在16位的实模式下,要发挥它强大的寻址能力,我们必须让cpu进去32位的世界,这32位的世界就是“保护模式”。


在实模式下,20位的地址需要段左移4位+偏移地址才能凑够20位,从而才达到1MB的访问空间。而进入intel80386的时代,我们有了32位的寄存器,一个寄存器就可以寻址4GB的空间,即使这样,在保护模式下,寻址方式发生了重大的变化,采用段式存储机制。



4.保护模式下的段存储机制



在保护模式下,寻址方式发生了变化,正如上图所述,段偏移地址为32位,段选择器是16位,有时段选择器又称为段选择子。寻址方式已经变为--- “段选择器:偏移”的形式。

我们称这种形式位逻辑地址,最终通过保护模式下的段存储机制转换成“真正访问“的线性地址。

从图中可以看出,段存储机制有好几部分组成,我们先介绍一下段描述符表。段描述符表又称为GDT(Global Descriptor Table).GDT由许多个段描述符组成,每个段描述符里面记录了每个段的基地址,段的长度等其他基本信息。另外,段描述符表基地址寄存器Gdtr,gdtr寄存器存放段描述符表的基地址和段描述符表的长度。段选择器里面存放段描述符表的偏移,通过与Gdtr基地址寄存器进行逻辑运算后得到段描述符的地址,通过段描述符里的信息得到段地址,段地址最后与段偏移地址逻辑运算后得到最终的线性地址。上述的过程完全由cpu自动完成,我们要做的是构建段描述符和段描述符表,将段描述符表的基地址和长度赋值给基地址寄存器gdtr,最后提供段选择器和偏移地址就间接地访问线性地址了。


4.1 用段描述符(Descriptor)构造GDT

Gdt由段描述符(Descriptor)组成,所以我们需要了解段描述符的结构。



上图所述位段描述符的组成机构。一个段描述符共64位,由32位的段基址 和 20位的段界限 以及其他一些属性位组成。由于历史原因,段基址和段界限都被分成了两部分。

其中第5字节和第6字节中有一些属性位,我们暂且先不管它,用到时了解一下即可。段描述符主要存放段基址和段界限的。由多个这种结构的Descriptor(段描述符)组成的类似于表结构的内容称之为“GDT”.


我们将用nasm汇编语言去填充段描述符,并构造出GDT

nasm 提供宏函数,可带参数,假如有3个参数,定义时,函数名后跟着参数的个数,对变量进行使用时,第一个变量为%1,第二个变量为%2,第三个变量为%3,以此类推.

格式:

%macro  函数名   参数个数

%endmacro

 

我们将要编写一个填充描述符的宏函数,起名:pm.inc,这个宏函数的作用是在数据段中占用了8字节的空间来存放段描述符.

; 填充描述符

; usage Descriptor  Base, Limit, Atrribute

;                                   Base:  dd  基地址为32位.

;                                   Limit :     dd  界限为20位.

;                                   Attribute:  dw  属性为12位.

%macro  Descriptor  3

           dw       %2 & 0xFFFF       ;段界限的低16位 (BYTE0 + BYTE1)

           dd        %1  & 0xFFFFFF   ;段基址低24位  (BYTE2 + BYTE3+ BYTE4)

           dw        (%2 >> 8)  & 0xF00  |   (%3 & 0xF0FF)  ; (BYTE5 + BYTE6)

           db        (%1 >>24)   ;(BYTE7)

%endmacro

;构造GDT

LABLE_GDT:                  Descriptor   0,  0, 0     ;空描述符

LABLE_DESC_CODE32:          Descriptor   xx,   xx,   xx

LABLE_DESC_VIDEO:           Descriptor   xx,   xx,   xx


4.2 (段描述符)基地址寄存器Gdtr 与段选择器

通过我们对保护模式下段存储机制的了解,通过Gdtr寄存器得到GDT的基地址,通过与段选择器中的偏移(或称为索引)相加,从而进一步得到段的基地址。

所以,Gdtr主要存放GDT的基地址,段选择器里存放你要使用哪个段描述符的偏移(或索引).


下图为gdtr寄存器结构图

32位基地址16位界限

------------------高地址-------------------------低地址---------------

                gdtr 寄存器结构


将我们构造好的GDT的基地址和长度 存放到相应字段即可。


段选择器的结构也非常的简单。由16位组成,结构如图所示,段选择器的第2位是引用描述符表指示位,标记为TI(Table Indicator),TI=0指示从全局描述符表GDT中读取描述符;TI=1指示从局部描述符表LDT中读取描述符,RPL为请求特权级, 至此我们应将TI 和 RPL设置为0,这样做是非常简便的,对于后面我们将会解释这一点。索引值指描述符在描述符表中的序号,例如我们定义了三个描述符, LABEL_GDT序号为0,LABEL_DESC_CODE32序号为1 ,LABEL_DESC_VIDEO序号为2,以此类推。




4.3  保护模式的关键寄存器CR0



CR0寄存器的第0位为PE位,此位为0时,CPU运行在实模式下,为1时,运行在保护模式下。



4.4  有一种代码叫“经典”

org  0x7c00  ;告诉汇编器我们将这段代码加载到内存的0x7c00处

%include  "pm.inc"     ;常量/宏函数/

       jmp    LABEL_BEGIN

;GDT存放在数据段中,一个描述符占8字节,共声明了3个描述符

;构造GDT

LABEL_GDT:     Descriptor   0,  0,  0    ; 空描述符,只是个标识

LABEL_DESC_CODE32:    Descriptor  0, SegCode32Len - 1,  DA_C+DA_32   ;代码段,32位,声明时基地址暂且为0x00

LABEL_DESC_VIDEO:     Descriptor   0xB8000, 0xFFFF, DA_DRW     ; 0xB8000为显存首地址,段界限为64K

;构造GDT结束


;构造Gdtr寄存器内容

GdtLen   equ   $-LABLE_GDT    ;GdtLen为16位

GdtPtr:

                dw   GdtLen  ; 低16位为gdt长度(界限)

                dd     0            ; GDT基地址,声明时暂且设置为0

;构造Gdtr寄存器内容结束


;创建段选择器(GDT选择子)

SelectorCode32    equ  LABEL_DESC_CODE - LABEL_GDT

SelectorVideo        equ  LABEL_DESC_VIDEO - LABEL_GDT


LABEL_BEGIN:

                  mov  ax, cs

                  mov  ds, ax

                  mov  es, ax


                 ;将32位代码段的首地址填充到CODE32描述符表的基地址中

                 xor   eax, eax   ;将eax清空

                 mov  ax,   cs

                 shl   eax, 4    ;左移4位

                 add  eax, LABEL_SEG_CODE32    ;eax存放32位代码段的物理地址

                 mov  word  [LABEL_DESC_CODE32 +2],  ax      ;设置CODE32段描述符的段基址的低16位

                 shr   eax, 16       ;右移16位

                 mov  byte [LABEL_DESC_CODE32 +4 ],  al       ;设置段基址的16-23位

                 mov  byte [LABEL_DESC_CODE32 +7],  ah       ; 设置段基址的24-31位


                 ;设置Gdt基地址, gdt基地址存放于gdtr基址寄存器中

                 xor   eax, eax

                 mov  ax,   ds

                 shl    eax,   4

                 add   eax,  LABEL_GDT    ;eax存放gdt的基地址

                 mov  dword [GdtPtr+2], eax   ;设置32位的Gdt基地址


                 ;gdtr寄存器赋值

                 lgdt     [GdtPtr]


                 ;关中断

                 cli


                ;打开地址线A20

                in  al,  92h

                or  al, 0x2

                out  92h, al

              

                ;准备切换到保护模式

                mov   eax,  cr0

                or   eax,  1

                mov  cr0, eax


                ;跳转进入保护模式

                 jmp   dword Selector32:0    ;将SelectorCode32 装入cs,并跳转到CODE32段偏移为0的位置,这个位置正好是LABEL_SEG_CODE32处


LABEL_SEG_CODE32:

                mov  ax, SelectorVideo

                mov gs,  ax

                mov  edi, (80*10 + 0) * 2;屏幕第10行,第0列

                mov  ah, 0ch              ;0000黑底  1100:红字

                mov  al,  ‘P’

                mov [gs:edi],  ax

             

               jmp   $

        

         SegCode32Len  equ   $ -LABEL_SEG _CODE32

                





                

                

                


 






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值