一个汇编指令如何转化为CPU上的电压变化?

54

我过去3-5年一直在使用C和CPython工作。请考虑这是我的知识基础。

如果我要对支持它的处理器使用像MOV AL, 61h这样的汇编指令,那么解释这段代码并将其作为电压信号分配的处理器内部有什么?这样一个简单的指令可能如何执行?

即使我试图思考MOV AL, 61h或者XOR EAX, EBX中包含的多种步骤,汇编甚至都感觉像高级语言。

编辑:我读了一些评论,问为什么我把这个放在嵌入式系统里,而x86系列不常见于嵌入式系统。欢迎来到我的无知境地。现在我认为,如果我对此一无所知,可能还有其他人也不了解。

对于你们所有人的答案付出的努力,让我很难选择最喜欢的答案。但我觉得有必要做出决定。朋友们,别伤心。

我经常发现,我学习计算机的越多,我就越意识到自己实际上知道的越少。谢谢你们打开我的思维,让我了解微码和晶体管逻辑!

编辑#2:感谢这个主题,我刚刚理解了为什么XOR EAX,EAXMOV EAX,0h更快。:)


3
顺便说一下,这是一个很棒的问题。令人惊讶的是,很少有开发者意识到晶体管如何影响他们的世界,或者去探究它们。你正在走向认识自己无知的道路上,这让你处于非常好的公司。 - 3Dave
真正会让你困惑的是电路制造与100年前的摄影技术并没有太大的区别。 - 3Dave
关于最后一段:如果你真的想知道异或零清零操作在各种现代Intel和AMD微架构中为什么更好(不只是代码大小方面),请参考这个答案 - Peter Cordes
12个回答

40
我最近开始阅读Charles Petzold的书籍《Code》,该书目前涵盖了您可能感兴趣的内容。但在购买/借阅之前,请先翻阅一下这本书。这是我的相对简短的回答,不是Petzold提供的……希望这符合您的好奇心。
我想你已经听说过晶体管。最初使用晶体管的方法是制作晶体管收音机。它基本上是一个放大器,将漂浮在空中的微小无线电信号送入晶体管的输入端口,晶体管会打开或关闭旁边电路的电流。然后您用更高功率的电路进行接线,因此您可以将非常小的信号放大并将其馈送到扬声器中,例如收听广播电台。(还有更多的内容,如隔离频率和保持晶体管平衡,但我希望您能理解这个概念)。
现在存在晶体管技术,我们可以将其用作开关,就像灯开关一样。收音机类似于调光灯开关,您可以将其调节到从完全开到完全关之间的任何位置。而非调光灯开关则只能完全开或完全关,在开关的中间位置有某种神奇的地方用于切换状态。数字电子学中我们使用晶体管同样的方式,将一个晶体管的输出馈入另一个晶体管的输入,第一个晶体管的输出当然不是像无线电波那么微弱,它强制第二个晶体管完全开或完全关。这就引出了TTL或晶体管-晶体管逻辑的概念。基本上,您有一个驱动高电压或者称之为1的晶体管,在其下面沉没0电压即所谓的0。然后您通过其他电子元件排列这些输入,从而可以创建AND门(如果两个输入都是1,则输出为1)、OR门(如果任一输入为1,则输出为1)和反相器、NAND门、NOR门(一个OR门加一个反相器)等。曾经有一本TTL手册,您可以购买有8个或更多引脚的芯片,内部包含一种或两种或四种某种类型门(NAND、NOR、AND等)的功能,每个门有两个输入和一个输出。现在我们不需要这些了,生产可编程逻辑或具有数百万晶体管的专用芯片更便宜。但是我们在硬件设计方面仍然想到与AND、OR和NOT门(通常更像nand和nor)。我不知道现在他们教的是什么,但概念其实是一样的。对于存储器而言,一个触发器可以被认为是两个这样的TTL(非与门)成对地连接在一起,其中一个的输出接到另一个的输入。这基本上就是我们所谓的SRAM,即静态随机存取存储器中的一个单个位。SRAM需要基本上4个晶体管。DRAM或动态随机存取存储器(你自己放入计算机内存条的那种)每比特需要1个晶体管,因此首先你可以看到为什么DRAM是你购买吉字节容量的东西。SRAM位会记住你设置它们的值,只要电源不中断。DRAM则会在你写入数据后很快开始遗忘,基本上DRAM使用第三种不同的晶体管方式,有一些电容(就像电容器一样,在这里不做赘述)它就像微小的可充电电池,一旦你充电并拔掉充电器,它就开始放电。把每个玻璃杯都想象成一行货架上的杯子,每个玻璃杯都有一个小孔,这些就是DRAM位,你希望其中的一些是1,所以你需要一个助手为你想要成为1的杯子倒满水,这个助手必须不断地注满水壶并沿着一行行走,保持“1”位玻璃杯中有足够的水,让“0”位玻璃杯保持空着。因此,任何时候,你想查看数据,只需观察水位即可判断是1还是0。所以即使在通电状态下,如果助手无法将玻璃杯装满足以区分1和0,它们最终也会全部变成0并且放电掉。这就是为了获得更多比特每片芯片而进行的权衡。所以简而言之,除处理器外,我们使用DRAM作为我们的大容量存储器,而有一个逻辑辅助来确保保持“1”和“0”的状态。但在芯片内部,例如AX寄存器和DS寄存器,使用触发器或SRAM来保持数据。对于像AX寄存器中的每个比特,可能有数百或数千个以上的比特用于将数据输入和输出到该AX寄存器。

