16进制计数法&处理器&内存&指令

十六进程计数法

二进制计数法回顾

关于二进制计数法

二进制到十进制的转换

Binary
Decimal

十进制到二进制的转换

十六进制计数法

十六进制计数法原理

十六进制到十进制的转换

Hexadecimal

十进制到十六进制的转换

为什么需要十六进制

使用windows计算器方便你的学习过程

处理器,内存和指令

了解Intel8086 处理器的通用寄存器和段地址加偏移地址的内存访问方式
了解分段机制对程序重定位的好处
理解Intel8086处理器内存分段的本质,充分认识到这种分段机制的灵活性

最早的处理器

处理器会在振荡脉冲的激励下,
从内存获取指令,
并发起一系列由该指令所定义的操作.
操作结束后,接着再取下一条指令.

寄存器和算术逻辑部件

处理器的底部或四周,有大量引脚,可接受从外面来的电信号,或向外发出电信号.
处理器的引脚很多,其中有一部分用来将参与运算的数字送入处理器内部.
有些引脚是复用的,如要进行加法运算,则要重复使用这些引脚.
来依次将被加数和加数送入.
一旦被加数通过引脚送入处理器,
代表这个二进制数字的一组电信号就会出现在与引脚相连的内部线路上.
这是一排高低电平的组合,代表着二进制数中的每一位.
这是,必须用一个称为寄存器的电路锁住,设RA.
因为相同的引脚和线路马上还要用于输入加数.
因为这个原因,这些内部线路称为处理器内部总线.

同样,加数也要锁进另一个寄存器中,设RB.
寄存器锁存数值后,其内容不再受外部数据线的影响.

寄存器是双向器件,可以在一端接收输入并加以锁存,
同时,它也会在另一端产生一模一样的输出.
与寄存器RA,RB相连的是算术逻辑单元或算术逻辑部件.
它是专门负责运算的电路,可以计算加法,减法,乘法,也可做逻辑运算.

一旦寄存器RA和RB锁存了参与运算的两个数,算术逻辑部件就会输出相加的结果.
这个结果可以临时用另外一个寄存器RC锁存,
稍后再通过处理器数据总线送到处理器外面,
或再次送入RA或RB.

处理器内部有一个控制器,
在指令的执行过程中,
它负责给各个部件发送控制信号,
使各个部件在某个正确的时间点上执行某个动作.
同时,
还负责决定在某个时间点上哪个部件有权使用总线,
以免彼此发生冲突.

处理器总是很繁忙的,
在它操作的过程中,
所有数据在寄存器里面都只能是临时存在一会儿,
然后再被送往别处.

1 byte[字节] = 8 bit[比特]
16位寄存器可以存放2个字节,这称为1个字.
32位寄存器可以存放4个字节,这称为1个双字.
64位寄存器可以存放8个字节,这称为4个字..

内存储器

处理器的计算过程,实际上是借助于寄存器和算术逻辑部件进行的.
参数计算的数来自何处?
答案是一个可以保存很多数字的电路,叫做存储器.
存储器的种类实际上很多,硬盘,U盘,甚至寄存器都算.

个人计算机都会用到的存储器,平时把它叫做内存条.
首先,它是计算机内部最主要的存储器,通常之和处理器相连.
所以叫做内存储器或主存储器,简称内存或主存.
其次,它一般被设计成扁平的条状电路板,所以叫内存条.

和寄存器不同,
内存用于保存更多的比特.
对于用的最多的个人计算机来说,
内存按字节组织.
单次访问的最小单位是1字节.这是基本的存储单元.
每个存储单元中,各位的变化分别是0~7.

内存中的每字节都对应着一个地址,
第一个字节的地址是0000H,第二个字节的地址是0001H,
第三个字节的地址是0002H,其他以此类推.
如果内存的容量了是65536字节,则最后一个字节的地址是FFFFH.

为了访问内存,处理器需给出一个地址.
访问包括读和写.
为此,处理器还要指明,本次访问是读访问还是写访问.
如是写访问,则还要给出待写入的数据.

8位处理器包含8位的寄存器和算术逻辑部件.
16位处理器拥有16位的寄存器和算术逻辑部件.
64位处理器则包含64位的寄存器和算术逻辑部件.

尽管内存的最小组成单位是字节,
但,经过精心的设计和安排,
它能够按字节,字,双字,四字进行访问.
换句话说,仅通过单次访问就能处理8位,16位,32位或64位的二进制数.

