首页>资讯 > >正文

计算机程序基础教程(02):x86处理器运行方式-全球热头条

1978年,英特尔发布了第一款使用x86指令集的处理器,命名为为8086,是一款16位处理器。


(资料图片)

1982年,80286处理器上市,16位处理器,增加了保护模式。

1985年,80386处理器上市,32位处理器,增加了分页管理内存方式。

1989年,80486处理器上市,32位处理器,增加了浮点数运算单元。

2003年,AMD对x86指令集进行扩充,称为x86-64,用于64位处理器中,并发布了第一款64位x86处理器。

自此之后,对于程序制作者来说,x86系列处理器的基本运行方式就没有太大变化。

【指令的结构】

控制器的运行需要由指令控制,指令是一个二进制数据,计算机启动后,控制器会不断的读取存储器中的指令,从而不间断的运行。

指令基本由如下几个部分组成:

1.操作码,确定处理器执行什么功能,比如读写数据、数学运算、逻辑运算、等等。

2.地址码,设置指令读写数据的位置,可以是寄存器、存储单元地址,若要写入的数据是固定的,也可以将数据直接存储在地址码中,这种数据称为立即数。

3.操作数类型码,确定操作数据的长度,比如1字节、2字节、4字节等长度。

一个数据既可以用来表示一个数值,也可以用来表示一个指令,为了区分两者,文章之后的内容将数据分为数值数据和指令数据,避免混乱。

【指令的执行方式】

指令有两种基础执行方式:顺序执行、跳转执行。

处理器默认以顺序执行的方式执行指令,当前指令执行完毕后,读取存储器中之后的数据作为指令执行。

而跳转执行可以让指令不按照顺序执行,当前指令执行完毕后,可以按需求跳转到之后或之前的某个地址处执行。

● 指令流水线与转移预测

CPU为了提高指令的执行速度,会将每一条指令的执行分为多个步骤,然后使用流水线的方式执行,当前指令执行时,之后的指令已经在流水线中进行准备工作。

若遇到需要跳转执行的指令,CPU会提前预测将要跳转到的地址,然后读取预测地址处的数据进入流水线,这个预测的正确率一般为90%以上,但是无法做到100%,若判断出错,就要清空流水线中的任务,重新来过,为了避免这种情况的发生,高级编程语言的编译器会尽量减少跳转指令的使用,使用其他多条指令的组合也能实现跳转指令的功能。

● 指令调度策略

程序中指令的执行是有顺序的,但是有些指令不按顺序执行也不会出错,此时CPU可能会改变某些指令的执行顺序、或者让指令交叉执行,当指令需要等待数据传入而进入暂停状态时,直接执行下一条指令。

编程语言的编译器也会进行相关优化,对指令进行重新排序,不会完全按照代码的编写顺序进行编译,而是按照指令最优的执行顺序去编译。

● 实模式与保护模式

最早的CPU其指令可以随意跳转执行 、随意访问任何内存,随着科学技术的进步,人们需要在计算机中同时执行多个程序,此时就需要一个称为操作系统的程序来管理其他程序的运行,并限制他们只能访问属于自己的内存区域,为此80286CPU有了保护模式,用于实现限制功能,之前对指令不进行任何限制的模式称为实模式。

保护模式将指令的权限分为4个等级,使用0-3表示,0级权限最高,可以使用CPU的任何资源,高权限指令可以跳转到低权限指令执行,反之则不行,操作系统使用0级权限,从而控制其他程序的执行,用户程序使用3级权限,1-2级权限被操作系统废弃不用。

CPU默认以实模式运行,修改控制寄存器CR0的PG位可以进入保护模式,进入保护模式后就不能再返回实模式。

【内存管理方式】

最简单的CPU在指令读写一个内存单元时,直接通过一个数据指定内存地址,此时程序必须放在固定的内存地址处,放在其他位置就会执行出错,比如程序需要跳转执行时,跳转到的地址已经写死,无法改变。

● 分段使用方式

8086处理器会将内存分为很多段来使用,目的是方便程序重定位,指令通过两个数据相加得到内存地址,分别称为段地址、偏移地址,段地址存储内存段的起始地址,偏移地址存储内存段的内部编号,偏移地址为0表示内存段的第一个存储单元、为1表示第二个存储单元。

操作系统启动后,会首先将内存分段,并在内存中创建全局描述符表,用于记录每个分段的起始地址、长度、访问属性等等信息。

