在CPU级别如何执行程序?


14

我知道这是一个非常普遍的问题。但是我的想法却不同。我将在这里尝试阐明。

据我所知,由于ALU及其晶体管(如果我们在硬件级别上),CPU执行的每条指令都是机器语言,并且所有CPU都可以执行一些算术运算。

但是,此类型比理解起来容易。因此,如果所有CPU都在执行加,减等操作,那么用这些算术运算如何执行一个程序,比如说打印Hello World的JAVA程序?

我的意思是该程序如何转换为CPU的附加功能?

附言:如果此问题不适用于本网站,则表示歉意。

- - -第二部分 - - -

好的。感谢所有人以如此迅速的热情回答。我认为最好修改一下我的问题,而不是去评论所有答案并再次询问。

就是这样

首先,所有人都专门回答了Hello World的示例。这是我的错。我应该保持这种通用。您好,世界示例带来了输出设备的问题,以及它的处理方式不仅限于CPU,而这在您的答案中是正确的。

你们中的许多人也注意到我,CPU的作用不只是加法。我同意这一点。我只是没有写,而是一直假设。据我了解,这是一个过程:

  1. 从内存中读取指令(使用数据和地址总线以及程序计数器的内容)

    1. 将数据存储在CPU内部的寄存器中
    2. 现在,ALU当然会在解码指令后进行算术运算,或者如果它是if指令,则进行跳转
    3. 然后在需要时与其他资源进行通信,例如与输出设备等等。目前,除此以外的过程都是微不足道的。

因此,在步骤3中,CPU对一条指令进行解码并决定执行算术运算(此处我们假设没有其他操作要执行,例如跳转当前指令。由于算术运算大部分已完成。因此,我们坚持这样做)这就是我的可视化结束的地方。我的程序中的指令如何仅仅是CPU的算术运算。它执行该算术运算,并且该指令达到其目的。

希望这次我能说清楚。

PS:我在这里假设一个很大的假设,即ALU不仅限于我们在程序中执行的实际算术运算,而是执行所有指令(现在为二进制形式),方法是将它们相加或相减,以得出它们的结果。屈服。如果我在这里错了,那么下面的答案可以正确回答我的问题。


我了解编译器会将程序转换为机器语言。我只是无法将程序可视化为算术运算。虽然如果程序本身是关于将两个数字相加的,那么它是可以理解的,但是否则不是。:)
user2827893 2015年

1
也许您应该开始查看CPU的实际指令集,例如非常简单的CPU,例如MC6502,Z80 ...,然后看看其中有内存访问指令,数据处理指令,分支...然后您可以猜测它们的状态。结合起来可以实现任何算法。
TEMLIB

3
CPU绝对可以做更多的事情。至关重要的是要提到CPU可以进行比较和跳转。
Theodoros Chatzigiannakis 2015年

1
您(似乎)仍然坚决拒绝看到IF(做出决定)和MOVE(读取和存储数据),“编程”是99%IF和MOVE。算术可以忽略。您的第一个示例(Hello world)根本没有算术运算。
edc65

1
1.我想如果您因自己的新困惑而提出一个新问题,而不是编辑此问题以更改您要问的内容,那么您更有可能获得良好的答案。您对原始问题有很好的答案,而原始问题似乎可以独立存在,那么为什么不删除编辑并提出新问题呢?2.也就是说,我无法理解新部分。您对新零件到底有什么疑问?当你说“这是我的可视化目的”,你的意思是你明白3步,或你不明白第3步?如果这样做,您不了解什么?
DW

Answers:


7

您可以尝试采用简单的程序并将其编译为本地机器代码。(Java通常会编译为JVM代码,但是Andrew Tennenbaum有一本书,他在书中描述了如何设计一个本机运行的CPU,这样就可以了。)例如,在GCC上,给编译器一个-S开关。

这将告诉您通过调用操作系统可以实现任何棘手的操作,例如I / O。虽然您可以将源代码下载到Linux内核并对其进行相同操作,但实际上是:一切都在操纵计算机内存的状态,例如正在运行的进程的列表,或者通过使用来与硬件对话控制它的特殊内存地址或使用x86 inout上的特殊CPU指令。通常,尽管如此,只有称为设备驱动程序的特殊程序才会与特定硬件对话,而OS会将使用硬件的请求发送给正确的驱动程序。

具体来说,如果您打印“你好,世界!” 您的编译器会将其转换为一组指令,该指令将字符串加载到特定位置(例如,将字符串在内存中的地址加载到%rdi寄存器中),并使用该call指令调用库函数。该库函数可能会使用循环找到字符串的长度,然后调用系统调用write()将该字节数从字符串写入文件描述符号1,这是标准输出。此时,操作系统将查找该进程的文件编号1是什么,并确定对其进行写操作的含义。如果在屏幕上打印对标准输出的写入,将执行一些过程,例如将字节复制到缓冲区,然后由终端程序读取该缓冲区,该程序告诉窗口系统将哪些字母放在哪种字体中。窗口系统准确地决定了外观,并告诉设备驱动程序通过更改视频内存将像素放置在屏幕上。