处理器发出字长控制信号,
以指示本次访问的字长是8,16,32还是64.
如字长为8,且给出的地址是0002H,
则本次访问只会影响到内存的一字节.
如字长是16,且给出的地址依然是0002H,
则实际访问的将是地址0002H处的一个字,
低8位在0002H中,高8位在0003H中.

指令和指令集

设计处理器的目标是使它成为一种可自动进行操作的器件.
还需提供一种机制,来允许程序员决定进行何种操作.

处理器的设计者用某些数来指示处理器所进行的操作.
称为指令或机器指令.只有处理器才认得它们.

处理器内部有寄存器和负责运算的部件,控制器"分析"一个个指令,
然后确定在哪个时间点让哪些部件进行工作.
指令是集中存放在内存里的,一条接着一条,
处理器的工作是自动按顺序取出并加以执行.

设从内存地址0000H开始,连续存放了一些指令.
同时,假定执行这些指令的是一个16位处理器,拥有两个16位的寄存器RA和RB.
一般来说,
指令由操作码和操作数构成,但也有小部分指令仅有操作码,而不含操作数,
指令的长度不定,短的指令有1字节,
而长的指令则有可能达到15字节[对IntelX86而言]

对处理器来说,
指令的操作码隐含了如何执行该指令的信息.
如它是做什么的,怎么去做.
如对操作码B8,
这表明,该指令是一条传送指令.
第一个操作数是寄存器,
第二个操作数是直接包含在指令中的,紧跟在操作码之后,可立即从指令中取得,
叫立即数.
B8 5D 00
操作码直接指出该寄存器是RA.
RA是16位寄存器,这条指令将按字进行操作.
所以,这条指令执行后,
该指令的操作数[立即数]005DH就被传送到RA中.
	
既然操作码中隐含了这么多的信息,则处理器就可以"知道"每条指令的长度.
这样,当它执行第一条指令B8 5D 00的时候,就已经知道,这是一个3字节指令,
下一条指令位于3个字节之后,即内存地址0003H处.

注意字数据在内存中的存放特点.
地址0001H和0002H里的内容分别是5D和00.
如果每次读一个字节,
则从地址0001H里读出的是5D,从0002H里读出的是00.
但如果以字的方式来访问地址0001H,读到的就会是005DH.
这种差别,跟处理器和内存之间的数据线连接方式有关.
	
对Intel处理器来说,
如访问内存中的一个字,
则它规定高字节位于高地址部分,低字节位于低地址部分,这称为低端字节序.
至于其他公司的处理器,则可能正相反,称为高端字节序.

对复杂一些的指令来说,
1字节的操作码可能不会够用.
所以,第2条指令的操作码为8B 1E,它隐含的意思是,
这是一条传送指令,第一个操作数是寄存器,而且是RB寄存器.
第二个操作数是内存地址,要传送到RB寄存器中的数存放在该地址中.
同时这是一个字操作指令,
应当从第二个操作数指定的地址中取出一个字.
8B 1E 3F 00
表示将003F地址单元里的数[一个字长度]传送到RB寄存器
该指令的操作数部分是3F 00,指定了一个内存地址003FH.
它相当于高级语言里的指针,
当处理器执行这条指令时,
会再次用003FH作为地址去访问内存,
从那里取出一个字[假设为1002H],
然后将它传送到寄存器RB.
传送之后,003FH单元里的数据还保持原样.

指令执行和操作的对象是数,
如果这个数已经在指令中给出了,不需要再次访问内存.
则这个数就是立即数.
相反,如果指令中给出的是地址,真正的数还需要用这个地址访问内存才能得到,
那它就不能称为立即数.

指令和非指令的普通二进制数是一模一样的,
在组成内存的电路中,都是一些高低电平的组合.
因为处理器是自动按顺序取指令并加以执行的,
在指令中混杂了非指令的数据会导致处理器不能正常工作.
为此,指令和数据要分开放.分别位于内存中的不同区域.
存放指令的区域叫代码区,存放数据的区域叫数据区.
为了让处理器正确识别和执行指令,
工程技术人员必须精心安排,并告诉处理器要执行的指令位于内存中的什么位置.

并非每一个二进制数都代表着一条指令.
每种处理器在设计的时候,也只能拥有有限的指令,
从几十条到几百条不等.
一个处理器能识别的指令的集合,称为该处理器的指令集.

古老的Intel 8086处理器

8086是Intel公司第一款16位处理器.
但是在Intel公司的所有处理器中,它占有很重要的地位,
是整个Intel 32位架构处理器[IA-32]的开山鼻祖.

