电脑怎样执行编程语言的?

2023-10-26


链接:https://www.zhihu.com/question/29227521/answer/154819061
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

这个问题真的是很大,让我们自顶向下的解释。


在顶层,程序员编写出来的都是源代码。源代码可以使用各种高级语言写成,例如 c/c++ c# java python 等等;也可以使用对应平台的低级语言写成,例如汇编。想必你已经了解其中的过程了。

到这一步为止,距离最终机器可以执行的指令还有一大步要走。

首先要面临的一个问题是:源代码都是以人类语言写成的。即便是能够和机器指令一对一翻译的汇编代码,依然是人类语言。计算机无法理解其中的含义,所以不可能执行。

所以我们需要将人类语言翻译为计算机语言。计算机能听懂的语言,就叫做机器语言,简称机器码。


在这里说几句题外话。

在计算机历史的上古时代,大概是上个世纪50年代之前。那时编译理论和形式语言还没有得到发展。几乎所有的程序都是直接由机器码写成的。比如由工程师直接将二进制机器码和数值编写在打孔卡上,通过读卡机读入计算机存储器,然后执行。

而打孔卡长这个样子:

<img src="https://pic1.zhimg.com/50/v2-c8536a9e501f9e21ef1dd6d9ef0a6096_hd.jpg" data-rawwidth="2232" data-rawheight="1004" class="origin_image zh-lightbox-thumb" width="2232" data-original="https://pic1.zhimg.com/v2-c8536a9e501f9e21ef1dd6d9ef0a6096_r.jpg">

(来自 wiki,80列标准 IBM 打孔卡,你能读出上面是什么意思吗?)

计算机的基本架构虽然经过了将近百年的发展,但是核心的模型倒是一直很稳定,都是存储程序模型。

首先将程序指令从外存(打孔卡,磁带,硬盘,软盘,光盘,闪存卡,网络等)读入内存,然后让处理器从内存按顺序取指执行,结果写回内存中。

在那个年代,人们对程序运行原理的理解是不存在什么障碍的。工程师怎么写,计算机就严格的按照指令执行。每一条指令对应一个步骤。最后的到结果。

在这种条件下,程序开发绝对是顶尖的职业,首先能够理解目标机的架构就需要相当的功夫了。其次还要按照机器的方式思考,写出正确无误的指令序列。

这样的开发过程无疑限制了计算机行业的发展。

同时,即便是擅长于按照机器方式思考的工程师,也认为机器指令太难记了。如你所见,在打孔卡上准确无误的写上指令真是头疼的要死。所以工程师们开发了一套助记符,用来指示对应的机器码,这样以来,程序的编写和 debug 就方便多了。到上世纪40年代末期,就已经有一批成熟的助记符体系了。

<img src="https://pic1.zhimg.com/50/v2-167f82b471c3e2f12622f731a9e5cbd9_hd.jpg" data-rawwidth="1330" data-rawheight="940" class="origin_image zh-lightbox-thumb" width="1330" data-original="https://pic1.zhimg.com/v2-167f82b471c3e2f12622f731a9e5cbd9_r.jpg">

(ARM v7 汇编指令卡中的某一页)

关于助记符的话题,暂且搁置。


回到正题。为了将人类语言翻译成机器变成机器能够理解的语言,还需要进行翻译。就好像你不懂英语,英语可以翻译成汉语,这样你就能明白其中的含义。对于计算机来说,这个过程是一样的。不过计算机对于翻译有更高的要求。人类之间互相翻译语言,有一些微小的出入并不影响理解,计算机为了能够准确的得到结果,要求这个翻译的过程,必须保证“将一种语言翻译成涵义相同的等价的另一种语言”。

在早期,程序的规模还比较小,翻译的过程可以人工的进行。利用查表的方式,最终是可以得到等价的机器码序列。随着计算机科学的发展,程序规模膨胀的越来越快,人工翻译变的没有可行性。此时就有人提出,编写一套软件来进行这个翻译的过程。

一开始人们只用汇编语言进行程序开发。所以只需要将汇编语言翻译为机器语言就可以了。这是相当直截了当的过程,因为汇编语言的助记符和机器指令是一一对应的关系。所以只需要完成一个能够自动查表并转换的程序即可。很快,这样的程序就被发明了出来。我们称之为“汇编器”。

伴随着汇编器的发展,工程师又开始想要偷懒。他们认为,既然汇编器可以将汇编指令翻译成等价的机器码,那么在翻译之前一定也可以做一些预先处理的工作,将一个助记符转换为多个助记符组成的序列。这样以来,开发人员就可以使用较少的代码,写出较多的内容。同时将常用的一些程序结构编写成对应的助记符,在需要时就使用这个助记符,还可以帮助开发人员减少程序出错的可能。简直太好了。于是,人们又在汇编器中引入了宏指令。

所谓“宏(macro)”就是一套预先定义好的指令序列。每当汇编进行的时候,先预处理一次将宏等价的展开,然后再进行翻译。如此,源程序变的更加容易理解了。


宏的引入,催生了程序结构化表达。在今天的汇编语言当中,我们也可以像使用高级语言的 if else for while 语句一样,使用等价的结构语句。只不过,汇编中的结构语句都是宏实现的。


结构化表达给了一些计算机科学人员启发。能不能更进一步,使用完全结构化,脱离某个对应机器平台的形式化语言来描述一个源程序?于是,就有了高级语言及其编译器。

