80X86系统是Inter为PC设计的一系列处理器的统称。其中最早的是8086,也就是单片机时代最出名的处理器,后来以32位处理器80386开始的个人PC时代。8086和80386虽然都同为80X86系列处理器,但是是有根本性的改变的。有些书上是不将8086作为80X86系列之中的。80386作为80X86系列处理器的代表作,其后的80486,80586及80686等与80386实际上是没有革命性改变的。80386与8086有很大的区别,最大的区别是8086是32位处理器,从位数上来说比8086多一倍。因此寻址空间增加到4GB空间,从而80386的工作模式与8086有了很大的不同,这些不同一方面是为了兼容以前的8086程序,另一方面是为应用80386提高的性能。这三种模式是实模式、保护模式和虚拟8086模式。我们知道WINTEL联盟,所以从WINDOWS操作系统来说,它是如何利用这三种模式呢?我们知道一个计算机系统,当完成开电自检后,系统BIOS会读取MBR记录,通过MBR记录来启动来选择启动的分区表,确定了分区表后,WINDOWS启动管理器会读取启动配置信息,并且在你选择相应的Windows启动项后,启动Winload.exe(这当中如果是休眠读取的winresume.exe,其它是Ntldr.exe),当这些完成后,进入Ntoskrnl.exe 加载驱动并启动登陆会话管理器smss.exe和wininit.exe。完成后启动服务services.exe和lsass.exe。在这整个启动过程中处理器交替转换了这三种模式。
【1】实模式 是当处理器被复位或者加电的时候是以实模式启动,这时存储器的寻址方式是以8086一样的。在实模式下,不对内存进行分页,32位地址线只使用20位,指令寻址的地址是真实的物理地址。在这个模式下,使用与8086等同的指令级别,指令无优先级,80386在实模式下初始化控制寄存器。只有当这些管理寄存器如GDTR,LDTR,TR,IDTR等初始化后,然后通过加载CR0,修改页设置,进入保护模式。这时中断方式也同8086一样,通过中断向量表定位中断服务程序。实模式下80386虽然处理形为同8086相同,但是因为这时寄存器都是32位,并且还新增了两下段寄存器FS,GS因此,从性能上来说比8086有了很大的提高。DOS系统是运行于实模式下。
【2】保护模式 当CR0修改了页标志后,80386进入保护模式,这里物理寻址空间是4GB。在保护模式下,内存不再是访问真实物理地址,而是线性地址。线性地址经过分页机制映射到真实物理地址。并且,在初期因为物理内存没有4GB,所以WINDOWS设计了虚拟内存技术,就是将一部分硬盘空间映射成内存地址空间。在保持模式下,指令执行完全被重新修改了,段寄存器不再用于寻址,而是用于定义内存页的相关属性,因此在保护模式下,配合段指向的段描述表信息,实现了多任务和优先级属性定义,并且通过修改控制寄存器CRO的控制位实现数据共享。WINDOWS7是运行于保护模式下。
【3】虚拟8086模式,我们登陆WINDOWS后都有一个MS-DOS控制台,这个就是一个虚拟8086处理器。这个虚拟86是以任务形式在保护模式上执行的,可以说是一个虚拟机。操作系统管理虚拟机处理的地址与真实的页面地址的映射。因此可以说虚拟86是一个运行在保护模式上的任务,只是这个任务可以处理8086的一些相关指令,但有些指令返回一些特定结果或者直接将结果忽略。如cli和iret指令就无法运行。
前面讲的这三种模式,实际上是WINDOWS利用80386的一些特性设计的三种模式,实际上对80386CPU来说,它是没有这种模式分别的。因此,80386的根本特性还是它对内存的看特上与8086不一样了,因此汇编指令也不与8086完全不同,相对来说更简单明了。从内存上,80836CPU指令上对内存与DOS下内存安排不一样,DOS下内存分配如下:
从这里可以看出DOS下内存分配分配是真实的物理内存,在内存低端,中断向量表和BIOS数据处占据了。从A0000后全部驱动和显示内存。真正为DOS和用户可用的从00500h开始到A00000总共不到640KB的内存。如果DOS或者用户程序中有驻留程序TSR,那么可用空间更少。从这里可以看出在DOS时代,内存是限制系统的真正瓶颈。这里因为虽然8086CPU使用段加上偏移地址进行内存访问,但实际上还是只能访问20位地址空间。因此到80386上,地址变成32位后,就是4GB空间,这是一个质的突破。我们知道80386的寄存器是32位,访问4GB的内存,不再需要通过段加上偏移方式,因为每个寄存器均可以访问32位地址。那么这样说的话,是否段寄存器是否就没什么用了?显然不是这样的,因为CPU只是按照指令进行工作,因此如何安排段寄存器只需要设计不同的指令集就行了。但是为了兼容8086指令集,段寄存器不能取消。那么段寄存器可以赋于不同的职能。我们前面知道实模式下,80386实际上同 8086一样,但在保护模式下,段寄存器用来存储一些特别的信息,这些信息关联到一个地址是否可被写入、可以被多少优先级别的代码写入等涉及到保护性的属性信息。显然要定义这些信息需要很多的内存空间,单凭一个段寄存器是无法满足的。因此,解决的方法就是把所有这些信息定义为段描述符(Segment descriptor)并用64位长的数据表示,最后把它顺序放在内存中的指定位置,组成一个段描述表(descriptor Table)。这样,段寄存器只需要用不同的位来对段描述表中的段描述符进行索引,就可以查询出这些属性信息。因此,这时段寄存器就叫段选择器(segment selector)。前面我们说段描述符表放在内存中,那么如何用寄存器来访问这两块区间,80386设计了两个新的寄存器,一个是48位的全局描述表寄存器GDTR,另一个是16位的局部描述符寄存器LDTR。GDTR指向的描述符表为全局描述符表GDT(Gloabel descriptor Table),它包含系统中所有任务都可用的段描述符,通常包含描述操作系统所使用的代码段、数据段、和堆栈段的描述符及各任务的LDT段等,GDT只有一个。LDTR则指向局部描述符表LDT(local descriptor table),80386处理器设计成每个任务都有一个独立的LDT,包含有每个任务私有的代码段、数据段和堆栈段的描述符,也包含该任务所使用的一些门描述符如任务门和调用门描述符。16位的段选择器中只有高13位表示索引值,其余三个,一个表示优先级RPL,另一个表示是在全局还局部。因此对一个地址xxxx:yyyyyyyyy格式,前面16位,后面32位,是怎么寻址的呢?
如上图所示,首先从段选择器选择TI值,根据TI值决定,如果0查找GDTR,则查询GDT位置,如果1则先查询GDTR表,接着查询LDTR,最后确定局部描述符在内存中的位置。从上面可以看出,80386寻址也同8086类似,那么对应的段+偏移表示的寻址通常在80386中指的是线性地址,而不是真实的物理地址。这又是为什么呢?这是因为在80386时代,内存重新设计出一种新的管理方式,就叫内存分页。在单任务的DOS系统中,程序可以使用所有空闲内存,程序退出,操作系统进行内存管理,这种管理就是进行内存合并。但实际上因为有很TSR程序驻留,会不停的产生内存碎片,导致无法分出大块内存。我们知道程序装入内存通常都是连在一起装入的,操作系统进行分配时很难作出特别的安排。80386采用了一种机制就叫分页机制,这种机制在数据结构中也常见。就是预先定义一块最小的内存,称之为一页内存4kb,对每一页内存设置一个分配状态目录,然后操作系统管理这个页目录。每一页大小为什么是4KB呢,这是因为它是12位。在80386处理器设置CR3寄存器用于保存页目录页面的地址,这个是真实的物理地址。那么我们前面说到80386在实模式下是没有分页机制的。那么怎么区分是否分页了内存呢?首先80386设置了一个新的CR0控制器,在31位(pg位),如果pg=0,则不分页,否则分页。因此,对同一个寻址地址来说,分页与不分页指向的真实物理地址是完全不同的。但这一切对指令来说都是透明的。如下图所示:
因此,从程序的角度,它只关注到线性地址。实模式下是不会有分页机制的。在保护模式下,因为有页目录寄存器,因此,页也有一些属性,这些属性包括可读、可写等,这样可以对一些特定内存页进行保护,如代码段页是不能写的。另外,进行了分页后,分页映射的真实物理地址,也可以在内存不够大时候指向虚拟内存。其操作效果对程序指令来说完全透明。现在内存比较便宜,也就是一台电脑配一个2G和4G内存比较普通。以前硬盘通过虚拟内存方式来扩大内存,利用的就是分页优势。分页目录先查询,查询到之后,再将真实的页面放入内存。这就是讲操作系统中经常提到的局部优先原理。WINDOWS操作系统和普通的LINUX操作系统都是分时多任务操作系统,所谓分时就是在同一个时间里只能执行一个程序,但是在一个较长的时间段内,不同任务程序的指令集是
轮流执行的。这样看起来,好像是多任务同时执行。实际上是单个任务在单个时间点上运行,多个任务轮流在不同的时间点运行。这不是真正的实时操作系统。真正的实时操作系统是在单个时间点运行多个任务。如下图所示:
上图显示CPU在每个时间片上,看到都是一个线性地址,这个线性地址是由操作系统基于分页机制映射到真实物理地址上。因此,在每个时间片上,线性地址空间都是隔离的。对每个时间片上运行的单个程序可以使用的线性地址空间是4G,但是不表示真实物理地址可以使用4G。因为这一切都是80386和操作系统在底层隐藏了。所以对程序来说,只需要关注线性地址。而且更强调安全,因此段描述符实际上已经在底层确定好了。不再由用户更改。所以win32汇编也不会去修改GDTR和LDTR。
前面我们讲完了在不同模式下内存管理方式,实际上对中断和异常也不一样了。在实模式下,中断同8086一样,使用中断向量表来处理。但在保护模式下是不同的,因为代码是有优先级的。因此,中断和异常都是从用户切换到操作系统代码中执行,需要进行优先级转换。这里在保持模式下使用了“门”,这个门就是一个低级别代码执行高级别代码的入口。通常在80386中分为中断门、自陷门和任务门几种。同前面段描述符一样,保护模式下的中断描述符放在一起组成“中断描述符表IDT”,并且引入了一个新的48位寄存器IDTR。其中高32位指定了IDT在内存中的基址,低16位指定了IDT的长度,相当于指定了可以支持的中断数量。
如上图所示,保护模式下发生异常或中断时,处理器根据IDTR查到中断描述符,然后出现n号中断的门描述符,然后根据这个描述符得到中断服务的寻址地址。这个地址转换后是一个线性地址。然后根据线性地址查找到操作系统提供的中断服务程序。这些中断服务程序一般由操作系统通过DLL来提供,与程序共享同一个线性空间。所以int指令不再有用。
综上所述,80386与8086最大的区别,还在于保护级别的提升,以前一切都开发,现在是每个地方都要进行严格检查。如段要进行类型检查,这个检查由段描述符来确定,如果检查不通过,就会报告错误。同样页也检查,并且还对代码和指令设置了优先级别,指令分为特权指令、敏感指令等级别。所有这些,都是为了防止一些非法程序对内存的非法访问修改。虽然WINDOWS操作系统结合80386CPU设置了这些防护条例,但是还是有很多漏洞的。所以说WINDOWS上的病毒那是相当的多啊。一个是本身复杂,防不胜防。另一个是研究的人多。