8086的成功使得市场上出现了大量针对它开发的软件产品.
这样,当Intel公司要设计新的处理器时,
它不得不考虑到兼容性的问题.
要使得老的软件也能在新的处理器上很好地运行,
必须要具备指定集和工作模式上的兼容性和一致性.
Intel公司很清楚,
如果新处理器和老处理器不兼容,
则新处理器越多,它扔掉的也越多.
所以,讲述处理器的时候,必须从8086开始;
而且,要学习汇编语言,针对8086的汇编技术也是必不可少的.

8086的通用寄存器

8086处理器内部有8个16位的通用寄存器,
分别被命名为AX,BX,CX,DX,SI,DI,BP,SP.
通用的意思是,它们中的大部分都可以根据需要用于多种目的.

因为这8个寄存器都是16位的,所以通常用于进行16位的操作.
如,可以在这8个寄存器之间相互传送数据,它们之间也可进行算术逻辑运算,
也可以在它们和内存单元之间进行16位的数据传送或算术逻辑运算.
	
同时,这8个寄存器中的前4个,即AX,BX,CX,DX,
又各自可以拆分成两个8位的寄存器来使用,
总共可以提供8个8位的寄存器AH,HL,BH,BL,CH,CL,DH,DL.
这样一来,当需要在寄存器和寄存器之间,
或者寄存器和内存单元之间进行8位的数据传送或者算术逻辑运算时,
使用它们就很方便.

将一个16位的寄存器当成两个8位的寄存器来用时.
对其中一个8位寄存器的操作不会影响到另一个8位寄存器.

程序的重定位难题

处理器是自动化的器件,
在给出了起始地址之后,它将从这个地址开始,
自动地取出每条指令并加以执行.
只要每条指令都正确无误,
它就能准确知道下一条指令的地址.
这意味着,完成某个工作的所有指令,必须集中在一起.
处于内存的某个位置,形成一个段,叫做代码段.
	
要是指令并没有一条挨着一条存放,中间夹杂了其他非指令的数据,
处理器将因为不能识别而出错.
为了做某件事而编写的指令,
它们一起形成了我们平时所说的程序.
程序总要操作大量的数据,这些数据也应该集中在一起,位于内存中的某个地方,
形成一个段,叫做数据段.

段的划分是逻辑上的.
从本质上来说,是如何看待和组织内存中的数据.
段在内存中的位置并不重要,
因为处理器是可控的,
可让它从内存的任何位置开始取指令并加以执行.

假定我们有16个数要相加,
这些数都是16位的二进制数,
分别是0005H,00A0H,00FFH,....
为了让处理器把它们加起来,
我们应该先在内存中定义一个数据段,
将这些数字写进去.
数据段可以起始于内存中的任何位置,
既然如此,将它定在0100H处.
这样一来,第一个要加的数位于地址0100H,第二个要加的数位于地址0102H,
最后一个数的地址是011EH.

一旦定义了数据段,
就知道了每个数的内存地址.
然后,紧挨着数据段,
从内存地址0120H处定义代码段.
严格地说,数据段和代码段是不需要连续的.
代码段是从内存地址0120H处开始的,
第一条指令是A1 00 01,其功能是将内存单元0100H里的字传送到AX寄存器.
指令执行后,AX的内容为0005H.

第二条指令是03 06 02 01,
功能是将AX中的内存和内存单元0102H里的字相加,结果在AX中.
由于AX里的内容是0005H,而内存地址0102H里的数是00A0H,
这条指令执行后,AX的内容为00A5H.

第三条指令是03 06 04 01,
功能是将AX中的内容和内存单元0104H里的字相加,结果在AX中.
此时,由于AX里的内容是00A5H,内存地址0104H里的数是00FFH,
本指令执行后,AX的内容为01A4H.

后面的指令没列出,但和前2条指令类似,
依次用AX的内容和下一个内存单元里的字相加,
一直到最后,在AX中得到总的累加和.
当累加的总和超出了AX所能表示的数的范围[最大为FFFFH,对应十进制65535]时
会产生进位,但这个进位被丢弃.

在内存中定义了数据段和代码段之后,
就可以命令处理器从内存地址0120H处开始执行.
当所有的指令执行完后,就能在AX寄存器中得到最后的结果.

这里确实有一个难题.
在前面的例子中,
所有在执行时需要访问内存单元的指令,使用的都是真实的内存地址,
整个程序[包括代码段和数据段]在内存中的位置,是由我们自己定的.
我们把数据段定在0100H,把代码段定在0120H.
问题是,大多数时候,
整个程序[包括代码段和数据段]在内存中的位置不是我们能够决定的.
你所运行的程序,在内存中被加载的位置完全是随机的.
哪里有空闲的地方,它就会被加载到哪里,并从那里开始被处理器执行.
同样是那个程序,一旦它在内存中的位置发生了改变,灾难就出现了.

