爱技术 & 爱分享
爱蛋蛋 & 爱生活

处理器(IA-32)

虽然不同的处理器系列结合了不同的指令集合和功能,但是大多数处理器都使用了相同的核心组件集合(处理器,控制总线,数据总线,地址总线)。
CPU Unit

处理器包含控制计算机操作的硬件和指令码。通过使用3个单独的总线:控制总线、地址总线和数据总线,处理器被连接到计算机的其他元素(内存存储单元、输入设备和输出设备)。

控制总线用于保持处理器和各个系统元素之间功能的同步。数据总线用于在处理器和外部系统元素之间传送数据。这种操作的一个例子是从内存位置读取数据。处理器把要读取的内存地址放到地址总线上,然后内存存储单元作出响应,把这个内存位置存储的值放到数据总线上以便处理器进行访问。

处理器本身由很多组件构成。在处理器处理数据的过程中,每个组件都有其作用。汇编语言程序具有访问和控制所有这些组件的能力,所以了解这些组件是什么是很重要的。处理器的主要组件如下:

  • 控制单元
  • 执行单元
  • 寄存器
  • 标志

CPU Component

控制单元

处理器的中心是控制单元。控制单元的主要作用是控制处理器内在任何时候进行什么操作。 在处理器运行时,必须从内存获得指令并且加载它们以便处理器进行处理。控制单元的工作是实现4个基本功能:

  1. 从内存获得指令。
  2. 对指令进行解码以便进行操作。
  3. 从内存获得所需的数据。
  4. 如果必要,就存储结果。

指令计数器从内存获得下一条指令码并且使之准备好进行处理。指令解码器用于把获得的指令码解码为微操作。微操作是控制处理器芯片之内的特定信号来执行指令码的功能的代码。微操作准备好之后,控制单元把它传递给执行单元进行处理,并且获得所有要存储在正确位置的结果。

控制单元是被研究得最多的处理器部分。在微处理器科技中很多发展都属于控制单元部分。在加快控制单元的操作方面最有帮助的改进之一是控制单元获得和处理指令的方式。

自奔腾Ⅳ引入了NetBurst改进特性(此处的介绍有些过时):

  • 指令预取和解码
  • 分支预测
  • 乱序执行
  • 退役

CPU Optimize

指令预取和解码管线

当执行单元需要指令和数据时,因为内存获取数据的时间比处理它们的时间要长很多,所以就发生了待办工作积压的情况,因此处 理器经常等待从内存获取指令和数据。为了解决这个问题,就建立了预取(prefetch)的概念。

预取涉及到试图在执行单元实际需要指令和/或数据之前获得(取)他们。为了实现预取,处理器芯片本身需要一个专门的区域(处理器可以很容易且高速的访问这一区域),这是使用管线操作(pipeline)实现的。

管线操作涉及到在处理器芯片中创建内存缓存,在处理指令和数据元素之前,可以从缓存获取和存储它们。当执行单元为下一条指令作好准备时,这条指令已经在缓存中了并且可以被快速地处理。目前主流的处理器都有3级缓存,分别为L1,L2,L3。

L1缓分成两种,一种是指令缓存,一种是数据缓存。L2缓存和L3缓存不分指令和数据。

L1和L2缓存在每一个CPU核中,L3则是所有CPU核心共享的内存。

L1、L2、L3的越离CPU近就越小,速度也越快,越离CPU远,速度也越慢。再往后面就是内存,内存的后面就是硬盘。我们来看一些他们的速度(非实测数据,引用自皓叔文章):

  • L1 的存取速度:4 个CPU时钟周期
  • L2 的存取速度: 11 个CPU时钟周期
  • L3 的存取速度:39 个CPU时钟周期
  • RAM内存的存取速度:107 个CPU时钟周期

更多CPU Cache资料,可以看皓叔这篇文章,非常清晰易懂— 与程序员相关的CPU缓存知识

写下这篇文章时,目前最强的CPU是AMD的线程撕裂者3990X,其L1 : 4MB(指令跟数据分别2M) L2 : 32MB L3 : 256MB

第一个缓存级别(称为L1)在它认为处理器将需要指令码和数据时,会试图从内存预取它们。随着指令指针在内存中移动, 预取算法确定应该读取哪些指令码并且把它们放到缓存中。按照类似的方式,如果要处理内存中的数据,预取算法就会试图确定接下来可能要访问什么数据元素,也从内存读取它们并且把它们放在缓存中。