谢谢@Lorehead。这个解释在Hello World示例中看起来不错。
user2827893 2015年

5

如您所知,您的CPU本身很笨。但是周围有一个硬件芯片的缩影。您有一条指令可以将CPU的一行设置为高电平,并将其连接到另一块芯片。那个硬件芯片监视着这条线,并说:“嘿,如果这条线很高,那我就用其他一些线做些什么。”

为了使此操作更容易,将这些行组合在一起。有些用来寻址设备,有些用来传输这些地址的数据,而有些只是“ Dude,我的芯片上发生了重要的事情”。

最后,您的CPU仅告诉其他一些芯片,请修改显示器的信号,使其看起来像“ Hello World”。

Google绘制一个7段显示器的图形。它有电线,如果您对其施加电压,则该电线将点亮。如果现在将CPU的一条输出线与7段显示器的一条线连接起来,则该显示器点亮。导致LED点亮的不是CPU,它只是向线路施加电压,但是其他一些硬件也可能因此而做得很漂亮。

如果您的CPU现在将H的所有行都设置为高,则7段将显示H,尽管H不是CPU要加减的数字。

现在,如果所有层都同意使7段显示H(将5特定行设置为高)是必需的,则Java编译器可以使代码使其显示H。这当然很不方便-因此各层开始抽象。最低层开始于:“是的,有26个字母,让我们为每个字母分配数字-我们如何给字母'H'赋予数字'72'?然后您可以告诉我“显示字母72”,而不是“将线设置为309高,将线设置为310高,将线设置为498高,将线设置为549高,将线3设置为高”。这样,每一层都开始提取信息,如何实现某些结果,因此您不需要关心他们。

因此,是的,它总结了CPU可以实际处理的数字或位的巨大映射,即链中每个人都同意的含义。


3

在大学里,作为CS学位课程的一部分,我研究了定义CPU 的寄存器传输语言的扩展示例。受到启发,我采取了不同的看法,并编写了一个接受这种表示法作为定义的模拟器,并在《嵌入式系统编程》(1989年3月号)中发布了该模拟器,以回答您所问的相同类型的问题,使人们能够建立他们对此类事物的直觉理解。

在课堂上,我们继续将电阻传输符号提炼到寄存器中的实际逻辑门中!它自己编写:查看所有以“ A”作为目标的地方,并用代码A =(case1)或(case2)...并将其表示为乘积和或乘积归一化形式。

仅在课程结束时,我才知道这是一个真正的CPU:如果我没记错的话,就是PDP-8。

今天,您可以将门控图馈入可编程逻辑阵列芯片。

这就是要点:设置一个寄存器的结果是AND和OR门的结果引回到其他寄存器。要包括的值之一是操作码值。

想象一下:A:=(opcode == 17&X + Y)|(opcode == 18&X + Z)| ...

现代的CPU更为复杂,具有管线和总线,但是像单个ALU这样的单个子单元就可以这样工作。


2

您在这里考虑使用CPU,但是运行“ Hello World”时还涉及另一个组件:显示器!

对于CPU,存储器中的值只是一个表示为给定位数(0和1)的数字。

它如何在屏幕上变成字母是另一回事:显示器还具有存储功能。该内存(图形内存)映射到屏幕上的“像素”。每个像素都有一个值编码:如果它是非常基本的单色显示,则该值仅是强度,对于彩色显示,该值是可以通过多种不同方式编码的红绿色和蓝(RGB)的组合。

因此,当CPU将给定值“写入”到显示存储器时,像素点亮。要实际写字母,需要点亮许多像素。通常,计算机将在其操作系统中定义一个字符集(实际上是几个字符集)。(对“字体”本身进行抽象,以映射到每个字母在屏幕上的外观的定义)

因此,在编译代码时,它包括来自OS库的各种东西,包括这些字体/字符集等。这些东西使CPU知道在图形存储器中的什么地方写什么。(这很复杂,但这是一般的想法:通过导入的库,编译器包含的代码比单独的“ hello world”代码中包含的代码要多得多)

最后,您可能会怀疑发生了很多事情,但是您不必编写所有代码。


1

这是从理论计算机科学领域来解决您问题的一种正式方法。

基本上,我们可以定义CPU和图灵机的计算模型之间的映射。有理论证据表明,所有可想象的图灵机程序(以及因此可在CPU上执行的所有可想象程序)的集合都是可数的。这意味着我们可以使用唯一的自然数来标识每个程序,包括将自然数扩展到图灵机的程序

