zoukankan      html  css  js  c++  java
  • 保护模式下的寻址

    在汇编语言中,或者你有学习过诸如微机原理或计算机组成原理等课程的话,那么你很可能听说过实模式和保护模式的概念。他们到底是什么,有什么不同,又如何寻址?

    在王爽的《汇编语言》最后,有关于Intel微处理器的三种工作模式的介绍。

    继Intel 8086推出之后,Intel又推出了划时代的80386微处理器,它可以在实模式、保护模式和虚拟8086模式下工作,从那以后的微处理器都提供了这三种工作模式,直到现在。Intel系列微处理器的三种工作模式如下:

    • 实模式:工作模式相当于一个8086
    • 保护模式:提供支持多任务环境的工作方式,建立保护机制
    • 虚拟8086模式:可以从保护模式切换至其中的一种8086工作方式,这种方式的提供使用户可以方便的在保护模式下运行一个或多个8086程序

    当我们的系统开机时,cpu首先工作在实模式下完成一些工作,之后跳入保护模式,为我们的系统提供多任务环境的支持。而当我们需要在保护模式的系统上运行实模式下的程序时(比如学习汇编时所用的DOS系统),我们就需要在当前的保护模式下弄一个“假”的实模式,这就是虚拟8086模式。

    GDT和描述符

    在实模式下(可以理解为工作在8086上时),我们的CPU是16位的,提供了16位的寄存器,16位数据总线,20位的地址总线,可寻址范围位1M。物理地址遵循下面的计算公式:

    [物理地址 = 段地址 * 16 + 偏移地址 ]

    其中的段地址和偏移地址都是16位的。

    从80386开始,Intel家族的CPU进入了32位时代,这时候CPU有32位的地址总线,所以可寻址范围为4G。CPU同样拥有的是32位的寄存器,一个寄存器即可寻址4GB的空间。

    在实模式下,我们采用段地址:偏移地址的寻址方式是因为我们只有16为的寄存器,单个寄存器的寻址范围达不到1MB,但现在我们拥有了32位的寄存器,单个寄存器的可寻址范围已经可以达到4GB了,那么是不是就不需要段寄存器了?答案是否定的。在保护模式下,地址仍然采用“段地址:偏移地址”的方式来表示,只是段的概念发生了根本性的变化

    实模式下,段值(段地址的值)还是地址的一部分。在保护模式下,虽然段值仍然由原来的16位的cs、ds等寄存器表示,但是此时它们仅仅是一个索引,这些个索引指向一个数据结构的表项,表项中详细定义了一个段的起始地址、界限、属性等内容,这个数据结构,叫做GDT(其实还可能是LDT,我们先讨论大多数情况),GDT中的每一个表项,叫做描述符

    寻址过程

    我们在来看一下保护模式下的寻址过程。在此之前,有几点要说明:

    • GDT是一个数据结构,它是保存在内存中的,所以它应该有一个起始地址,它是一系列描述符的集合
    • GDT的起始地址由一个专门的寄存器来存放 -- gdtr,gdtr寄存器是48位的,这个寄存器我们稍后在探讨
    • GDT中的每一个描述符描述一个段,其中包括段的起始地址(基址)等属性
    • 保护模式的偏移地址和实模式下的是相同的,只不过是32位

    好了,下面有一张图,我们可以看着这张图过一遍保护模式下是如何寻址的。

    1. 寻址时,先找到gdtr寄存器,从中得到GDT的基址
    2. 有了GDT的基址,又有段寄存器中保存的索引,可以得到段寄存器“所指”的那个表项,既所指的那个描述符
    3. 得到了描述符,就可以从描述符中得到该描述符所描述的那个段的起始地址
    4. 有了段的起始地址,将偏移地址拿过来与之相加,便能得到最后的线性地址
    5. 有了线性地址(虚拟地址),经过变换,即可得到相应的物理地址

    相信到这里,你已经对寻址过程有了个大概的了解,然后我们看看我们上面所未详细提及的东西

    gdtr寄存器

    gdtr是一个48位的寄存器,其中保存了GDT的基地址和界限(或者说GDT的长度),高32位为GDT的基地址,低16位为界限。还记得保护模式中的段寄存器也是16位的吗,它们和gdtr中的界限是对应的啊。

    描述符

    GDT中的每个描述符占8个字节,其结构如下

    我们可以不用管其中的属性,仅看段基址和段界限。是不是和上面的寻址联系上了呢。

    你可能会问,问什么段基址和段界限都被分开了,却不放在一起?这主要还是历史遗留问题,我们就不在探讨了。

    代码

    光看理论终究还是水中月,我们看一段简单的代码实际体会一下。

    [SECTION .gdt]
    ; GDT
    ;                              段基址,       段界限     , 属性
    LABEL_GDT:	   Descriptor       0,                0, 0           ; 空描述符
    LABEL_DESC_CODE32: Descriptor       0, SegCode32Len - 1, DA_C + DA_32; 非一致代码段
    LABEL_DESC_VIDEO:  Descriptor 0B8000h,           0ffffh, DA_DRW	     ; 显存首地址
    ; GDT 结束
    
    GdtLen		equ	$ - LABEL_GDT	; GDT长度
    GdtPtr		dw	GdtLen - 1	; GDT界限
    		dd	0		; GDT基地址
    
    ; GDT 选择子
    SelectorCode32		equ	LABEL_DESC_CODE32	- LABEL_GDT
    SelectorVideo		equ	LABEL_DESC_VIDEO	- LABEL_GDT
    

    上面的代码中,我们定义了一个角.gdt的段,其中前三个LABLE_xxx后是我们用一个叫Descriptor宏定义了三个选择子,其中的数值并不一定正确,因为我们只是定义了,还并没有初始化。 Descriptor的作用是将段基址、段界限和属性放在一个选择子中相应的位置,其定义在文章末尾,感兴趣的话可以看下。

    GdtPtr是不是和gdtr中所放的内容一样呢?没错,当我们在实模式进入保护模式之前,我们需要将GdtPtr的值加载到gdtr寄存器:使用指令lgdt [GdtPtr]

    那最后两个GDT选择子又是什么呢?好像是描述符相对于GDT基地址的偏移,其实并不全对,它稍稍复杂一些,如下图所示。

    其中TI和RPL是选择子的一些属性,剩下的高13位表示的是描述符在描述符表的位置,即GDT中第几个描述符

    最后,我们看一下如何使用上面的东西吧

    [SECTION .s32]; 32 位代码段. 
    [BITS	32]
    
    LABEL_SEG_CODE32:
    	mov	ax, SelectorVideo
    	mov	gs, ax			; 视频段选择子(目的)
    
    	mov	edi, (80 * 11 + 79) * 2	; 屏幕第 11 行, 第 79 列。
    	mov	ah, 0Ch			; 0000: 黑底    1100: 红字
    	mov	al, 'P'
    	mov	[gs:edi], ax
    
    	; 到此停止
    	jmp	$
    
    SegCode32Len	equ	$ - LABEL_SEG_CODE32
    

    上述代码将一个字母P显示在屏幕上。gs中保存的是显存的选择子,edi为偏移地址,然后使用mov [gs:edi], ax将ax的内容写入到地址为gs所指的描述符中的段基址+edi的内存处,由于这里写入的是显存,所以将会将一个字母P显示在屏幕上。

    Descriptor宏的定义如下

    ; usage: Descriptor Base, Limit, Attr
    ;        Base:  dd
    ;        Limit: dd (low 20 bits available)
    ;        Attr:  dw (lower 4 bits of higher byte are always 0)
    %macro Descriptor 3
    	dw	%2 & 0FFFFh				; 段界限1
    	dw	%1 & 0FFFFh				; 段基址1
    	db	(%1 >> 16) & 0FFh			; 段基址2
    	dw	((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)	; 属性1 + 段界限2 + 属性2
    	db	(%1 >> 24) & 0FFh			; 段基址3
    %endmacro ; 共 8 字节
    

    参考:

    • 《汇编语言》 王爽
    • 《一个操作系统的实现》 于渊
  • 相关阅读:
    LiLicense server OR Activation code
    一个比喻讲明Docker是什么
    Linux 系统目录结构说明
    Sublime Text2支持Vue语法高亮显示
    javascript权威指南笔记[6-8]
    javascript权威指南笔记[1-5]
    使用chrome控制台调试js代码
    windows与linux下执行.class(包含main方法)
    linux 命令
    几种常见的编码格式
  • 原文地址:https://www.cnblogs.com/tcctw/p/11300720.html
Copyright © 2011-2022 走看看