实模式与保护模式的寻址
-
寄存器操作数:(存放在CPU中)
MOV AX,0FFFFH
AX 即为寄存器操作数。
操作数本身存放于寄存器中,在指令中只是给出了几个位的代码来表示它具体存放在那个寄存器中。
内存中的数据经过寄存器读入CPU,进入数据的运算。
内存操作数:(存放在内存中)
其存储于某内存区域,因此叫内存操作数。
-
mydata dw 1234H 或 mydata db 12H等
上述中mydata这样的变量
-
xx db 0FFH,0
mov bx, OFFSET xx //用offset运算符计算出xx单元的偏移值
mov ax,[bx]
上述中[bx]就叫做非变量名直接寻址的内存操作数
实模式与保护模式
最早期的8086 CPU只有一种工作方式,那就是实模式,而且数据总线为16位,地址总线为20位,实模式下所有寄存器都是16位。
而从80286开始就有了保护模式,从80386开始CPU数据总线和地址总线均为32位,而且寄存器都是32位。
但80386以及现在的奔腾,酷睿等等CPU为了向前兼容都保留了实模式,现代操作系统在刚加电时首先运行在实模式下,然后再切换到保护模式下运行。
1.实模式寻址方式
8086CPU数据总线为16位,也就是一次最多能取2 ^ 16 = 64KB数据,这个数据也解释了实模式下为什么每个段最大只有64KB。
但其地址总线为20位,这样它能寻址的能力其实是2 ^ 20 = 1MB,这也就是实模式下CPU的最大寻址能力。那即然它有1MB寻址能力,那怎么用16位的段寄存器表示呢?
这就引出了分段的概念,8086CPU将1MB存储空间分成许多逻辑段,每个段最大限长为64KB(但不一定就是64KB)。 这样每个存储单元就可以用“段基地址+段内偏移地址”表示。
段寄存器是因为对内存的分段管理而设置的。 计算机需要对内存分段,以分配给不同的程序使用(类似于硬盘分页)。
为了运用所有的内存空间,8086设定了四个段寄存器,专门用来保存段地址:CS(Code Segment):代码段寄存器;DS(Data Segment):数据段寄存器;SS(Stack Segment):堆栈段寄存器;ES(Extra Segment):附加段寄存器。
段寄存器含有段值,为访问存储器形成物理地址时,处理器引用相应的某个段寄存器并将其值乘以16,形成20位的段基地址。
因此,段基地址由16 位段寄存器值左移4位表达,段内偏移表示相对于某个段起始位置的偏移量 。
2.保护模式寻址方式
段寄存器不再存储段基址,而是存储段选择子,不再需要段寄存器左移加偏移。真正的段基址存在描述符高速缓存中。
在保护方式下,为了访问存储器形成线性地址时,处理器要使用选择子所指定的描述符中的基地址等信息。为了避免在每次存储器访问时,都要访问描述符表而获得对应的段描述符,从80286开始每个段寄存器都配有一个高速缓冲寄存器,称之为段描述符高速缓冲寄存器或描述符投影寄存器,对程序员而言它是不可见的。每当把一个选择子装入到某个段寄存器时,处理器自动从描述符表中取出相应的描述符,把描述符中的信息保存到对应的高速缓冲寄存器中。此后对该段访问时,处理器都使用对应高速缓冲寄存器中的描述符信息,而不用再从描述符表中取描述符。
这种方式下,每个段寄存器一共96位,分为两部分:
- 16位可见部分,称为段选择符,又称为选择子(selector)
- 80位不可见部分,称为描述符高速缓存器:
32位基址+32位段长度+16位属性
。无法通过任何指令来操作它
可见的选择子用来找到GDT/LDT表的一个段描述符,用这个段描述符填充这80位不可见部分。
这种方式通过“段选择符+段内偏移”寻址最终的线性地址或物理地址,如下图所示:
下图为段选择符结构,段选择符为16位,它不直接指向段,而是通过指向的段描述符,段描述符再定义段的信息。
其中TI用来指明全局描述符表GDT还是局部描述符表LDT,RPL表示请求特权级,索引值为13位。
所以从这里看出,在保护模式下最多可以表示2^13=8192个段描述符,而TI又分GDT和LDT,所以一共可以表示81928*2=16384个段描述符,每个段描述符可以指定一个具体的段信息,所以一共可以表示16384个段。而在前面的图可以看出,段内偏移地址为32位值,所以一个段最大可达4GB,这样16384*4GB=64TB,这就是所谓的64TB最大寻址能力,也即逻辑地址/虚拟地址。
虚拟空间(虚拟存储器地址空间)/编程空间 (程序的编写空间):
存储管理部件把主存(物理存储器)和辅存(磁盘)看作是一个整体,即虚拟存储器。486允许虚拟存储器容量最大为246=64T,即程序员可在此地址范围内编程,程序可大大超过物理空间。
-
全局描述符表寄存器GDTR
寄存器GDTR用来存放GDT的入口地址,程序员将GDT设定在内存中某个位置之后,可以通过LGDT指令将GDT的入口地址装入此积存器,从此以后,CPU就根据此寄存器中的内容作为GDT的入口来访问GDT了。
GDTR长48位,其中高32位为基地址,低16位为界限。
段选择子中只有13位作为描述符索引,而每个描述符长8个字节,所以用16位的界限足够。
-
局部描述符表寄存器LDTR
实际上,每个任务的局部描述符表LDT作为系统的一个特殊段,由一个描述符描述。而用于描述符LDT的描述符存放在GDT中。
LDTR类似于段寄存器,由程序员可见的16位的寄存器和程序员不可见的高速缓冲寄存器组成。 在初始化或任务切换过程中,把描述符对应任务LDT的描述符的选择子装入LDTR,处理器根据装入LDTR可见部分的选择子,从GDT中取出对应的描述符,并把LDT的基地址、界限和属性等信息保存到LDTR的不可见的高速缓冲寄存器中。
随后对LDT的访问,就可根据保存在高速缓冲寄存器中的有关信息进行合法性检查。
下面再来看段描述符结构,段描述符表中的每一项为一个段描述符,每一项为8字节,其结构如下图所示。
从图中可知,段选择符指向的段描述符里有三个部分基地址信息,这三部分组成一个32位地址就决定了段基地址位置,此地址再加上段内偏移最终确定线性地址位置。
注:
-
如果不分页的话,线性地址就是物理地址;
-
如果分页的话,则由分页部件把线性地址转换为
物理地址。
-
实模式:存储空间仅分段,而不分页;
保护模式:存储空间先分段,再分页。
什么是逻辑地址?
逻辑地址是CPU所生成的地址 ,指的是机器语言指令中,用来指定一个操作数或者是一条指令的地址。
**逻辑地址其实是逻辑上的地址,实模式下由“段基地址+段内偏移”组成;保护模式下由“段选择符+段内偏移”组成。逻辑地址经分段机制后就成线性地址。 **
如指令:
1.直接寻址 格式:段寄存器:[偏移地址] mov bx, ds:[1234H] //表示从ds数据段偏移地址为1234H的单元取数——>bx 假设(DS)=5000H 可计算出 线性地址为16d×(DS)+1234H=16d×5000H+1234h=50000H+1234H=51234H 。 命令的最后结果就是把线性地址为51234H的存储单元中的操作数据放入BX中 2.间接寻址 格式:段寄存器:[间址寄存器] / [间址寄存器] 注意:可省略段寄存器是因为会到默认约定的段寄存器中取内容数据 例: mov ds,数据段段基址 mov bx,buf单元的偏移地址 mov al,ds:[bx] 等价于:mov al,[bx]
间接寄存器和约定访问的逻辑段如下图所示:
分页内存管理
在我们进行程序开发的时候,一般情况下,是不需要管理内存的,也不需要操心内存够不够用,其实,这就是分页机制给我们带来的好处。它是实现虚拟存储的关键,位于线性地址与物理地址之间,在使用这种内存分页管理方法时,每个执行中的进程(任务)可以使用比实际内存容量大得多的连续地址空间。而且当系统内存实际上被分成很多凌乱的块时,它可以建立一个大而连续的内存空间的映象,好让程序不用操心和管理这些分散的内存块。分页机制增强了分段机制的性能。页地址变换是建立在段变换基础之上的。因为,段管理机制对于Intel处理器来说是最基本的,任何时候都无法关闭。所以即使启用了页管理功能,分段机制依然是起作用的,段部件也依然工作。
为什么需要分页内存管理?
因为有以下优点:
- 不会产生外部碎片化(空间碎片化的根源就是每个程序的大小不一样,这样在空间分配时不存在一致性。解决的办法自然是将空间按照某种规定的大小进行分配。将虚拟内存和物理内存都分成大小一样的部分,我们称之为“页”。),一个进程占用的内存空间可以不是连续的,
- 一个进程的虚拟页面在不需要时,可以存放在磁盘上,不需要全部同时加载到内存上。
- 可以共享小的地址,即页面共享。只要给相应的页表里面做一个相应的记录便可。
在二级分页系统的机制下:
32位的虚拟地址中,高10位(22-31)用来在页目录表中定位一个页目录表项(PDE),PDE中有页表的物理地址。找到页表后,中间10位(12-21)则用来在页表中定位一个页表项(PTE),PTE中有分配的物理页地址。余下低12位则用于页内偏移量。
所以每一个32位的线性地址被划分为三部份,页目录索引(10位):页表索引(10位):偏移(12位)
依据以下步骤进行转换:
- cr3寄存器中取出进程的页目录地址(操作系统负责在调度进程的时候,把这个地址装入对应寄存器);
- 根据线性地址前十位,在数组中,找到对应的索引项,因为引入了二级管理模式,页目录中的项,不再是页的地址,而是一个页表的地址。(又引入了一个数组),页的地址被放到页表中去了。
- 根据线性地址的中间十位,在页表(也是数组)中找到页的起始地址;
- 将页的起始地址与线性地址中最后12位相加,得到最终我们想要的物理地址;
-