当然,对指令和数据进行缓存的个缺陷在于不能保证程序将按照连续的顺序执行指令。如果程序采用某一逻辑分支,使指令指针转移到内存中完全不同的位置,那么整个缓存就没有用处了,必须清空整个缓存并且使用来自新位置的指令重新填充缓存。

为了缓解这问题,就创建了第二个缓存级别。第二个缓存级别(称为L2)也可以保存指令码和数据元素,它独立于第一个缓存级别,并且容量会大于第一个缓存级别。当程序逻辑跳转到内存中完全不同的区域去执行指令时,第二个级别的缓存仍然可以保存来自前面指令位置的指令。如果程序逻辑跳转回这个区域,那么这些指令仍然被缓存保存着,并且处理这些指令的速度几乎和存储在第一级缓存中的指令一样迅速。

虽然汇编语言程序不能访问指令和数据缓存,但是了解这些元素如何工作也是有好处的。通过尽最减少程序中的分支,可以帮助提高程序中指令码的执行速度。
CPU Flow

分支预测单元

虽然实现多个级别的缓存是帮助加快程序逻辑的执行速度的一个途径,但是仍然没有解决”跳转的”程序的问题。如果程序采用很多不同的逻辑分支,那么使不同级别的缓存跟上分支的跳转也许是不可能的事情,结果就造成了更多在最后时刻对指令码和数据元素进行内存访问的情况。

为了解决这个问题,引入了分支预测(branchprediction),最早出现在IA-32(Intel Architecture,32-bit)平台处理器中。分支预测使用专门的算法试图预测接下来在程序分支中需要哪些指令码。

引入专门的统计学算法和分析,来确定指令码中最可能的执行路径。这条路径上的指令码被预取并且加载到缓存中。

  • 深度分支预测
  • 动态数据流分析
  • 推理性执行

深度分支预测使处理器能够试图越过程序中的多个分支对指令进行解码。这里同样实现统计学算法来预测程序在分支中最可能的执行路径。虽然这种技术有所帮助,但是它并不是完全不出错误的。

动态数据流分析对处理器中的数据流进行统计学实时分析。被预测为程序流程必须经过的、但是指令指针还没有达到的指令被传递给乱序执行引擎(下面讲解)。另外,在处理器等待与另一条指令相关的数据时,任何可以执行的指令都会被处理。

推理性执行使处理器能够确定指令码分支中不是立即就需要执行的哪些“远距离”指令码以后有可能需要执行,并且试图处理这些指令,这里同样使用乱序执行引擎。

乱序执行引擎

乱序执行引擎最早出现在奔腾4处理器中。

Shuffle Engine

这里是为执行单元准备要处理的指令的地方。它包含几个缓冲区用于改变管线中指令的顺序,以便提高控制单元的性能。

从预取和解码管线获取的指令被分析和重新排序,使它们能够尽快地被执行。通过分析大量指令,乱序执行引擎能够找到那些在程序的其他部分需要它们之前随时可以执行(并且保存它们的结果)的独立指令。在任何一个时刻,奔腾4处理器的乱序执行引擎中可以有最多126条指令。

乱序执行引擎内有3个部分:

  • 分配器
  • 寄存器重命名
  • 微操作调度器

分配器是乱序执行引擎的交通警察。它的工作是确保缓冲区空间被适当地分配给乱序执行引擎正在处理的每条指令。如果所需的资源不可用,分配器会停止对指令的处理并且把资源分配给能够完成处理的另一条指令。

寄存器重命名部分分配逻辑寄存器去处理需要寄存器访问的指令。寄存器重命名部分不使用IA-32处理器上可用的8个通用寄存器(以后在“寄存器”小节中讲解),而是包含128个逻辑寄存器。它把指令提出的寄存器请求映射到某个逻辑寄存器上,以便允许多条指令对相同寄存 器同时进行访问。寄存器映射是使用寄存器分配表(registerallocation table, RAT)完成的。这帮助提高需要访问相同寄存器集合的指令的处理速度。

通过检查微操作需要的输入元素,微操作调度器确定何时准备好处理微操作。它的工作是把准备好处理的微操作发送给退役单元,同时仍然维持程中相关性。微操作调度器使用两个队列,在它们中安排微操作(一个队列用于需要内存访问的微操作,一个用于不需要内存访问的)。
这两个队列被捆绑到分派端口。不同类型的奔腾处理器可能包含不同数量的分派端口。分派端口把微操作发送给退役单元。