只要所有的指令都是连续存放的,
代码段位于内存中的什么地方都可以正常执行.
所以,处理器可以按你的要求,从内存地址1020H处连续执行,
但结果完全不是你想要的.
请看第一条指令A1 00 01,它的意思是从内存地址0100H处取得一个字,
将其传送到寄存器AX,
但是由于程序刚刚改变了位置,它要取的那个数,现在实际上位于1000H,
它取的是别人地盘里的数.

因为我们在指令中使用了绝对内存地址[物理地址],
这样的程序是无法重定位的.
为了让你写的程序在卖给别人之后,可以在内存中的任何地方正确执行,
就只能在编写程序的时候使用相对地址或者逻辑地址了,
而不能使用真实的物理地址.
当程序加载时,
这些相对地址还要根据程序实际被加载的位置重新计算.

任何时候,
程序的重定位都是非常棘手的事情.
在8086处理器上,
这个问题特别容易解决.
因为该处理器在访问内存时使用了分段机制.
我们可借助该机制.

内存分段机制

整个内存空间就像长长的纸条,在内存中分段,
就像从长纸条中裁下小段来.
根据需要,段可以开始于内存中的任何位置.
本例子中,分段开始于地址为A532H的内存单元处.这个起始地址就是段地址.
这个分段包含了6个存储单元.
在分段之前,
它们在整个内存空间里的物理地址分别是
A532H,A533H,A534H,A535H,A536H,A537H.
但是在分段之后,
它们的地址可以只相对于自己所在的段.
这样,它们相对于段开始处的距离分别为0,1,2,3,4,5.
这叫做偏移地址.

于是,采用分段策略之后,一个内存单元的地址实际上就可以用"段:偏移"或者
"段地址:偏移地址"来表示,这就是通常所说的逻辑地址.
比如,段内第一个存储单元的地址为A532H:0000H,
第三个存储单元的地址为A532H:0002H,
而本段最后一个存储单元的地址则是A532H:0005H.

为了在硬件一级提供对"段地址:偏移地址"内存访问模式的支持,
处理器至少要提供两个段寄存器,
分别是代码段寄存器[CS]和数据段寄存器[DS]

对CS内容的改变将导致处理器从新的代码段开始执行.
同样,在开始访问内存中的数据之前,
也必须首先设置好DS寄存器,使之指向数据段.

除此之外,
最重要的是,
当处理器访问内存时,
它把指令中指定的内存地址看成是段内的偏移地址,而不是物理地址.
这样,处理器一旦遇到一条访问内存的指令,
它将把DS中的数据段起始地址和指令中提供的段内偏移相加,
来得到访问内存所需要的物理地址.
	
设代码段的段地址为1020H.
数据段的段地址为1000H.
在代码段中有一条指令A1 02 00,
它的功能是将地址0002H处的一个字传送到寄存器AX.
在这里,处理器将0002H看成是段内的偏移地址,
段地址在DS中,
应该在执行这条指令之前就已经用别的指令传送到DS中了.
当执行指令A1 02 00时,
处理器将把DS中的内容和指令中指定的偏移地址0002H相加.
得到1002H.这是一个物理地址,处理器用它来访问内存,就可得到所需的数00A0H

如下一次执行这个程序时,
代码段和数据段在内存中的位置发生了变化,
只要把它们的段地址分别传送到CS和DS,它也能够正确执行.

8086的内存分段机制

前面讲了如何从逻辑地址转换到物理地址,
以使得程序的运行和它在内存中的位置无关.
这种策略在很多处理器中得到了支持,包括8086处理器.
但由于8086自身的局限性,它的做法还要复杂一些.

8086内部有8个16位的通用寄存器,
分别是AX,BX,CX,DX,SI,DI,BP,SP
其中,前四个寄存器中的每一个,
都还可以当成两个8位的寄存器来使用,
分别是AH,AL,BH,BL,CH,CL,DH,DL

在进行数据传送或者算术逻辑运算的时候,
使用算术逻辑部件[ALU].
比如,将AX的内容和CX的内容相加,
结果仍在AX中,
则,在相加的结果返回到AX之前,
需要通过一个叫数据暂存器的寄存器中转.

处理器能自动运行,这是控制器的功劳.
为加快指令执行速度,8086内部有一个6字节的指令预取队列,
在处理器忙着执行那些不需要访问内存的指令时,
指令预取部件可趁机访问内存预取指令.
这时,
多达6字节的指令流可以排队等待解码和执行.