程序执行时,只能占用操作系统设置好的分段,不能自己创建分段,操作系统会为每个执行的程序创建一个局部描述符表,用于记录此程序占用的内存段相关信息。

指令读写内存单元时只需要指定偏移地址即可,之后CPU通过查找描述符表将偏移地址与段地址相加,合成内存地址,这样就实现了将程序放在哪个位置都可以正确执行。

● 分页使用方式

使用分段方式管理内存时,操作系统往往不会设置很小的分段,并且分段的数量是有上限的,使用不方便,容易造成浪费,为程序分配的内存段大小不一定是程序需要使用的容量。

为了解决这个问题,从80386开始有了分页管理内存方式,处理器将内存按照4KB的大小分成很多组,每一组称为一个页,程序以页为单位占用内存,避免浪费。

启用分页机制后,指令依然使用分段的方式读写内存,目的是为了保留分段的优势,让程序放在内存中的任何位置都能正确执行。

但是此时段地址+偏移地址合成的地址已经不是真实的内存地址,为了避免混乱,此时段地址+偏移地址合成的地址称为虚拟地址。

计算机文件有一个文件偏移地址的概念,它的作用是为文件内数据分配编号,第一个字节分配编号0、第二个字节编号为1、直到最后一个字节,通过这个编号调用文件内数据比较方便,无需知道文件存放在哪个位置。就好比使用火车托运货物,你只需要记录火车车厢的编号即可,无论火车走到哪里,你只要告诉管理人员你的货物在几号车厢,就能拿走你的货物,至于火车停在哪个轨道上,你无需关心,那是管理人员的事。

虚拟地址的作用就类似文件偏移地址,它用来为程序内部的数据分配编号,虚拟地址虽然也叫地址,但是它与内存地址已经没有一毛关系。

操作系统启动后,首先启用保护模式,然后启用分页机制,程序执行时操作系统为其按页分配内存,并创建一个页表,用于记录程序占用了哪些页、每个页的属性等信息,此时虚拟地址会与内存物理地址建立映射关系,指令通过虚拟地址说明要调用程序内的第几个数据,至于这个数据编号对应哪个内存地址,指令不关心,也无需知道,CPU会根据页表中的记录将虚拟地址转换为内存物理地址,之后执行指令。

【寄存器】

寄存器按功能的不同基本可以分为5类:通用寄存器、段地址寄存器、偏移地址寄存器、标志寄存器、控制寄存器。

● 通用寄存器

通用寄存器可以存储任何数据,又分为多种长度类型,64位寄存器只存在于64位CPU中,但是同时也包含32位、16位、8位寄存器。

x86-64处理器中的通用寄存器如下:

64位:rax、rbx、rcx、rdx,最早的x86指令集定义的通用寄存器,在x86-64中长度扩展为64。

64位:r8、r9、r10、r11、r12、r13、r14、r15,x86-64指令集新增的寄存器。

32位:eax、ebx、ecx、edx,由64位的rax-rdx拆分形成,不能与对应的64位寄存器同时使用,使用rax时不能同时使用eax,下同。

32位:r8d、r9d、r10d、r11d、r12d、r13d、r14d、r15d,由64位的r8-r15拆分形成,只存在于x86-64指令集中,x86指令集没有,下同。

16位:ax、bx、cx、dx,由32位的eax-edx拆分形成。

16位:r8w、r9w、r10w、r11w、r12w、r13w、r14w、r15w,由64位的r8-r15拆分形成,只存在于x86-64指令集中。

8位:al、bl、cl、dl、ah、bh、ch、dh,由16位的ax-dx拆分形成,ax的低8位为al,高8位为ah。

8位:r8b、r9b、r10b、r11b、r12b、r13b、r14b、r15b,由64位的r8-r15拆分形成,只存在于x86-64指令集中。

● 段地址寄存器

8086处理器定义了4个段地址寄存器:CS、DS、ES、SS。

CS存放指令数据的段地址。

DS存放数值数据的段地址。

SS存放栈空间的段地址。

ES是辅助段寄存器,供程序自由安排使用。

80386处理器新增了两个辅助段寄存器:FS、GS。

启用保护模式后,CS寄存器中存放的不再是段地址,而是段选择子,用来存储操作系统设置好的内存段编号、并记录指令的特权级别。

● 偏移地址寄存器

8086处理器定义了5个偏移地址寄存器:IP、SP、BP、SI、DI。

IP,存放指令数据的偏移地址,CS+IP合成的内存地址处理器就认为是指令,会从这里开始顺序执行。