众所周知,CPU几乎所做的一切都是以二进制形式对自然数进行计算,因此可以推断出CPU可以执行所有可以想象的程序。

注意:这过于简化,但我认为这提供了很好的直觉。


1

可能有帮助的是将您的思维方式从“做算术”转移出去。如果您真的想弄清楚计算机在做什么,以打印“ Hello World”,则最好将其降低一级。计算机的“状态”可以描述为一组位,这些位由打开或关闭的晶体管开关(或已充电或未充电的电容器)存储。计算机根据规则操作这些位。允许计算机操作这些位的方式以晶体管的形式写入CPU,该晶体管负责将位从0更改为1或从1更改为0。

当ALU“执行算术”时,其真正含义是它以与我们的算术规则一致的方式改变了计算机的状态。它所做的只是更改了一些位。这就是软件背后的含义,它解释了为什么我们应该将其视为加法或减法。CPU不“知道”它在做什么。它只是因州而异,仅此而已(至少在天网接管之前)。

以这种方式考虑时,更复杂的指令(如“跳转”指令)也没有什么不同。它所做的只是更改一些位。在这种情况下,碰巧会改变我们知道的意味着要执行的下一条指令的位置的位。CPU不“知道”这一点,但是我们知道。因此,我们使用了将这些位更改为在代码中到处“跳转”的指令。

IO确实也没有什么不同,只是在改变位。唯一的次要区别是这些位连接到晶体管,最终导致屏幕上的字符点亮。如果我可以追溯到几十年来“ Hello World”实际上很简单的时候,那儿会有一个存储空间,如果您向其中写入与“ Hello World”的ASCII字符相对应的位,这些字符将直接呈现到屏幕。如今,它有点复杂了,因为我们有一些显卡和操作系统与之混为一谈,但是基本思想是相同的。您有一组打开或关闭的晶体管,这些晶体管与电路相连以在屏幕上显示像素。我们设置了正确的,看起来像“ Hello World”出现在屏幕上。

混乱仅仅是语法与语义的问题。ALU中“半添加”或“完全添加”的行为是语法。它定义了当您放入位时将输出哪些位。它的语义是加法能力的概念。您和我都知道ALU可以“做加法”,但是要真正了解其下发生了什么,您必须记住ALU仅操纵语法的位和字节。


0

CPU的工作方式如下:

  • 获取当前指令,增加“当前指令”指针。

  • 对其进行解码(例如,找出该指令告诉CPU做什么)

  • 执行它(执行指令说的话)-如果指令类似于“跳转”,则可以修改当前指令指针。

  • 永远重复

现代的CPU更为复杂,它们会大量重叠甚至预测该过程的某些部分(例如,当10条其他指令正在解码时开始执行,而CPU则在“当前指令”指针之前进行取指以保持“流水线”已满),但是基本过程实际上是相同的。

指令类型很多,其中一个示例是:

  • “移动”指令。它们可以将X复制到另一个X,如果CPU支持,则X是内存(RAM),寄存器或I / O空间中的地址。

  • 堆栈操作指令,包括弹出寄存器,堆栈上的推入寄存器等。这些是使用和更新“堆栈指针”寄存器的“移动”指令的特例。

  • 在两个寄存器之间或在存储器和一个寄存器之间执行数学运算的指令。这些指令会自动影响标志寄存器。一个这样的标志是“零”标志,如果结果为零则置位,另一个是“负”标志,如果结果的最高有效位被置位则置位。可能还取决于CPU。

  • 数学运算的一种特殊情况是比较指令,它与减法相同,但不保留结果。标志仍然受到影响。

  • 如果设置了特定标志,则有分支指令跳转到存储器地址。还记得上面提到的“零”标志吗?它也作为“如果相等”标志的两倍,因此您会看到BEQ许多CPU上的指令,如果设置了“零”标志,它们实际上会分支。

  • 执行逻辑运算(AND,OR,NOT),移位位和测试位的指令。它们可能会影响标志,例如数学指令,具体取决于CPU。

  • 无条件跳转的指令。

  • 将返回地址跳转并保存在堆栈中的指令(“调用”),以及将地址弹出堆栈的其他指令(“返回”)。

  • 诸如停止CPU,识别CPU或调用中断处理程序之类的特殊指令。

  • “无操作”-几乎所有CPU都有一条“无操作”指令,该指令仅消耗周期并继续运行。

这实际上只是一个例子,有些CPU的指令类型较少,而CPU的指令类型较多。

关键是要说明除了CPU中的数学指令外,还有许多类型的指令。高级语言中的所有内容均按上述操作类型细分,只有其中一些是数学或ALU类型的指令。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.