你知道处理器以某个时钟速度运行,现在大约是2 GHz或每秒20亿个时钟。想象一下时钟,由晶体产生,另一个主题,但逻辑电路将该时钟视为电压,在这个时钟速率2GHz或其他速率(Gameboy Advance是17MHz,旧iPod大约是75MHz,最初的IBM PC是4.77MHz)上高低跳动。

所以用作开关的晶体管允许我们将电压转换为硬件工程师和软件工程师熟悉的1和0,并且能够提供AND、OR和NOT逻辑功能。而我们则有了这些神奇的晶体,可以让我们获得精确的电压振荡。

现在,我们可以这样做:如果时钟是1,并且我的状态变量表明我处于获取指令状态,则需要切换一些门电路,使我想要的指令的地址(即程序计数器中的地址)传输到内存总线上,以便内存逻辑可以提供我MOV AL,61h的指令。您可以在x86手册中查找此信息,并发现某些操作码位表示这是一个mov操作,目标是EAX寄存器的低8位,mov的源是立即值,这意味着该值在此指令后的内存位置中。因此,我们需要将那条指令/操作码保存在某个地方,并在下一个时钟周期中获取下一个内存位置。因此,现在我们已经保存了mov al,立即数,并且我们从内存中读取了61h的值,我们可以切换一些晶体管逻辑,以便该61h的第0位存储在al的第0个触发器中,第1位存储在第1位触发器中,以此类推。
你们可能会问,所有这些是如何实现的?想象一下一个Python函数执行某个数学公式。您从程序顶部开始,使用作为变量输入的一些公式,通过程序的各个步骤,可能会加上某个常量或从库中调用平方根函数等等。最后返回答案。硬件逻辑的实现方式也是如此,今天使用的编程语言之一看起来很像C。主要区别在于,您的硬件函数可能有数百或数千个输入,输出是单个位。在每个时钟周期,AL寄存器的第0位正在使用一个巨大的算法进行计算,取决于您想要查看的距离有多远。想想你为数学运算调用的平方根函数,该函数本身就是其中之一,一些输入会产生一个输出,它可能会调用其他函数,例如乘法或除法。因此,您可能有一个位可以认为是bit 0之前的最后一步,其功能是:如果时钟是1,则AL [0] = AL_next [0];否则,AL [0] = AL [0];但是还有一个更高的功能,其中包含从其他输入计算出的下一个al位,以及更高的功能和更高的功能,这些都是由编译器创建的方式与您的三行Python代码可以转换为数百或数千行汇编语言相同。几行HDL可以变成数百或数千个甚至更多的晶体管。硬件专业人士通常不会查看特定位的最低级公式,以查找所有可能的输入以及计算任何可能的AND、OR和NOT所需的所有可能的逻辑门。这与您可能不会检查由程序生成的汇编代码类似,但是如果您愿意,可以这样做。关于微码,大多数处理器不使用微码。例如,x86指令集是其时代的一种良好指令集,但在表面上难以跟上现代时代的步伐。其他指令集不需要微码,直接使用我上面描述的逻辑。您可以将微码视为使用不同指令集/汇编语言的不同处理器,该指令集模拟您在表面上看到的指令集。微码层专门为工作而设计,您可能认为只有AX、BX、CX、DX四个寄存器,但实际上还有更多。自然而然地,一个汇编程序可以在一个核心或多个核心上通过多条执行路径执行。就像闹钟或洗衣机中的处理器一样,微码程序简单、小巧、调试良好,并烧录在硬件中,希望永远不需要固件更新。至少理想情况下是这样。但是,就像iPod或手机一样,有时您确实希望进行修复或其他操作,并且有一种升级处理器的方法(bios或其他软件在引导时加载补丁)。假设您打开电视遥控器或计算器的电池盒,您可能会看到一排金属触点的孔,可能是三个或五个或更多。对于某些遥控器和计算器,如果您真的想要,可以重新编程它并更新固件。通常不这样做,理想情况下,该遥控器足够完美以超过电视机组。微码提供了在市场上推出非常复杂的产品(数百万、数亿个晶体管)并在未来修复大型且可修复的错误的能力。想象一下,您的团队在18个月内编写了200万行Python程序,并且必须交付该程序或公司将无法与竞争对手的产品竞争。与此类似的事情是,在现场只能更新该代码的一小部分其余部分必须保持不变。对于闹钟或烤面包机,如果出现错误或需要帮助,您只需将其扔掉并获得另一个即可。
如果您翻阅维基百科或仅仅搜索一些东西,您可以查看像6502、z80、8080和其他处理器的指令集和机器语言。可能有8个寄存器和250个指令,您可以从晶体管数量上感受到,相比于计算每个时钟周期中触发器中的每个位所需的逻辑门序列,这250个汇编指令仍然是一种非常高级的语言。除了微码处理器,这种低级逻辑无法以任何方式重新编程,您必须使用软件修复硬件错误(对于已交付或将要交付而不是报废的硬件)。看看那本Petzold书,他在解释这些方面做得非常出色,比我写的任何东西都要好。