开发人员利用高级语言编写程序,然后利用对应的编译器生成中间代码,最后再将中间代码变成机器码。中间代码可以是等价的汇编代码,也可以是其它类型的代码例如 JVM 的字节码。最终处理中间代码的程序可以是一个对应平台的汇编器,也可以是一个解释器。在这里姑且隐去这些细节,将编译的最终产物都视为一系列可以被执行的二进制机器码。关于编译器的更多内容,在网上可以找到很多详细的资料。在这个话题下,编译器不是核心问题,我就不再深入讨论了。


至此,就得到了一个可以被执行的程序了。这个文件的内容是一系列二进制指令和数据组成的序列。它能被装入机器的内存,并且可以被处理器解码执行。


但是,为什么是二进制


说回来,计算机其实是长期使用的一个简称。严格的讲应该叫做“电子计算机”。但是计算机的形态并不限于电子式计算机。算盘,计算尺,对数计算表都可以算作广义上的计算机,同时在电子式计算机出现之前,它的还有一个机械式计算机的表亲。

<img src="https://pic3.zhimg.com/50/v2-39e7480eed57ce4010d52f631b34e451_hd.jpg" data-rawwidth="1704" data-rawheight="2272" class="origin_image zh-lightbox-thumb" width="1704" data-original="https://pic3.zhimg.com/v2-39e7480eed57ce4010d52f631b34e451_r.jpg">

(来自 Wiki 。 查尔斯·巴贝奇 的分析机。蒸汽动力驱动,采用十进制,其内存能够存储1000个50位的十进制数,相当于20.7 KB 的 SRAM 或 DDRAM。采用打孔纸带读入程序,具有类似汇编语言的助记符编程系统,是图灵完备的。很蒸汽朋克,嗯?)


可是我们并不认为算盘以及计算尺和现代计算机是同一个东西。最核心的区别在于,现代计算系统是可编程的。按照这个定义,上面的分析机也是现代电子是计算机的鼻祖。它身上的核心模型一直继承至今。

在分析机上,已经实现了 “ 存储程序计算机 ”。

这也就是现代计算系统的基本概念:

  1. 以运算单元为中心
  2. 采用存储程序原理
  3. 存储器是按地址访问的,线性的空间
  4. 控制流由程序的指令流产生
  5. 指令由操作码和操作数组成

这一概念所描述的计算模型具有以下的过程:将完整的程序装入存储器后,运算单元依照地址按顺序的从存储器中取出指令和数据且执行。指令序列就像流水一样“流”入运算单元,当指令流尽,就意味着程序结束了。

<img src="https://pic1.zhimg.com/50/v2-4dff85953dd98440a398a57f3a7e5c8e_hd.jpg" data-rawwidth="543" data-rawheight="223" class="origin_image zh-lightbox-thumb" width="543" data-original="https://pic1.zhimg.com/v2-4dff85953dd98440a398a57f3a7e5c8e_r.jpg">

对于计算机,自然是希望运算的速度越快越好。所以机械式运算很快就淘汰了。取而代之的就是电子式计算机。


电子式计算机的硬件基础,就是数字电路。因为二进制可以很自然的表示开和关的两种状态,高和低的两种状态,通和断的两种状态,等等。所以很快就取得了主导低位,其它进制的数字电子器件沦为小众。

理论上,二进制和十进制表示的数的范围是一样多的。因为实数集是一个连续同,不同进制实质上是对数集的不同分割。


基于二进制数字电子器件制造的电子式计算机自然就需要二进制的输入输出。


到了这个层次,我们基本上解释了高级语言源程序是如何成为计算机可以识别的二进制指令序列的。接下来的问题是,计算机如何识别并执行二进制指令呢?


通用处理器被称为“通用”,就是因为它不限定于特定用途。路边上买一个计算器。只能计算四则运算,而计算机还能进行字处理,可以玩游戏看电影。都有赖于通用处理器提供的运算能力。

为了实现通用的目标,处理器在设计之初就不能对未来可能进行的运算进行限制。但是未来的可能性是无穷的。处理器不可能穷尽所有可能。

所以,处理器提供了一套它能够支持的运算操作的集合,称为“指令集”。指令集就限定了该处理器能够进行的所有运算。而且这些运算通常都是有关于数字的运算。如果我们想解决一个任意问题,那么首先要把这个问题转换为一个数字问题,再把数字问题的解答过程,用指令集当中的指令求解。

将其它问题转换为数学问题的一种方法就是编码。比如我们常见的 ASCII 码表,就是把英语字符数字字符以及电报传输过程中的控制字编码成对应的数字。例如字符 a 就等于数字97。


处理器的指令集同样是经过编码的。所以我们才能用二进制数字流来表示指令。

举个例子。在一个典型的 Intel IA-32 处理器上所支持的 x86 指令集。假设我们想将一个字节的数据从内存移动到 al 寄存器,不妨就让这个数据在内存中 0x20 (十六进制表示的32)号字节的位置好了。那么,我们要写出汇编代码:


mov al, 30h

将这一行代码送入汇编器,得到对应的机器码为:


0xB0 0x20

二进制的表示为:


1011 0000  0010 0000

其中 0xB0 就是我们的指令,也就是执行第 176 号指令。这条指令的意思是:从内存中指定的位置搬移数据一个字节宽度的数据到 al 寄存器。地址由紧跟在本指令后的数给出,在这里就是 0x20。

指令集中的每一个指令都可以这样编码。每一条指令都定义了一系列的操作。