退役单元

退役单元接收从管线解码器和乱序执行引擎发送来的所有微操作,并且试图把微操作重新调整为程序能够正确执行的适当顺序。
退役单元按照乱序执行引擎发送微操作的顺序,把它们发送给执行单元进行处理,但是然后它监视结果,把结果重新调整为程序能够执行的正确顺序。

这些操作是使用一个大型缓冲区区域保存微操作的结果并且按照它们被需要的正确顺序放置它们来实现的。

当微操作完成并且结果被安排为正确的顺序之后,微操作就被认为是退役了,并且从退役单元删除。退役单元还更新分支预测单元中的信息,以便确保它知道哪些分支已经被采用了,以及哪些指令码已经被处理过了。

执行单元

处理器的主要功能是执行指令。 这个功能是在执行单元中实现的。 单一处理器实际上可以包含多个执行单元,能够同时处理多条指令码。
执行单元由一个或者多个运算逻辑单元 (Arithmetic Logic Unit, ALU) 构成。 ALU被专门设计为处理不同数据类型的数学操作。 奔腾4的执行单元包括用于下列功能的独立ALU:

  • 简单整数操作
  • 复杂整数操作
  • 浮点操作

低级潜在因素的整数执行单元被设计为快速完成简单整数数学操作,比如加法、减法和布尔操作。奔腾4处理器在每个时钟周期能够完成两个低级潜在因素的整数操作,实际上使处理速度加倍了。

复杂整数执行单元处理更加复杂的整数数学操作。 复杂整数执行单元在4个时钟周期之内处理大多数移位和循环指令。 乘法和除法操作涉及较长的计算时间,通常要花费14-60个时钟周期。

在IA-32系列的不同处理器之间,浮点执行单元是有区别的。 所有奔腾处理器都可以使用标准浮点执行单元处理浮点数学操作。 包含MMX和SSE支持的奔腾处理器也在浮点执行单元中完成这些计算。
浮点执行单元包含处理长度从64位到128位的数据元素的寄存器。 这样就使在计算中使用更大的浮点值成为可能,这可以加快复杂浮点计算(比如数字信号处理和视频压缩)的速度。

寄存器

处理器的大多数操作都必须处理数据。不幸的是,处理器可能采取的最慢的操作是试图读取内存中的数据或者把数据存储到内存中。

当处理器访问数据元素时,请求必须被发送到处理器外部、 通过控制总线,然后进入内存存储单元。这一过程不仅复杂,而且在执行内存访问时迫使处理器处于等待状态。这一 停机时间可以用于处理其他指令。为了帮助解决这个问题,处理器包含内部的内存位置, 称为寄存器(register)。寄存器能够存储要处理的数据元素, 而无需访问内存存储单元。寄存器的不利之处在于处理器芯片中内置的寄存器数最是有限的。

IA-32平台具有不同长度的多组寄存器。IA-32平台中的不同处理器包含专门的寄存器。IA-32系列中所有处理器都可以使用的寄存器的核心组显示在下表中。

寄存器 描述
通用 8个32位寄存器,用于存储正在处理的数据
6个16位寄存器,用于处理内存访问
指令指针 单一的32位寄存器,指向要执行的下一条指令码
浮点数据 8个80位寄存器,用于浮点数据存储
控制 5个32位寄存器,用于确定处理器的操作模式
调试 8个32位寄存器,用于在调试处理器时包含信息

通用寄存器

当处理器处理数据时,通用寄存器用于临时地存储数据。通用寄存器的应用从旧式的8位8080处理器的时代就开始了,发展到奔腾处理器中可用的32位寄存器,目前处理器的通用寄存器都是64位。通用寄存器的每个新版本都被设计为完全向下兼容以前的处理器。因此,使用8080芯片上的8位寄存器的代码在32位或64位的通用寄存器上依然是有效的。

虽然大多数通用寄存器都可以用于存储任何类型的数据,但是其中一些具有专门的用途,它们在汇编语言程序中以一致的方式使用。 下表列出了奔腾平台上可用的通用寄存器和它们最常被用于什么目的。