2
不错的回答。虽然我不会说它是“相对较短”的;-)。 - sleske
5
相对于可能需要讨论此主题的篇幅,比如我的回答,引用了三本教科书和一份实验室手册,这篇回答相对较短。与那些相比,这篇回答算是短的。 - Brian Campbell

20
编辑:这里有一个使用Python/Javascript模拟了6502 CPU晶体管的示例:http://visual6502.org您可以将代码输入,以查看它如何实现功能。 编辑:杰作《新机器的灵魂》(The Soul of a New Machine): 特蕾西·基德(Tracy Kidder)提供了一个优秀的10 000米高度视角。

我曾经很难想象这件事情,直到我做了微程序设计,才明白了(抽象的)。这是一个复杂的主题,但是从非常高层次的角度来看。

本质上,可以这样考虑。

CPU指令本质上是存储在电路中的一组电荷,这些电荷组成了内存。有电路使得这些电荷从内存传输到CPU的内部。一旦进入CPU,这些电荷被设置为CPU电路的输入。这本质上是一个数学函数,将导致更多的电流输出,并且该周期继续。

现代CPU要复杂得多,并包含许多层微码,但原理仍然相同。内存是一组电荷。移动电荷的电路和执行功能的其他电路会导致产生其他电荷(输出),以供馈送到内存或其他电路以执行其他功能。

要了解内存的工作原理,您需要了解逻辑门及其如何由多个晶体管创建。这导致了硬件和软件在数学意义上执行函数的等效性发现。


14

这是一个需要在StackOverflow上解释的问题。

要从最基本的电子元件一直学习到基本的机器代码,请阅读The Art of Electronics, by Horowitz and Hill。要了解更多关于计算机体系结构的知识,请阅读Computer Organization and Design by Patterson and Hennessey。如果您想进入更高级的主题,请阅读Computer Architecture: A Quantitative Approach, by Hennessey and Patterson