如此,只要按照顺序的从存储器读入指令代号和数据,就可以让程序执行下去。

你又要说了,那如果我有循环,有条件判断怎么办?

简单。处理器为了能顺序的取指并执行,需要知道当前指令的下一条指令在哪里。为什么不是这一条指令在哪了?因为这一条指令已经取回来了,所以它在哪里就不重要了。为了记录当前指令的下一条指令的位置,处理器内部有一个存放了这个地址的电子装置,实现上它是一系列门电路组成的锁存器,叫做 IP 寄存器(也有叫做 PC 的,这里统称为 IP)。IP 的值可以在运行时被修改。那么只要提供了能够修改 IP 值的指令,就能改变程序的执行流程。可以返回到之前的某个位置,也可以一次前进到之后的某个位置。这个过程叫做“跳转”。

所谓循环和判断,本质上都是判断并跳转。

用一个程序来做一个直观的说明。这个程序很简单。求出一个数组中所有数的和,然后返回这个值,如果这个值是0,则返回一个 -1。

和它等价的 C 代码如下,这里我们将结果返回运行时:


int main(void) {
    int numbers[5] = {1, 2, 3, 4, 5};
    int result = 0;

    for (size_t i = 0; i != 5; ++i) result += numbers[i];

    return (result == 0 ? -1 : result);
}

编译器产生的汇编文件长什么样子呢?长这样的:


CONST	SEGMENT
constNumbers: 0x01, 0x02, 0x03, 0x04, 0x05
CONST	ENDS

TEXT	SEGMENT
numbers SIZE 20 BYTE

main	PROC

	sub	esp, 20	
	movaps	xmm0, XMMWORD PTR constNumbers

	xor	eax, eax
	push	5
	pop	edx
	movups	XMMWORD PTR numbers[ebp], xmm0
	mov	DWORD PTR numbers[ebp+16], edx

	mov	ecx, eax
Loop:
	add	eax, DWORD PTR numbers[ebp+ecx*4]
	inc	ecx
	cmp	ecx, edx
	jne	SHORT Loop

	or	ecx, -1
	test	eax, eax
	cmove	eax, ecx

_main	ENDP
_TEXT	ENDS
END

为了便于解释,这里隐去了很多细节,并且使用了很多伪代码。上面的汇编程序是不经修改是无法通过编译的。

等价的二进制文件又是什么样子呢?为了方便阅读,我稍稍整理了一下,并且加上了对应的汇编代码,它长这个样子:

<img src="https://pic4.zhimg.com/50/v2-8b6115a3b19af8d6b459aeb45b0223b9_hd.jpg" data-rawwidth="658" data-rawheight="307" class="origin_image zh-lightbox-thumb" width="658" data-original="https://pic4.zhimg.com/v2-8b6115a3b19af8d6b459aeb45b0223b9_r.jpg">

(第8行操作数应当分为两列,这里有一个小错误。)

同样的,还是省去了很多细节。绿色的部分就是机器码。

我完全理解使用助记符和高级语言的重要性。否则谁能通过机器码一眼看出一段程序的含义呢?


当程序装入内存以后,IP将被(另外的某个程序,可能来自操作系统,或者其它软件)设置为 1,意思是:下一条要读取的指令在 1 的位置。然后处理器就开始读入指令。

为什么处理器会读入指令呢?它是收到某个信号才会读指令吗?简单的讲,处理器从上电到掉电的整个过程当中只做三件事情,那就是:


  1. 从内存读取一条指令和指令携带的操作数,同时 IP + 1
  2. 解码并执行指令
  3. 回到 1

所以不需要什么信号。在上一条指令将 IP 的值修改为 1 之后,处理器就已经完成跳转,找到程序入口了。

处理器取指,读入第一条指令 0xce83。这里要插入一点,Intel 的处理器采用的是小端数据格式,就是说一个数的高位放在地址较高的地方,低位放在地址较低的位置。所以要倒过来读,在这里就不详细解释了,略过。

处理器将这条啊指令送入解码器,解码的结果告诉处理器,应当执行“将 esp 寄存器中的值减去一个指定数,该数由紧随指令的连续四个字节指定”的操作。然后处理器通过数据总线连续读入四个字节,得出操作数应该是 0x14(十六进制的20)。接着就执行了这个操作,IP + 1,继续取出下一条指令。

这个过程是很好理解的。总之就是这样的循环。直到断电。


再注意一下行号 11 和 12 标识的代码。11 行将执行比较 ecx 寄存器中的值和 edx 寄存器内的值。根据不同的结果,12 行指令将有不同的行为:


  • 两个值相同的时候,12 行指令什么也不做,IP + 1。
  • 两个值不同的时候,12 行指令会将 Loop 标号的地址写入 IP。 IP = 9。

程序走着走着就走回去了。这就是比较与跳转。简单吧。

而 10 行的代码将会使 ecx 寄存器内的值增长,每次经过 10 行都 +1,随着循环的进行,程序流不断的跳转到 9 行,然后经过 10 行。在某一次经过后,ecx 等增长正好令 ecx = edx 成立。这时候 12 行将什么也不做,IP 指向 13,程序又继续进行下去了。


接下来,进入处理器的层次来理解它如何工作的。在这里我们要讨论四个问题:

  1. 指令是如何表示的?
  2. 数据是如何取回的?
  3. 指令是如何解码的?
  4. 指令是如何执行的?

程序运行的过程,上面已经提到过了。程序是完整的装入内存中的。运算器能够直接操作的只有存储器中的数据。他们之间的硬件连接如图所示:

<img src="https://pic3.zhimg.com/50/v2-3b06f9699950184c4b4c210d145eb13c_hd.jpg" data-rawwidth="648" data-rawheight="424" class="origin_image zh-lightbox-thumb" width="648" data-original="https://pic3.zhimg.com/v2-3b06f9699950184c4b4c210d145eb13c_r.jpg">

sorry,搞错了,是这个:

<img src="https://pic2.zhimg.com/50/v2-1ad9ef2a8a07b07281896f4f040c9457_hd.jpg" data-rawwidth="800" data-rawheight="668" class="origin_image zh-lightbox-thumb" width="800" data-original="https://pic2.zhimg.com/v2-1ad9ef2a8a07b07281896f4f040c9457_r.jpg">

图上黄色的一根粗线其实以一排并列的导线,在这里是 8 根导线并列在一起。只是看起来画在了一起,其实是互相分开的。

使用 8051 及其外部扩展存储器接口电路来说明问题主要是为了简便。在不失准确性的前提下,我依然隐去一些细节,方便理解。


访问存储器的过程主要关注两个问题:

  1. 送出地址
  2. 取回数据

考虑一般的访问过程,当运算器执行如下操作时:


mov al, 0xD0D0

将会发生什么呢?

首先 mov 指令指定了数据搬移的操作,第二个操作数是一个立即数参数,直接给出了地址。现在就要到存储器当中去找这个地方了。

处理器不能直接操作存储器的具体单元,但是它可以请存储器将对应单元中的数据准备好,然后取回来。你肯定有过取快递的经历,菜鸟驿站去过吧,门市点不会让你亲自进仓库去找快递的,但是你可以告诉快递小哥你的单号,然后他进去帮你找到,最后把包裹交给你。内存和这一个意思。

处理器首先将地址放到地址总线上,地址总线就是图上 D0-D7 和 A8-A15 这15 根导线组成的。

处理器将自己的端口设置成对应的值,就把地址放到了总线上。0xD0D0对应的端口状态应该是:

<img src="https://pic2.zhimg.com/50/v2-d914aedb579fae88ba0d43ca7d4fc43e_hd.jpg" data-rawwidth="1155" data-rawheight="41" class="origin_image zh-lightbox-thumb" width="1155" data-original="https://pic2.zhimg.com/v2-d914aedb579fae88ba0d43ca7d4fc43e_r.jpg">

(图有点小)

然后,处理器告诉存储器,我准备好取数据了,地址在总线上,请你准备数据。具体的方式就是拉低 \overline{OE} 端口的电压到地电位(一般就是 0V)。存储器得到这个消息后,就从总线上取得地址。然后解码这个地址,找到对应的数据,假设数据是0x11吧,然后把数据再放回总线 D7-D0上。

处理器在发出取指指令后会等待一段时间,然后就从总线上取回数据。取回的数据就当做存储器的回应。至于这个等待的时间具体多长,这是两个设备间相互约定好的。不需要关心。

最后,将总线上取得的数据 0x11 放入 al 中,指令完成。


可能有的读者就很迷惑了,为什么放到总线上就能传递数据呢。

真实的情况是,总线上传递的是电压的信号。这也是为什么使用二进制方便的原因。总线就是一组导线,在这一组导线上,一一对应的连接了处理器和存储器的端口。虽然电子在导体内的移动速度很慢,但是电场的传播速度却是光速。所以当总线一端的端口建立了电位之后,另一端的电位将立刻改变。此时信号就已经从一个器件传递到了另一个。器件之间信号的传递,依赖的就是端口上电压的改变。器件对总线数据的读取,就是读取端口上电压的高低。而二进制可以使这个问题变的很简单。只要端口上能够反应电压的高和低区别就足以传递信号了,一般的,高电位的区间在 3.3V - 5.0V 之间,而低电位在 0V - 2.2V 之间。考虑到总线都是板级的传输,距离很近,总线上电场传播所需要的时间可以忽略掉。那么一组总线传播数据的速度就取决于其两端端口上电位改变的速度。这可比读卡器读卡高了不知道哪去了。也比磁盘寻道和读取快的多。


在数字电路中,我们一般用 0 表示低电平,用 1 表示高电平。

上面提到过,mov 指令的编码是 0xB0。这个编码是什么意思呢?将其写作二进制会发现


0xB0 = 1011 0000

刚刚我们已经介绍过了。0和是表示的就是电压的高低。现在一切都清楚了。数据的编码其实就说说的端口上电压的高低状态。如果处理器的输入端口在读入指令时读入的端口情况是从 D7到D0为 高低高高 低低低低。那么就读入了 1011 0000。


那么我们已经知道如何取数据了。取指令也是一个方法。只不过取指令的过程是自动的,指令的地址总是 IP 的值。取回的指令总是送入指令解码器当中。

根据读入数据时处理器所处的不同阶段,将会给读入的数据一个不同的解释。读指令阶段就会把数据送入解码器。读数据阶段就会把数据送入另外的地方。


接下来,就需要进行指令解码。指令解码本身也是一个非常大的话题,其实单独拿出来也可以写出和本篇一样长的文章了。在这里只能概略的介绍一下。