寄存器 描述
EAX 用于操作数和结果数据的累加器
EBX 指向数据内存段中的数据的指针
EXC 字符串和循环操作的计数器
EDX I/O指针
EDI 用于字符串操作的目标的数据指针
ESI 用于字符串操作的源的数据指针
ESP 堆栈指针
EBP 堆栈数据指针

RAX_EAX_AX_AH_AL

64位的RAX,32位的EAX寄存器也可用通过16位和8位名称引用, 以表示寄存器的旧式版本。

通过使用EAX引用,RAX寄存器的低32位被使用。

通过使用AX引用,EAX寄存器的低16位被使用。

通过使用AL引用,EAX的低8位被使用。 AH引用AL之后的高8位。

段寄存器

段寄存器专门用于引用内存位置。IA-32处理器平台允许3种不同的访问系统内存的方法:

  • 平坦内存模式
  • 分段内存模式
  • 实地址模式

平坦内存模式把全部系统内存表示为连续的地址空间。 所有指令、 数据和堆栈都包含在相同的地址空间中。通过称为线性地址 (linear address) 的特定地址访问每个内存位置。

分段内存模式把系统内存划分为独立段的组,通过位于段寄存器中的指针进行引用。 每个段用于包含特定类型的数据。一个段用于包含指令码, 另一个段用于包含数据元素,第三个段用于包含程序堆栈。
段中的内存位置是通过逻辑地址定义的。

逻辑地址由段地址和偏移地址构成。

处理器把逻辑地址转换为相应的线性地址位置以便访问内存的字节。

段寄存器用于包含特定数据访问的段地址。

下表描述可用的段寄存器:

段寄存器 描述
CS 代码段
DS 数据段
SS 堆栈段
ES 附加段指针
FS 附加段指针
GS 附加段指针

每个段寄存器都是16位的,包含指向内存特定段的起始位置的指针。CS寄存器包含指向内存中代码段的指针。代码段是内存中存储指令码的位置。处理器按照CS寄存器的值和EIP指令指针寄存器中包含的偏移值从内存获得指令码。程序不能显式地加载或者改变CS寄存器。 当程序被分配一个内存空间时,处理器将为CS寄存器赋值。

DS、 ES、 FS和GS段寄存器都用于指向数据段。通过使用4个独立的数据段,程序可以分隔数据元素, 确保它们不会重叠。 程序必须加载带有段的正确指针值的数据段寄存器,并且使用偏移值引用各个内存位置。

SS段寄存器用于指向堆栈段。堆栈包含传递给程序中的函数和过程的数据值。

如果程序使用实地址模式,那么所有段寄存器都指向零线性地址,并且都不会被程序改动。所有指令码、数据元素和堆栈元素都是通过它们的线性地址直接访问的。

指令指针寄存器

指令指针寄存器(即EIP寄存器),有时候称为程序计数器 (program counter),它跟踪要执行的下一条指令码。虽然听上去像一个简单的过程,但是对于指令预取缓存的实现却不是的。指令指针指向要执行的下一条指令。

应用程序不能直接修改指令指针本身,不能指定内存地址并且把它放到EIP寄存器中。必须使用一般的程序控制指令(比如跳转)来改变要读入到预取缓存的下一条指令。

在平坦内存模式中,指令指针包含下一条指令码的内存位置的线性地址。如果应用程序使用分段内存模式,那么指令指针指向逻辑内存地址,通过CS寄存器的内容引用。

控制寄存器

5个控制寄存器用于确定处理器的操作模式,还有当前正在执行的任务的特性。下表介绍各个控制寄存器:

控制寄存器 描述
CR0 控制操作模式和处理器状态的系统标志
CR1 当前没有使用
CR2 内存页面错误信息
CR3 内存页面目录信息
CR4 支持处理器特效和说明处理器特效能力的标志

不能直接访问控制寄存器中的值,但是可以把控制寄存器中包含的数据传送给通用寄存器。数据被传送给通用寄存器之后,应用程序就可以查看寄存器中的位标志以便确定处理器和/或当前正在运行的任务的操作状态。

如果必须改动控制寄存器的标志值,可以改动通用寄存器中的数据, 然后把内容传送给控制寄存器。

系统程序员经常修改控制寄存器中的值。一般的用户应用程序不经常修改控制寄存 器项目,虽然它们可能经常查询标志值以便确定正在运行应用程序的宿主处理器芯片的能力。