顺便说一下,The Art of Electronics还有一个配套的lab manual。如果您有时间和资源可用,我强烈推荐做实验;我曾经参加过Tom Hayes教授的课程,在其中我们建造了各种模拟和数字电路,最终使用68k芯片、一些RAM、一些PLD和一些离散元件构建了一台计算机。您将直接使用十六进制键盘将机器代码输入RAM;这是一种非常棒的方式,可以在计算机的最低层次上获得实践经验。


电子艺术非常棒。 - Anycorn
很遗憾它最近没有更新。它有点过时了。 :-( 除此之外,这是一个非常好的资源! - Brian Knoblauch
我也建议阅读SICP的后面章节(http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-30.html#%_chap_5) - SingleNegationElimination
@TokenMacGuy我会建议任何对编程感兴趣的人都阅读完SICP,但是针对这个特定问题,Horowitz和Hill更适合于低级别、实践性的经验,而Patterson和Hennessey则更适合描述现实世界中相对现代的计算机架构。但是,我始终会推荐阅读SICP。 - Brian Campbell
讨论寄存器机的章节对我有帮助,让我更好地理解逻辑门如何组合形成功能块以及这些块如何组合执行指令。 - SingleNegationElimination
哇,那个课程听起来太棒了。 - Paul Nathan

13

在没有整本书的情况下详细解释整个系统是不可能的,但以下是一个非常简单电脑的高级概述:

  • 在最底层是物理和材料(例如,由掺杂硅制成的晶体管)。
  • 使用物理和材料,您可以得出NAND逻辑门
  • 使用NAND门,您可以派生所有其他基本逻辑门(AND、OR、XOR、NOT等),或者为了效率直接从晶体管构建它们,包括带有超过2个输入的版本。
  • 使用基本逻辑门,您可以推导出更复杂的电路,例如加法器多路复用器等。
  • 同样使用基本逻辑门,您可以推导出有状态数字电路元素,例如触发器时钟等。
  • 使用更复杂的状态电路,您可以推导出更高级别的部件,例如计数器内存寄存器算术逻辑单元等。
  • 现在您只需将高级部件粘合在一起,使其满足以下要求:
    • 从内存中输出一个值
    • 通过使用多路选择器等技术,将该值解释为指令并将其分派到适当的位置(例如ALU或内存)。 (基本指令类型包括从内存读取到寄存器,从寄存器写入到内存,对寄存器执行操作和根据条件跳转到指令。)
    • 接下来的指令也是如此。

要理解汇编指令如何引起电压变化,您只需要了解每个级别是如何由下一级表示的。例如,ADD 指令将导致两个寄存器的值传播到 ALU 中,它具有计算所有逻辑操作的电路。然后在另一侧的多路选择器中,接收来自指令的 ADD 信号,选择所需的结果,并传播回其中一个寄存器。


通常情况下,您不会仅从NAND构建所有电路;为了提高效率,您使用一些不完全遵循的组合。而任何CPU中最重要的部分是您省略的部分:锁存器,通常由时钟信号驱动。(它也是CPU寄存器工作的核心。) - Donal Fellows
2
@Donal 这是针对一个简单的计算机,而不是实用的计算机。我在其他层面也削减了很多信息。此外,我说的是触发器而不是锁存器。 - Craig Gidney
1
@ Strilanc,你忘了在“计数器、存储器、寄存器等”之后提到FSM了。那是墙后面的“大脑”!!!当然,所有硬件系统都不过是“数据通路”(计数器、存储器、寄存器、多路复用器等)和“FSM”(组合逻辑+触发器)。 - fante

8

7

6

简单来说,机器码指令是以一系列位的形式存储在处理器中的。如果你在处理器数据手册中查找MOV,你会看到它有一个十六进制值,比如(例如)0xA5,这是特定于MOV指令的。(不同类型的MOV指令具有不同的值,但我们暂且忽略这一点。)

0xA5 hex == 10100101 binary.

(这不是X86上MOV的真实操作码值 - 我只是为了说明目的而选择了一个值。)

在处理器内部,这被存储在一个“寄存器”中,它实际上是一个存储电压的触发器或锁存器数组:

+5 0 +5 0 0 +5 0 +5

这些电压中的每一个都馈入到一个门或一组门的输入中。

在下一个时钟边沿,那些门根据来自寄存器的输入电压更新其输出。

这些门的输出馈入到另一级门中,或者返回到它们自己。该级别馈入到下一个级别,依此类推。

最终,一条远离的门输出将连接回另一个锁存器/触发器(内部存储器),或者连接到处理器上的一个输出引脚。

Register->(clock)->Gate A->(clock)->Gate B->pin
                                          ->latch

(忽略不同门类型和更高级别结构的反馈)

这些操作在一定程度上并行发生,由核心架构定义。 "更快"的处理器-比如说,2.0GHz与1.0GHz-表现更好的原因之一是更快的时钟速度(GHz值)导致从一个门集合到下一个门集合的传播更快。

重要的是要理解,在非常高的层面上,处理器所做的就是改变引脚电压。我们在使用PC等设备时看到的所有复杂性都是由门内部模式和连接到处理器的外部设备/外围设备的模式派生出来的,例如其他CPU、RAM等。处理器的魔力在于它的引脚以何种模式和序列改变电压,以及内部反馈允许CPU在某一时刻的状态对其下一时刻的状态做出贡献。(在汇编语言中,此状态由标志、指令指针/计数器、寄存器值等表示。)

实际上,每个操作码(机器码指令)的位都与处理器的内部结构物理绑定(尽管必要时可能会用内部查找表/指令映射来抽象化)。

希望这有所帮助。我还接受过良好的EE教育和丰富的嵌入式开发经验,因此这些抽象对我来说是有意义的,但对新手可能没有太大用处。


很好的回答。我想要知道的是,MOV的二进制数字是如何转换为电压的。我意识到这需要硬件(例如硬盘磁头)来“看到”磁盘上的1并将寄存器的一部分充电至2V,然后“看到”0并将另一个寄存器充电至0V,以此类推... - Nav
1
顺便提一下,A5x86指令movsw/d/q的操作码。如果您想忽略不是实际x86 MOV操作码的警告,则A3 disp32是操作码,用于将EAX存储到32位或64位绝对地址(没有ModRM字节来正常编码操作数)的特殊情况。只有1个比您的示例少一个位,但是这是一个奇怪的情况。8Bmov reg32,r / m32的操作码,用于复制或加载4字节寄存器。mov-imm到reg通常使用B8 + rd id形式,其中reg num =低3个操作码位。 - Peter Cordes
@Nav,“MOV”并不是被转换成电压的。它已经以电压模式的形式存储。你认为它会从哪里进行转换呢?内存中的一个字节是一组触发器,其状态是电压。 - 3Dave
@3Dave:我想我指的是硬盘上的磁性数据(磁粒子排列)如何转换为电压。 - Nav

1
书籍的初稿 "Microprocessor Design" 目前已经在Wikibooks上在线
我希望有一天它能够包含一个对于那个问题的优秀答案。同时,也许您仍然可以从当前对于那个问题的初稿中学到一些东西,并且帮助我们改进或者至少指出我们忘记解释的内容以及说明不清的地方。

1
数字电路中最基本的元素应该是逻辑门。 逻辑门可用于构建逻辑电路,以执行布尔运算、解码器或时序电路(例如Flip-Flops)。 Flip-Flop可以被认为是1位存储器。 它是更复杂的时序电路的基础,例如计数器或寄存器(位数组)。 微处理器只不过是一堆时序电路和寄存器。对于微处理器的“指令”不过是一些位模式,这些位模式被顺序地推送到某些寄存器中,以触发特定的序列来对“数据”执行计算。 数据表示为位数组...现在我们进入了更高级别的领域。

1

这里有一个非常糟糕的总结 :-)

MOV AL,61h是一种人类可读的代码形式,输入汇编器。汇编器生成等效的十六进制代码,这基本上是处理器理解的字节序列,并且是您将存储在内存中的内容。在嵌入式系统环境中,链接器脚本为您提供了对在内存中放置这些字节(程序/数据等单独区域)的精细控制。

处理器实质上包含一个有限状态机(微码),使用触发器实现。该机器从内存中读取(获取周期)“MOV”的十六进制代码,找出(解码周期)它需要一个操作数,在这种情况下是61h,再次从内存中获取它,并执行它(即将61复制到累加器寄存器中。“读取”、“获取”、“执行”等都意味着使用数字电路(如加法器、减法器、多路复用器等)将字节移位/添加到移位寄存器中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接