学习交流加
- 个人qq:
1126137994- 个人微信:
liu1126137994- 学习交流资源分享qq群:
962535112
我们已经知道,处理器是一台电子计算机的核心,它会在振荡器脉冲的激励下,从内存中获取指令,并发起一系列由该指令所定义的操作。当这些操作结束之后,它接着再取下一条指令。通常情况下,这个过程是连续不断、循环往复的。
文章目录
1、寄存器和算数逻辑部件
电子计算机能能做很多事情。计算天气预报,看电影,听音乐,上网等,实际上都是以数学计算为基础。它之所以能够计算,是因为它特殊的设计。想要了解计算机为什么能够自动计算,请去阅读书籍《穿越计算机的迷雾》。本片文章主要讲解处理器的简单功能。
如图是一个处理器:
![](https://i-blog.csdnimg.cn/blog_migrate/15858bd5f6e331a7ed17c30906df727c.png)
在处理器的周围,有大量的引脚,这些引脚可以接受从外面来的电信号或者向外发送电信号(与外设进行信号的交互)。有很多引脚是复用的,假如现在我们要进行加法运算,我们就需要重复使用某一些引脚来依次将加数与被加数送入。一旦被加数被送入处理器,代表这个二进制数字的一组电信号就会出现在与该引脚相连的内部线路上(我们称这个内部线路为内部总线)。这是一组高低电平的组合,代表着二进制数中的每一位。当被加数被送进来了,接下来就要将加数送进来,但是此时内部总线上有被加数,所以处理器内部需要有一个电路来将被加数“锁存”。这个将被加数锁存的电路就是寄存器。它是一个存储器件。
同样,加数也要锁存进另一个寄存器。如图中RA与RB分别锁存的是被加数与加数。此后,被加数与加数将不再受处理器的内部总线的影响。寄存器是双向器件,它会将它锁存的数,在另一端产生一模一样的电信号(注意此时寄存器中依然锁存着之前的电信号,它只是在另一端产生一个一模一样的电信号),传送给相应的算数逻辑部件(ALU)进行计算。算数逻辑部件(ALU)计算出结果后,将结果送出,给另一个寄存器RC锁存,稍后再将RC中的内容通过内部总线送到处理器外部,或者再次送回RA RB寄存器进行其他计算。
那么,处理器是如何知道什么时候该干什么,各个部件在各个时间点该干什么,以及哪些部件在哪些时候使用内部总线以避免总线冲突呢?这些实际上都是由处理器内部的一个控制器控制的(图中没有画出)。
处理器总是繁忙的,所有数据都只能在寄存器中寄存一会,就必须被送往别处。早期的寄存器只能存储4比特、8比特或者16比特,分别叫做4位寄存器,8位寄存器和16位寄存器。现在的处理器大多是32位和64位的。
2、内存储器
上一节我们知道,处理器的计算过程实际上是借助于寄存器和算数逻辑部件进行的。那么计算的数据是从哪里来的呢?它是从一个可以保存很多数字的电路,叫做存储器。存储器的种类很多,我们常见的U盘,硬盘,磁盘,内存条,甚至寄存器等都是存储器。
我们这里主要说内存条。由于它通常是和处理器直接相连的,所以叫内存储器或者主存储器。又或者内存或者主存。和寄存器不同,内存用于保存更多的比特。对应用的最多的个人计算机来说,内存是按字节来组织的,单次访问的最小单位是1字节,这是最基本的存储单元。如下图所示是一个内存和内存访问示意图:
内存中的每一个字节都对应着一个地址。从0地址开始,由于上面的内存是65536字节,所以该内存的最后一个字节是FFFF(都是用的十六进制表示的)。为了访问一个内存,处理器需要给出一个地址。同时访问内存是什么方式?读方式或者写方式,所以处理器需要有一个读写控制。如果是写数据,处理器还需要有一个数据传输的控制。最后,处理器单次访问内存时,是以字节为单位访问还是以字为单位访问等,需要有一个字长控制来告知。
3、指令和指令集
从一开始设计处理器,就是想让它自动工作,另外,还需要提供一种机制,来允许程序员决定进行何种工作。处理器的设计者用某些数来表示该处理器该做什么,这些数,我们称为指令或者叫机器指令。
下面给个实际例子来分析指令在内存中的布局:
![](https://i-blog.csdnimg.cn/blog_migrate/689cd0eb6aabfdf85745a54516d849e8.png)
假设现在我们的处理器是从内存的0000地址开始取指令(实际上肯定不是从0地址开始,这里只是假设)。第一条指令B8 5D 00,该指令的意思是将立即数005D传送到RA寄存器,它具有一个字节的操作码B8。由此我们可以看出简单的一个B8 5D 00指令,是一个传送指令,且该指令将两个字节(005D)传送到RA寄存器,并且由于下一条指令的地址是在0003字节处,所以该指令肯定还知道它自己是一条3字节指令,以便下一次处理器可以直接去到0003字节处去取指令。
对于一些复杂的指令,一个字节的操作码肯定不够用,所以第二条指令8B 1E 3F 00的指令操作码是两字节8B 1E。
同时我们注意到上述前两条指令中,第一条指令是将一个数005D直接传送到寄存器,第二条指令,是需要从另外一个地址(003F)先取出数据,然后再将数据传送到寄存器。第一种的操作数005D我们称为立即数(表示可以立即将数传送而不需要再去另外一个地址取数据)。
由以上分析,我们很容易知道,上述的几条指令,首先将两个数放到寄存器中去运算,然后运算结果放到其中一个寄存器中,最终还需要将运算结果再传回到内存中以便该结果作为其他用处时处理器好可以从内存中得到计算结果。最后F4指令停机。
指令和非指令的二进制数据是一模一样的,在组成内存的电路中,都是一些高低电平的组合。因为处理器是按顺序取指令,并加以执行的,这样的话,如果指令和数据存放在一起,处理器很容易将数据的二进制当成指令,从而导致错误。所以我们需要将指令与数据分开存放。分别存放在不同的区域。存放指令的区域叫做代码区,存放数据的区域叫做数据区(这就是为什么进程的虚拟地址空间中代码和数据区分开的原因)。
一个处理器能够识别的指令的集合,称为指令集。
4、Intel 8086处理器
要想学好编程,就必须要懂底层原理。但是想要彻底学好底层,就要把汇编学好。想要学好汇编,不得不先学好针对8086的汇编技术。
4.1、 8086的通用寄存器
8086处理器内部有8个16位寄存器。分别被命名为:AX,BX,CX,DX,SI,DI,BP,SP。通用的意思是他们中大部分可以根据需要用于多种目的。他们的用处,将在后面的文章逐渐给出。
下图是几个寄存器:
可以看到,有介个寄存器,AX,BX,CX,DX,又可以分为两个8位的寄存器来使用。在后面文章中,我们会看到具体的应用。
4.2、 程序的重定位问题
我们知道,处理器是自动取指令和执行指令的,只要每条指令都正确无误,它就能准确的知道下一条指令的地址。所以,指令必须集中在一起,形成一个段,叫做代码段。
为了做某件事而编写的指令,形成我们所知道的程序。程序中肯定需要操作大量的数据,这大量的数据也应该集中在一起,位于内存中的某个地方,叫做数据段。
段在内存中的位置并不重要,因为处理器是可控的,我们可以让处理器在内存中的任何一个位置开始取指令并加以执行。
下面看一个图示:
假设上面是一个程序片段在内存中的位置,相关指令的用处已经在图中表明。这里不再赘述。但是现在有一个问题,就是我们写的程序(不管是C语言还是C++语言或者Java语言),最终要运行在内存中的位置,是无法确定的。因为你想一想,本身你的电脑中有各种程序在跑了,有qq程序在跑,微信程序在跑,或者还有微博等在跑,他们可能把你的内存都占完了,当你想运行你写的程序的时候,你的程序本想从0100H处开始存放代码段,但是可能这个时候0100H处有其他程序的代码或者指令在运行,此时你的程序只能去另一个位置存放你的代码和数据,假设你的代码下一次运行的时候代码段和数据段在如下位置:
此时你的程序的代数据段在内存的位置为1000H处,但是你会发现,你的代码段的第一条指令,还是将地址单元0100H处的内容传送到AX寄存器中。但是!!!此时0100H处的内容,并不是我们想要的内容。
产生这种情况的原因就是我们在程序中使用的是绝对内存地址。这样的程序是无法重新定位的。所以我们需要使用另一种方式访问内存:相对地址或者叫做逻辑地址。在任何时候,程序的重定位都是非常棘手的事情。在8086处理器中,这个问题得到解决。它使用了分段机制。
4.3、 内存分段机制
在内存分段中,段,是很多个连续的字节组成的。如图:
上图有一个7个字节的段。段的起始地址为A532。那么段中的地址从第一个字节开始,他们的地址此时就是段内的偏移地址(0000,0001,0002…)。而他们的实际的物理地址,又恰好等于段地址加上段内的偏移地址。可以用“段地址:偏移地址”,来表示段中的每一个字节的地址。
为了在硬件一级提供对“段地址:偏移地址”的支持。处理器至少要提供两个段寄存器,分别是代码段寄存器CS,数据段寄存器DS。
对代码段CS内容的改变,将导致处理器从新的代码段开始执行。当然,在访问数据之前,也是必须要提前设置好数据段寄存器DS的,使DS之指向数据段。
很重要的一点是,当处理器取指令执行指令的时候,它是把指令中指定的内存地址看成是段内偏移地址的。而不是内存的物理地址。那么当处理器遇到一条访问内存的指令时,它就会将DS中的数据段起始地址加上指令中提供的段内偏移地址得到访问内存所需要的物理地址的。
如下图所示:
代码段地址为1020H,数据段地址为1000H,在代码段中有一条指令:A1 02 00,它的功能是将地址0002H处的的一个字传送到寄存器AX。在这里处理器将0002H看成是段内偏移地址,段地址在DS中,应该在执行这条指令之前就已经用别的地址将数据段地址传送到DS寄存器中了。当处理器执行到指令A1 02 00时,处理器会将DS中的内容和指令中指定的便宜地址相加,得到内存中的一个物理地址,这个物理地址就是处理器要去访问的内存地址,就可以从该地址获取一个字:00A0H。
如果下一次执行该程序,代码段和数据段发生变化,只需要将程序的代码段地址和数据段地址分别传送到CS和DS就可以正常的执行程序了。
4.4 、8086的内存分段机制
前面讲了如何从逻辑地址转换到物理地址,以使得程序的运行和它在内存中的位置是无关的。上述策略在很多处理器上应用得到了支持。但是在8086处理器上,由于8086处理器是16位处理器,如果按照正常计算,给它提供16根地址线的话,那么8086处理器就只能用于寻址最多64KB的内存空间(65536字节=2的15次方+1)。在当时的年代64KB的内存还是不够的。所以8086处理器就将16根地址线扩展到20根地址线。从而使得可寻址的空间变为1M(16*64KB)。
但是有一个问题就是16位的段地址加上16位的段内偏移地址,还是16位的,并不是20位的。所以有一个解决办法就是在计算内存的物理地址时,先将段地址先左移4位,然后加上段内偏移地址,这样就可以得到20位的物理地址,就可以将整个1M的地址空间表示完全。
8086在进行分段时,并不是每一个地址都可以作为段地址,地址必须是16的倍数,才能作为段地址。
如下图,是其中的一种情况,我们从0地址开始分段,每段16字节(从任何地址只要这个地址是16的倍数,都可以作为段地址,每一个段内最低有16字节(可以分为65536个段)的内存,最高有65536(可以分为16个段)个字节的内存)
4.5 、8086 处理器内部组成框图
最后我们再来看一下8086处理器内部组成框图:
上图中,我们知道8086内部有8个16位通用寄存器,分别为AX,BX,CX,DX,SI,DI,BP,SP。ALU是算数逻辑部件,用于算数逻辑运算或者数据传送。标志寄存器是用于控制各种依赖于标志位的标志来进行相应的指令执行的,这个可以等到以后的文章中进行详细的说明,目前不需要理解。处理器可以自动执行,依赖于控制器的控制。
指令队列缓冲器,又名:指令预取队列。什么意思呢?就是当你的CPU在忙于做其他事情(一般是指执行那些不需要去内存中取的指令)而并没有去内存中取指令执行时,此时指令队列缓冲器(指令预取队列)就会去内存中取出6个字节的指令放到指令队列缓冲器,那么当CPU该执行内存中的指令时,它就会就近向指令队列缓冲器中去取指令,这样的话就会比去内存中取指令要快很多,毕竟内存还是通过外部总线与内存条相连接的。
CS是代码段寄存器。DS是数据段寄存器。SS是栈段寄存器(很重要,以后会讲)。ES是额外的段寄存器,它的用处相当于补充段寄存器,当你的DS在使用时,但是你还要访问另一个数据段,那么此时就可以使用ES寄存器(后面的文章中我们就用ES寄存器指向显卡的内存区域,用来寻址显存,从而控制显卡)。IP寄存器,是存代码段的段内偏移地址的。那么指令的地址此时就可以用“CS:IP”来表示。
上面的与总线控制逻辑相连的16位总线,实际上是16位的数据总线与20位的地址总线的低16位复用的。20位代表的是20位的地址总线是(当然,低16位是与数据总线复用的)。
5、 总结
想要写出优秀的应用程序,就必须对系统编程有深入的理解。想要对系统编程有一定的理解,就必须理解操作系统原理。我选择从X86汇编开始学习,逐步深入理解操作系统与计算机系统之间的关系。
本片文章可以让我们学会
- 寄存器与算数逻辑部件的简单关系
- 内存储器
- 指令和指令集
- 程序的重定位问题
- 处理器与内存之间的关系
- 内存分段机制
学习探讨加
qq:1126137994
微信:liu1126137994