处理器本身要完成某些特定的运算,在硬件上是需要某些特定结构的电路的支持的。比如你要完成一次加法,就需要一个带有加法器的电路。完成一次位移,就要有带移位寄存器的电路。简单的说,任何一条指令,都需要一组特定的电路来提供支持。但是人们通过长期的对数字电路的研究发现。几乎所有的运算,都可以通过有限的几种器件的不同组合来完成。这样的话,我们的通用运算器当中,可以包含一些要素器件,然后通过运行时改变它们的连接来实现不同的功能。这就是我们思考指令编码的方向。

其实在电子式计算机刚刚诞生的时候,就已经实现基本运算器的复用了。运算中心中包含了一组基础的运算器,它们的输入输出端口上同时连接了很多组不同的电路,每执行一条指令的时候,都选择其中特定的一组电路,使其生效,而让剩下的电路失效。这样在指令执行的过程中,这一组执行电路就可以独占整个运算器。当运算结束拿到结果后,电路再将运算器释放掉。就可以准备下一次的运算了。

在早期,还没有指令编码技术。要使用不同的指令,必须改变电路间的硬连接。也就是要把一组插头从这里拔下来插到另外的地方去。世界上第一台通用电子计算机 ENIAC 的操作方式就是如此。编程的方式是女工进机房去接插头。

<img src="https://pic1.zhimg.com/50/v2-42cc3730a33fa5303e52eaa50dfd91ce_hd.jpg" data-rawwidth="382" data-rawheight="214" class="content_image" width="382">