标志

对于处理器中实现的每个操作,都必须有一种机制来确定操作是否成功。处理器标志用于实现这个功能。

标志对于汇编语言程序是重要的,因为它们是可以用于确定程序的功能是否成功执行的唯一途径。

例如,如果应用程序执行减法操作,其结果为负值,处理器内一个专门的标志就会被设置。不检查这个标志,汇编语言程序就没办法了解是否发生了错误。

IA-32平台使用单一的32位寄存器包含一组状态、控制和系统标志。 EFLAGS寄存器包含32位信息,这些信息被映射为表示信息的特定标志。一些位被保留供未来使用,允许在未来的处理器中定义附加的标志。

按照功能,标志被分为3组。

  • 状态标志
  • 控制标志
  • 系统标志

状态标志

状态标志用于表明处理器进行的数学操作结果。

标志 名称
CF 0 进位标志
PF 2 奇偶校验标志
AF 4 辅助进位标志
ZF 6 零标志
SF 7 符号标志
QF 11 溢出标志

如果无符号整数值的数学操作产生最高有效位的进位或者借位,进位标志就被设置为1。 这表示数学操作中发生了寄存器溢出的情况。 发生溢出时, 寄存器中保存的数据不是数学操作的正确答案。

奇偶校验标志用于表明数学操作中的结果寄存器是否包含错误数据。作为简单的正确性检查, 如果结果中为1的位的总数是偶数,奇偶校验标志就被设置为1;如果结果中为I的位的总数是奇 数,它就被清零。通过检查奇偶校验标志,应用程序可以确定寄存器的数据是否被操作破坏了。

辅助进位标志用于二进制编码的十进制(Binary Coded Decimal, BCD) 。 如果用于运算的寄存器的第3位发生进位或者借位操作,辅助进位标志就被设置为1。

如果操作的结果为零,零标志就被设置为1。 这经常用作确定数学操作的结果是否为零的便捷途径。

符号标志被设置为结果的最高有效位,这一位是符号位。 它表明结果是正值还是负值。

当带符号整数运算的结果的正值过大, 或者负值过小时, 溢出标志用于正确地表示寄存器中的值。

控制标志

控制标志用于控制处理器中的特定行为。 当前,只定义了一个控制标志——DF标志,即方向标志。 它用于控制处理器处理字符串的方式。

当DF标志被设置(置l)时,字符串指令自动递减内存地址以便到达字符串中的下一个字节。当DF标志被清零(置0)时, 字符串指令自动递增内存地址以便到达字符串的下一个字节。

系统标志

系统标志用于控制操作系统级别的操作。 应用程序绝不应该试图修改系统标志。 下表列出了系统标志。

标志 名称
TF 8 陷阱标志
IF 9 中断使能标志
IOPL 12和13 I/O特权级别标志
NT 14 嵌套任务标志
RF 16 恢复标志
VM 17 虚拟8086模式标志
AC 18 对准检查标志
VIF 19 虚拟中断标志
VIP 20 虚拟中断挂起标志
ID 21 识别标志

陷阱标志被设置为l时启用单步模式。 在单步模式中,处理器每次只执行一条指令, 然后等待执行下一条指令的信号。 在调试汇编语言程序时这一特性极为有用。

中断使能标志控制处理器如何响应从外部源接收到的信号。

I/0特权字段表明当前正在运行的任务的1/0特权级别。 它定义I/0地址空间的访问级别。 特 权字段值必须小于或者等于访问1/0地址空间所必需的访问级别;否则, 任何访间地址空间的请求都会被拒绝。

嵌套任务标志控制当前正在运行的任务是否链接到前一个执行的任务。 它用于链接被中断 和被调用的任务。

恢复标志控制处理器在调试模式中如何响应异常。

虚拟8086标志表明处理器在虚拟8086模式中进行操作, 而不是保护模式或者实模式。

对准检查标志(和CRO控制寄存器中的AM位一起)用于启用内存引用的对准检查。

当处理器在虚拟模式中进行操作时, 虚拟中断标志起IF标志的作用。

当处理器在虚拟模式中进行操作时, 虚拟中断挂起标志用于表示一个中断正被挂起。

ID标志有意思的地方是它表示处理器是否支持CPUID指令。
如果处理器能够设置或者清零这个标志, 它就支持CPUID指令。 如果不能,那么CPUID指令就不可用。