SP,存储栈空间的偏移地址,push、pop栈操作指令会影响SP的值。

BP,存储栈空间的偏移地址,push、pop栈操作指令不会影响BP的值,使用mov指令读写栈空间时一般会使用BP寄存器。

SI、DI,存储数值数据的偏移地址,服务于读写数组相关指令,比如movsb、movsw。

32位处理器中,偏移地址寄存器的长度扩充为32位,使用32位模式时,名称前添加E字母,比如:EIP、EDI。

64位处理器中,偏移地址寄存器的长度扩充为64位,使用64位模式时,名称前添加R字母,比如:RIP、RDI。

BX虽然是通用寄存器,但是也可以用来存储数值数据的偏移地址。

SP、BP、SI、DI也可以当做通用寄存器使用,在x86-64指令集中,SP、BP、SI、DI又可以拆分形成4个8位寄存器:SPL、BPL、SIL、DIL,这4个8位寄存器只存在于x86-64指令集中。

● 标志寄存器

8086处理器有一个标志寄存器,名为FLAGS,长度16位,用于记录指令执行结果、或者设置处理器的某些功能,每一种信息使用一个二进制位记录,常用位如下:

CF位:

记录无符号数加法运算结果是否发生溢出,或者减法运算结果是否为负数。

当进行无符号数加法时,记录加法结果是否因长度过大导致溢出,若溢出则CF位存储1,否则CF为0。

当进行无符号数减法时,减法会转换为加法,若加补码运算没有溢出,则计算结果为负数,并使用补码存储,此时CF位存储1,否则存储0。可用于判断两个无符号数的大于小于关系,将两个数做减法运算即可。

OF位:

记录有符号数加法结果是否发生溢出,若溢出则OF位存储1,否则为0。

SF位:

记录有符号数减法运算结果是否为负,若为负则SF位存储1,若为正存储0。

ZF位:

记录数学运算、逻辑运算、数据比较等指令的执行结果中是否所有位都为0,若为0则ZF存储1,否则存储0。

可用于判断一个未知数是否与一个已知数相等,将两者进行减法运算即可。

无符号数与有符号数都可以通过此位来判断减法结果是否为0,当有符号数减法结果为0时,符号位也为0,这里体现出了有符号数只有+0没有-0的优势。

DF位:

设置movsb等数组指令读写数据的方向。

若DF位为0,执行movsb指令时si、di寄存器各自加1,执行movsw指令时si、di寄存器各自加2。

若DF位为1,执行movsb指令时si、di寄存器各自减1,执行movsw指令时si、di寄存器各自减2。

TF位:

设置是否产生单步中断,用于调试程序。

IF位:

设置处理器是否接收可被屏蔽的外中断,若为1则接收,为0不接收。

● 控制寄存器

用来设置处理器工作模式的转换、缓存的禁用与启用、虚拟地址的转换等等功能。

80386有4个控制寄存器:CR0 - CR3,其中CR1保留不用,而x86-64指令集定义了CR0-CR15共16个控制寄存器,但只使用其中的CRO、CR2、CR3、CR4、CR8。

CR0的PE位用来设置是否启用保护模式,若为1则启用保护模式,进入保护模式后不能再回到实模式,PG位用来设置是否启用内存分页管理模式,启用分页模式之前必须首先启用保护模式。

【中断】

CPU执行一个程序时,若发生了一些重要事件,需要暂停程序,转而去处理事件,这时候就需要使用中断功能。

每一种中断都会有一个编号,可以为每一种中断绑定一个处理程序,当发生中断后,CPU会去执行中断处理程序,执行完毕后再返回之前的程序执行。

CPU外部产生的中断称为外中断,CPU对外连接的某些引脚用于外中断功能,通过改变它的电压来告知CPU是否有事件发生,比如按下键盘按键后会向CPU发出一个外中断,告知CPU输入设备有数据发送。外中断分为可屏蔽中断与不可屏蔽中断,大多数外中断都是可屏蔽的,可通过修改标志寄存器中的IF位屏蔽这些外中断。

CPU内部产生的中断称为内中断或者异常,比如除数为0时会产生一个内中断,也可以通过执行int指令人为产生一个内中断,用户程序使用系统调用时就是通过这种方式实现,因为用户程序无法跳转到操作系统内核执行,所以用户程序会通过int指令发出一个内中断,并设置一些参数,操作系统接收到内中断后根据参数执行系统调用。

标签: 偏移地址 操作系统 虚拟地址

相关阅读