8086内部有4个段寄存器.
其中,CS是代码段寄存器,DS是数据段寄存器,
ES是附加段寄存器.
附加段的意思是,它是额外赠送的礼物,
当需要在程序中同时使用两个数据段时,
DS指向一个,ES指向另一个.
可以在指令中指定使用DS和ES中的哪一个.
如没指定,默认使用DS.

SS是栈段寄存器,以后讲,且非常重要.
IP是指令指针寄存器,
它只和CS一起使用,
且只有处理器才能直接改变它的内容.
当一段代码开始执行时,
CS指向代码段的起始地址,
IP则指向段内偏移.
这样由CS和IP共同形成逻辑地址,
并由总线接口部件变换成物理地址来取得指令.
然后,
处理器会自动根据当前指令的长度来改变IP的值,使它指向下一条指令.

当然,
如果在指令的执行过程中需要访问内存单元,
则处理器将用DS的值和指令中提供的偏移地址相加,
来形成访问内存所需的物理地址.

8086的段寄存器和IP寄存器都是16位的,
如按原先的方式,
把段寄存器的内容和偏移地址直接相加来形成物理地址的话,
也只能得到16位的物理地址.

麻烦的是,
8086却提供了20根地址线.
换句话说,
它提供的是20位的物理地址.

提供20位地址线的原因很简单,
16位的物理地址只能访问64KB的内存,
地址范围是0000H~FFFFH,共65536个字节.
这样的容量,即使在那个年代,也不太够用.
1Byte = 8 Bits
1KB = 1024 Byte
1MB = 1024 KB
1GB = 1024 MB

20位的物理地址可访问多达1MB的内存,
地址范围从00000H~FFFFFH.
问题是,16位的段地址和16位的偏移地址相加,
只能形成16位的物理地址,
怎么得到这20位的物理地址呢?

为解决这个问题,
8086处理器在形成物理地址时,
先将段寄存器的内容左移4位[相当于乘以十六进制的10或十进制的16],
形成20位的段地址.
然后再同16位的偏移地址相加,
得到20位的物理地址.
如,对于逻辑地址F000H:052DH,
处理器在形成物理地址时,
将段地址F000H左移4位,变成F0000H,
再加上偏移地址052DH,
就形成了20位的物理地址F052DH.

这样,因为段寄存器是16位的,
在段不重叠的情况下,
最多可以将1MB的内存分成65536个段.
段地址分别为0000H,....,直到FFFFH.
在这种情况下,每个段正好16个字节,偏移地址从0000H到000FH.

同样,
在不允许段之间重叠的情况下,
每个段的最大长度是64KB,
因为偏移地址也是16位的,
从0000H到FFFFH.
这种情况下,
1MB的内存,最多只能划分成16个段.
每段长64KB,
段地址分别是0000H,1000H,2000H,...一直到F000H.

以上所说的只是两种最典型的情况.
通常,
段地址的选择取决于内存中哪些区域是空闲的.
假设82251H之后的地方可以加载你的程序.

接着,你的任务是定义段地址并设置处理器的段寄存器.
其中最重要的是段地址的选取.
因为偏移地址总是要求从0000H开始,
而82260H是第一个符合该条件的物理地址,
因为,它恰好对应着逻辑地址8226H:0000H.
符合偏移地址的条件,
所以完全可以将段地址定为8226H.

但是,举个例子来说,
如你从物理地址82255H加载程序,
由于它根本无法表示成一个偏移地址为0000H的逻辑地址.
所以不符合要求.段不能从这里开始划分.
区别在于,82260H可以被十进制数16[或十六进制数10H]整除,
而82255H不能.

这个例子可看出,
8086处理器的逻辑分段,
起始地址都是16个倍数,这称为是按16字节对齐的.

段的划分是自由的,
它可以起始于任何16字节对齐的位置.
也可以是任意长度,只要不超过64KB.
比如,段地址可以是82260H,段的长度可以是64KB.
这时,该段所对应的逻辑地址范围是
8226H:0000H~8226H:FFFFH,
其所对应的物理地址范围是82260H~9225FH.

同时,
正是由于段的划分非常自由,
使得8086的内存访问也非常随意.
同一个物理地址,
或者同一片内存区域,
根据需要,
可以随意指定一个段来访问它,
前提是那个物理地址位于该段的64KB范围内.
也即,
同一个物理地址,实际对应着多个逻辑地址.

猜你喜欢

转载自blog.csdn.net/x13262608581/article/details/124524182