x87浮点单元

从80486处理器开始, 80287和80387芯片的高级运算功能被合并到了主处理器中。 为了支持这些功能, 需要附加的指令码以及附加的寄存器和执行单元。 这些元素结合在一起被称为x87浮点单元 (floating-point unit, FPU)。

x87 FPU引入了下列附加的寄存器:

FPU寄存器 描述
数据寄存器 用于浮点数据的8个80位寄存器
状态寄存器 报告FPU状态的16位寄存器
控制寄存器 控制FPU精度的16位寄存器
标记寄存器 描述8个数据寄存器的内容的16位寄存器
FIP寄存器 指向下一条FPU指令的48位FPU指令指针(FPU Instruction Pointer,FIP)
FDP寄存器 指向内存中的数据的48位FPU数据指针(FPU Data Pointer,FDP)
操作码寄存器 保存FPU处理的最后指令的11位寄存器

FPU寄存器和指令码使汇编语言程序能够快速地处理复杂的浮点数学功能, 比如图形处理、数字信号处理和复杂业务应用程序所需的那些功能。 与不带FPU的标准处理器中使用的软件模拟方式相比, FPU可以使处理浮点运算的速度有相当大的提高。 只要有可能, 汇编语言程序员就应该利用FPU处理浮点运算。

MMX

多媒体扩展 (multimedia extension, MMX)自奔腾Ⅱ引入 用来实现复杂的整数算术运算操作,是支持Intel的单指令多数据 (Single Instruction, Multiple Data, SIMD) 执行模型的第一种技术。
开发SIMD模型是为了处理多媒体应用程序中常见的较大的数字。 SIMD模型使用扩展的寄 存器长度和新的数字格式来加快实时多媒体表现所需的复杂数字处理。

MMX环境包含处理器可以处理的3种新的整数数据类型:

  • 64位打包的字节整数
  • 64位打包的字整数
  • 64位打包的双字整数

为了处理新的数据格式,MMX技术引入了8个FPU寄存器作为专用寄存器。 MMX寄存器被命名为MMO到MM7,它们用于对64位打包的整数执行整数算术运算。

虽然MMX技术提高了复杂整数算术运算的处理速度,但是它对需要复杂浮点算术运算的程序没有任何帮助。 这个问题是使用SSE环境解决的。

SSE

流化SIMD扩展 (Streaming SIMD extension, SSE)增强了经常用于3D图形、 动态视频和视频会议的复杂浮点算术运算的性能。
奔腾III处理器中SSE的第一个实现引入了8个新的128位寄存器(称为XMM0到XMM7) 和一种新的数据类型—-128位打包的单精度浮点数。SSE技术还引入了附加的新指令码,可以在单 一指令中处理最多4个128位打包的单精度浮点数。
奔腾Ⅳ处理器中SSE的第二个实现(SSE2) 引入了SSE使用的相同的XMM寄存器,还引入了 5种新的数据类型:

  • 128位打包的双精度浮点数
  • 128位打包的字节整数
  • 128位打包的字整数
  • 128位打包的双字整数
  • 128位打包的四字整数

新的数据类型和相应的指令码使程序员能够在他们的程序中利用更加复杂的数学操作。128位双精度浮点数据类型允许用最短的处理器时间实现高级3D几何技术(比如光线跟踪)。SSE的第三个实现(SSE3)没有创建任何新的数据类型,但是提供了几个新的指令用于处理XMM寄存器中的整数和浮点值。

超线程

超线程从奔腾Ⅳ处理器系列开始引入,使单一IA-32处理器能够同时处理多个程序执行线程。

超线程技术由位于单一物理处理器中的两个或者多个逻辑处理器构成。每个逻辑处理器都包含通用段、控制和调试寄存器的完整集合。所有逻辑处理器共享相同的执行单元。乱序执行引擎负责处理不同逻辑处理器提供的独立线程的指令码。

超线程的主要优势表现在操作系统级别。多任务操作系统(比如MicrosoftWindows和各种UNIX实现)能够把应用程序分配给各个逻辑处理器。

本文学习自 Professional Assembly Language

赞(0) 打赏
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。ShadowInk » 处理器(IA-32)
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

游戏 && 后端

传送门传送门

觉得文章写的还行就打赏一下呗~~~

支付宝扫一扫打赏

微信扫一扫打赏