(假设我们有三条可编程指令流水线,那么如果我们想依次执行数据转移,异或,求和的操作,就需要连接 #1 的 move, #2 的 xor 和 #3 的 add)

而后出现了指令编码。送入的指令被解码器解码后,自动启动一组对应的电路。

<img src="https://pic3.zhimg.com/50/v2-af944f1f1713ade91eb16c40e468a422_hd.jpg" data-rawwidth="403" data-rawheight="180" class="content_image" width="403">

这样说也许很难让人明白“自动”的含义。所以我在这里实现一个简单的编码指令处理器。在这里我们只实现 3 条指令:

  • 指令0:将输入端的数据存入寄存器 a
  • 指令1:将输入端的数据存入寄存器 b
  • 指令2:取寄存器 a 寄存器 b 中的值求和,将结果放入寄存器 a

在这里我们只研究解码,不管其它的因素,这样就简化了问题。不多说,直接上图:

<img src="https://pic2.zhimg.com/50/v2-87814e38f146df96bee0b3d745ef38be_hd.jpg" data-rawwidth="1182" data-rawheight="937" class="origin_image zh-lightbox-thumb" width="1182" data-original="https://pic2.zhimg.com/v2-87814e38f146df96bee0b3d745ef38be_r.jpg">

最顶上的 instraction register 和 instraction decoder 的部分就是指令解器。首先将指令读入一个寄存器,然后解码。实际的运算器也是这样的流程。图中蓝色的就是数据总线,寄存器内的值分别是两个寄存器 Qx 端口上的值。

让我们启动他它,来算一下 0x10 + 0x0F (16 + 15)是多少。

<img src="https://pic3.zhimg.com/50/v2-f36b90c5cb14f23d542e43e9d40779cf_hd.jpg" data-rawwidth="1182" data-rawheight="936" class="origin_image zh-lightbox-thumb" width="1182" data-original="https://pic3.zhimg.com/v2-f36b90c5cb14f23d542e43e9d40779cf_r.jpg">

上电之后,我们注意到:

  • 寄存器 a 和寄存器 b (右下两个)都被初始化为 0xFF
  • 输入端口(左下角)上的值为 0x00
  • 指令寄存器(上方)当中当前的指令为 0x0F (15号),这是一条未定义的指令,所以没有任何效果。

首先我们要执行


mov a, 0x10

当指令读入后,在指令输入端将会是 0x00 的状态,译码输出端口上输出全 0,指示出目前要执行0 号指令。同时选中了 0 号指令的执行电路。

数据端的输入为 0001 0000。mov 命令的状态下,输入选择器选择输入端口的数据放到总线上。同时,寄存器 a 被激活,将总线上的值存入:

<img src="https://pic1.zhimg.com/50/v2-11a49ccbdf02d9f65d7bd431bea04fce_hd.jpg" data-rawwidth="1182" data-rawheight="936" class="origin_image zh-lightbox-thumb" width="1182" data-original="https://pic1.zhimg.com/v2-11a49ccbdf02d9f65d7bd431bea04fce_r.jpg">

(可以看到 0x10 已经被存入寄存器了)

第二条指令,我们将启动寄存器 b,然后存入数据。指令为:


mov b, 0x0f
<img src="https://pic1.zhimg.com/50/v2-f10de0d8cfc7dd98ee94d89db11c4332_hd.jpg" data-rawwidth="1182" data-rawheight="936" class="origin_image zh-lightbox-thumb" width="1182" data-original="https://pic1.zhimg.com/v2-f10de0d8cfc7dd98ee94d89db11c4332_r.jpg">

instraction decoder 的 1 号输出被选中,此时激活了 1 号指令的电路。输入端的 0x0F 被存入了寄存器 b。而寄存器 a 中的值保持不变。

第三条指令,2 号指令,求值。


add

没有给出操作时是因为操作是已经隐含的指明了,就是 a 和 b:

<img src="https://pic1.zhimg.com/50/v2-727b3d793df16781896a5d97d67b7742_hd.jpg" data-rawwidth="1182" data-rawheight="936" class="origin_image zh-lightbox-thumb" width="1182" data-original="https://pic1.zhimg.com/v2-727b3d793df16781896a5d97d67b7742_r.jpg">

译码器的 2 号输出选中了。全加器完成了运算(左侧是第四位,右侧是高四位),结果放上了总线,被锁存到了加法器的输出缓冲器当中。

同一时间,双输入选择器也被激活。它截断了输入端口的连接而选择加法器输出缓冲值作为输入,将其放上了总线。

寄存器 a 从总线取得数据,存入。完成了指令。


看看,结果是 0x1F,恰好就是我们预期的 31。


实际中的处理器的处理过程比这个复杂得多。这里为了方便理解,做了很多简化。但是概念都是相同的。处理器自动的从内存中读入指令和数据,然后解码,启动对应的电路,最后拿到结果。如此往复。


到此为止,已经几乎完全说明了计算机的运算原理,以及高级语言和机器语言的关系。但是我们依然可以更进一部,探究一下数字电路的构成。编码器是怎么运行的?寄存器是怎么锁存数据的?


上面一直在说解码器,那么解码器到底是什么?

处理器内部的指令解码器可能非常复杂,也许是一个器件,也有可能是一组器件,或者是可编程的硬件电路(对的,硬件电路也是可以编程的,例如 FPGA)。

而这里,我在上面的例子中使用的解码器:74LS42 4 Lines to 10 Lines BCD to Decimal Decoder (4线10线BCD译码器)的内部结构是这样的:

<img src="https://pic3.zhimg.com/50/v2-6698baf03b1f6bf7d5e027d4090ca48f_hd.jpg" data-rawwidth="428" data-rawheight="414" class="origin_image zh-lightbox-thumb" width="428" data-original="https://pic3.zhimg.com/v2-6698baf03b1f6bf7d5e027d4090ca48f_r.jpg">

可以看到,BCD 输入端(左边)输入后首先连接了非门(NOT),然后进入一个选择矩阵,最后通过三入与非门(NAND)输出。


与门(AND)、或门(OR)、非门(NOT)是数字电路中,最基础的三种逻辑门电路。它们的组后构建了大量的实用器件。

关于三种逻辑门,它们的特性可以使用真值表来表示:

<img src="https://pic2.zhimg.com/50/v2-9620ecea93ad929c25f8e95adb4a6138_hd.jpg" data-rawwidth="291" data-rawheight="231" class="content_image" width="291">

(1 代表真,0 代表假)

与门:所有输入全为真,输出为真;

或门:任意一个输入为真,则输出真;

非门:输出总是输入的反。


利用这三个门就可以做很多有趣的事情了。

你需要理解欧姆定律,理解电阻、电容、二极管、三极管/场效应管,理解与或非门电路,组合逻辑电路,时序逻辑电路,理解CPU和指令集,机器代码。

到了这一步,你就知道机器代码的一堆01010010011011是怎么通过控制单元,逻辑运算单元,寄存器,以及底下的加法器,编码器,译码器,多路选择器通过高电平低电平的脉冲跑起来的。

你还要理解汇编器,理解编译器和连接器,理解其中的词法分析,语法分析,代码生成,你需要理解操作系统。

到了这一步你就能明白电脑怎么讲你用高级语言编写的程序转换成机器代码的一堆01010010011011并提交给计算机执行的了。

涵盖这些知识的就是电路分析,模拟电路,逻辑电路,单片机,计算机组成原理,离散数学,数据结构,编译原理,操作系统这些计算机专业的课程。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

电脑怎样执行编程语言的? 的相关文章

  • 电子科技大学编译原理复习笔记(八):语义分析

  • LLVM Language Reference Manual

    摘要 该文档是LLVM汇编语言的参考指南 LLVM是基于表示的静态单赋值 SSA 该表示提供类型安全 低层级操作 灵活性 及简洁表示所有高层级语言的能力 这是贯穿各方面LLVM编译策略的通用代码表示 简介 LLVM代码表示用于三个不同形式
  • 编译实验(三)目标代码生成

    通过词法分析 语法分析 语义分析 最后产生了四元式 而代码生成则是通过四元式来完成 我们先从简单开始做起 编译实验项目下载链接 http download csdn net download supersmart dong 10224159
  • 编译原理 课程设计 LR(1)分析法

    作业目的 构造LR 1 分析程序 利用它进行语法分析 判断给出的符号串是否为该文法识别的句子 了解LR K 分析方法是严格的从左向右扫描 和自底向上的语法分析方法 作业题目 1 对下列文法 用LR 1 分析法对任意输入的符号串进行分析 0
  • 【编译原理】FIRST集合和FOLLOW集合

    FIRST集合 定义 可从 推导得到的串的首符号的集合 其中 是任意的文法符号串 规则 计算文法符号 X 的 FIRST X 不断运用以下规则直到没有新终结符号或 可以被加入为止 1 如果 X 是一个终结符号 那么 FIRST X X 2
  • 合工大 编译原理 实验

    目前仅有实验一二三四 Windows桌面应用程序项目 开发语言 c 开发环境 Visual Studio 实验一 GitHub 实验二 传送门 实验三 传送门 实验四 传送门 实验一大致功能 支持程序运行时输入关键词 支持已保存关键词的表格
  • 编译原理实验 实验二 LL(1)分析法 Python实现

    1 实验目的 通过完成预测分析法的语法分析程序 了解预测分析法和递归子程序法的区别和联系 使学生了解语法分析的功能 掌握语法分析程序设计的原理和构造方法 训练学生掌握开发应用程序的基本方法 有利于提高学生的专业素质 为培养适应社会多方面需要
  • 电子科技大学编译原理复习笔记(五):词法分析

    目录 前言 重点一览 词法分析概述 词法分析的功能 词法分析器的输出形式 词法分析器的结构 状态转换图 状态转换图的构造 词法分析器的设计 基本结构 内容 符号表 目的 组成 在词法分析中的作用 符号表的一般形式 常用的符号表结构 总结与补
  • gcc常用选项及常见的文件格式,扩展名

    gcc常用选项 编译过程 预处理 编译 汇编 链接 gcc的选项 必须分开给出 x 语言名 指出后面文件的语言 c 编译 汇编源文件 生成目标文件 S 编译不汇编 生成汇编文件 E 预处理 输出送到标准输出 o 指定输出的文件名 pipe
  • 静态类型推导

    前面说泛型的时候 提到了C 模板的实现方式是动态特性静态化 在实际情况中 这是一个提高效率的好办法 动态性的好处是灵活 开发简便 静态性的特性是效率高 编译期检查较好 因此很自然地就有一个问题 能不能各取所长 达到两全其美 应该说 在一定程
  • 编译课设 (词法分析+LR1语法分析+语法制导翻译(四元式生成))

    代码已上传至 Github 完整的 VS2019 项目已上传至百度云 提取码 lql1 目录 源语言 语义动作 中间代码定义 整体框架 声明 语句 i f if if 语句
  • 吉首大学_编译原理实验题_基于预测方法的语法分析程序的设计【通过代码】

    一 实验要求 实验二 基于预测方法的语法分析程序的设计 一 实验目的 了解预测分析器的基本构成及用自顶向下的预测法对表达式进行语法分析的方法 掌握预测语法分析程序的手工构造方法 二 实验内容 1 了解编译程序的基于预测方法的语法分析过程 2
  • (二):C++求解文法的First集和Follow集

    功能及代码结构 为实现编译器前端 需要对文法进行分析 该部分实现从文件中读入文法 方便修改 用合适的数据结构表示并求解各个非终结符号的First集和Follow集 仓库 https github com xs1317 Complier 文件
  • LLVM SSA 介绍

    最近做研究碰到了一个难题 需要对程序变量按生命期进行重命名 考虑到 SSA 中一个变量在不同的程序分支中赋值时会进行重命名 因此打算以此作为参考 看看能否采取同样的方法达到目的 由于之前看到的文档中都说 LLVM IR 是 SSA 形式的
  • The Backus-Naur Form (BNF) & The Extended Backus-Naur Form (EBNF)

    The Backus Naur Form BNF The Backus Naur Form BNF is a notation used for formal description of the syntax of programming
  • 合肥工业大学编译原理实验一词法分析

    基本思路 词法分析是对输入语句串中一个个单词符号进行分析 最后格式化输出种别码 类型 位置等信息 那么 就可以考虑一次读入一个字符将它们拼接成一个字符串 当碰到空格或者分界符 时 就把前面已读的字符串格式化输出 再输出当前分界符 然后再往后
  • 程序语言翻译器的设计与实现----算术表达式转换四元式(编译原理)

    此篇博客是将前面的内容进行整合并进一步提升 真正实现一个简单表达式语法的编译器 如果有不了解的地方请查看下面链接 词法分析 LR 1 分析 一 LR 1 分析 二 这里说的程序语言编译器是指将算术表达式部分进行翻译 暂时不包括优化以及目标语
  • 【编译原理】LR(0)分析方法(c++实现)

    基本流程 Created with Rapha l 2 2 0 输入文法 拓广文法 求项目集规范族 GO I a 转移函数 构造DF
  • arm-linux-gcc char与signed char和unsigned char

    1 三者关系 1 ANSI C 提供了3种字符类型 分别是char signed char unsigned char 而不是像short int一样只有两种 int默认就是unsigned int 2 三者都占1个字节 3 signed
  • LLVM是如何编译指令的

    本文将会通过一条指令在LLVM中的不同阶段 从源程序语言中的语义结构到成为机器二进制码来研究LLVM的工作原理 本文不会介绍LLVM是如何工作的 这需要理解LLVM的设计以及code以及各种细节 输入代码 我们从一段C代码开始探险 如下 i

随机推荐

  • display aspect ratio和遥控器上的调整

    刚刚通过实验新鲜总结出来的 科普一下 一张DVD电影碟片 其解码出来的视频画面大小总是固定的 比如NTSC的DVD 总是720x480 其实不一定 但可以姑且这么认为 显示比例为4 3 不过 值得注意的是 现代的DVD Player和电视机
  • C++模板

    模板定义 函数模板 template
  • websocket 心跳机制

    WebSocket 是一种在客户端和服务器之间创建持久连接的技术 为了保持连接的稳定性 就需要通过发送心跳消息来维持 WebSocket 连接 1 创建一个webscoket基本的使用 创建 WebSocket 对象 传入服务器地址 con
  • k8s--基础--23.7--认证-授权-准入控制--限制用户操作k8s资源的权限

    k8s 基础 23 7 认证 授权 准入控制 限制用户操作k8s资源的权限 1 生成一个证书 1 1 生成一个私钥 cd etc kubernetes pki umask 077 openssl genrsa out lucky key 2
  • 使用Mindstudio调用Modelarts进行模型训练

    使用MindStudio连接ModelArts进行模型训练 1 介绍 本文介绍如何使用MindStudio调用ModelArts资源进行模型训练 ModelArts是面向AI开发者的一站式开发平台 帮助用户快速创建和部署模型 管理全周期AI
  • 彩虹医疗器械彩超、内窥镜维修技能学习

    近几年随着医疗行业的快速发展 医疗器械的需求量不断增加 同时对医疗器械的维修和保养需求也在不断增长随着医疗技术的不断进步 新型 复杂的医疗器械不断涌现 这对维修技术提出了更高的要求 加强技术研发是必经之路 通过加强工程师培训 提高工程师工作
  • 安卓搜不到airpods_完美兼容 安卓手机连接AirPods必懂方法

    苹果的AirPods被很多人认为是最好的蓝牙耳机 除了iOS用户外 不少安卓用户也折服于其产品力 购买了它并搭配安卓机使用 不过AirPods和安卓机之间的配合 显然不如和iOS那样天衣无缝 虽然AirPods也能通过蓝牙连接安卓听歌 但不
  • 微信小程序隐私协议接入

    自2023年9月15日起 对于涉及处理用户个人信息的小程序开发者 微信要求 仅当开发者主动向平台同步用户已阅读并同意了小程序的隐私保护指引等信息处理规则后 方可调用微信提供的隐私接口 相关公告见 关于小程序隐私保护指引设置的公告 微信开放社
  • javaweb(四)——过滤器与监听器

    文章目录 过滤器Filter 基本概念 滤波器的分类 时域和频域表示 滤波器类型 1 低通滤波器 Low Pass Filter 2 高通滤波器 High Pass Filter 3 带通滤波器 Band Pass Filter 4 带阻滤
  • 如果您在搭载 Apple M1 芯片的 Mac 上重新安装 macOS 时收到个性化错误

    在重新安装时 您可能会收到一条信息 提示在准备更新期间出现了错误 如果您抹掉了搭载 Apple M1 芯片的 Mac 您可能无法通过 macOS 恢复功能重新安装 macOS 系统可能会显示信息 准备更新时出错 未能个性化软件更新 请再试一
  • mysql 安装 中途遇到的意外:it seems that the port 3306 is already in use 和 卡到最后一个页面

    由于软件工程大作业需要 需要复习数据库 重装数据库 中途遇到两个意外 it seems that the port 3306 is already in use 和 一直卡到最后一个页面 目录 首次安装 意外一 it seems that
  • RAID介绍及RAID10配置实例

    目录 一 RAID磁盘阵列介绍 二 RAID磁盘阵列详解 2 1RAID0 条带化存储 2 2RAID 1 镜像存储 2 3RAID5 2 4RAID6 2 5 RAID 1 0 先做镜像 再做条带 2 6RAID 0 1 先做条带 在做镜
  • logstash数据同步

    1 编写logstash的pipeline文件 abc conf input stdin jdbc jdbc connection string gt jdbc mysql ip 3306 db serverTimezone Asia Sh
  • 我在Gazebo中加入了IMU传感器,并用Python发布了话题和传感器数据,可是为什么数据都为0,哪里漏了或者错了

    Gazebo SDF 文件中传感器的添加如下 话题和数据发布的代码如下 使用如下指令查看话题数据 rtopic echo Imu 1 获得的IMU数据都为0 有人知道哪里不对吗 能帮助的话就太感谢了
  • CloudCompare 二次开发(8)——提取点云的重叠区域

    目录 一 概述 二 代码集成 三 结果展示 本文由CSDN点云侠原创 原文链接 爬虫网站自重 一 概述 使用CloudCompare与PCL联合编程实现两期点云数据重叠区域的获取 具体计算原理见 PCL 提取两片点云的重叠部分并保存 二 代
  • 华为OD机试真题- 荒岛逃生游戏-2023年OD统一考试(B卷)

    题目描述 一个荒岛上有若干人 岛上只有一条路通往岛屿两端的港口 大家需要逃往两端的港口才可逃生 假定每个人移动的速度一样 且只可选择向左或 向右逃生 若两个人相遇 则进行决斗 战斗力强的能够活下来 并损失掉与对方相同的战斗力 若战斗力相同
  • 【自学51单片机】5 --- 定时器介绍、数码管静态显示、逻辑运算符和逻辑电路符号

    文章目录 1 逻辑运算和逻辑电路 1 1 C语言逻辑运算符 1 2 逻辑电路符号 2 定时器学习 重点非难点 2 1 时钟周期和机器周期的介绍 2 2 定时器的介绍 2 2 1 定时器寄存器介绍 2 2 2 定时器模式工作电路逻辑图 2 3
  • github可以做文件服务器吗,局域网搭建git服务端并使用Github Desktop作为客户端

    在使用了github的客户端软件Github Desktop之后 感受到了git的便捷 研究了一下与svn的区别之后 结合目前的团队情况 决定下个项目开始使用git 整理一下 这里服务端为centos 6 5 客户端为mac 一 服务端安装
  • Mysql 架构图

    Mysql 架构图 第一层 对客户端的连接处理 安全认证 授权等 每个客户端连接都会在服务端拥有一个线程 每个连接发起的查询都会在对应的单独线程中执行 第二层 MySQL的核心服务功能层 包括查询解析 分析 查询缓存 内置函数 存储过程 触
  • 电脑怎样执行编程语言的?

    链接 https www zhihu com question 29227521 answer 154819061 来源 知乎 著作权归作者所有 商业转载请联系作者获得授权 非商业转载请注明出处 这个问题真的是很大 让我们自顶向下的解释 在