(已更正) 这个问题包括CPU的硬件结构和汇编语言的范畴. 这里梳理一下.
首先, 题主"李建国"自问自答的部分说的是正确的, CPU的指令集是软件与CPU这两个层级之间的接口, 而CPU自己, 就是对于这一套CPU指令集的"实例化".
无论处于上层的软件多么的高级, 想要在CPU执行, 就必须被翻译成"机器码", 翻译这个工作由编译器来执行. 编译器在这个过程中, 要经过"编译", "汇编", "链接"几个步骤, 最后生成"可执行文件". 可执行文件中保存的是二进制机器码. 这串机器码可以直接被CPU读取和执行.
软件意义上, "指令集"实际上是一个规范, 规范汇编的文件格式.
以下为一条x86汇编代码:
mov word ptr es:[eax + ecx * 8 + 0x11223344], 0x12345678
这里可以体现出指令集的格式限制:
1. 可以使用mov指令, 但它只能有2个操作数.
2. 它的操作数长度是16 (word), 不要看到后面0x12345678就认为是32位操作数.
3. 它带有段超越前缀, 这里使用了es, 还可以使用ds, cs, ss, fs, gs. 但是只能用这几个.
4. 第一个操作数是一个内存地址, 第二个是立即数. 但是, 这个内存地址不能乱写, 写成[eax+ecx*10+0x11223344]就错了.
实际上, 一条汇编指令与一段机器码是一一对应的. 上面这段汇, 可以被x86编译器翻译成几乎唯一的一段机器码:
26 66 c7 84 c8 44 33 22 11 78 56
上面提到的1,2,3,4点如果有一个弄错, 这一步就会失败.
可以看出来, 指令集的作用, 就是告诉程序员/编译器, 汇编一定要有格式. 支持什么指令, 指令带什么限制条件, 用什么操作数, 用什么地址, 都是指令集规范的内容, 要是写错了, 就无法翻译成机器码.
指令集规范汇编, 汇编可以翻译成机器码, 机器码告诉CPU每个周期去做什么. 因此, CPU指令集是描述CPU能实现什么功能的一个集合, 就是描述"CPU能使用哪些机器码"的集合".
那机器码进入到CPU后又做什么呢?
=====================编译器和CPU的分界线========================
需要被执行的机器码先要被OS调度到内存之中, 程序执行时, 机器码依次经过了Memory--Cache--CPU fetch, 进入CPU流水线, 接着就要对它进行译码了, 译码工作生成的象是CPU内部数据格式, 微码(或者类似的格式, 这个格式不同的厂商会自己设计).
这个过程画成图就是:
软件层: 汇编语言
------------------------------------------------------------------------
接口: 汇编语言所对应的机器码
------------------------------------------------------------------------
硬件层: CPU使用内部数据结构进行运算
如果机器码代表的功能是在指令集规范内的, 这条机器码就可以生产微码, 并在CPU内正常流动. 假设机器码是错误的, 是不可以通过CPU的译码阶段的, 控制电路一定会报错. 这种情况反映在Windows里往往都是蓝屏, 因为CPU无法继续执行, 它连下一条指令在哪都不知道.
那么指令集在CPU里就代表: 只有CPU指令集范围内的指令可以被成功的译码, 并送往CPU流水线后端去执行.
和常规的想法不一样, CPU不需要任何形式的存储介质去存储指令集, 因为"译码"这个步骤就是在对指令集里规范的机器码做解码. 硬件上, 译码这件事需要庞大数目的逻辑门阵列来实现.
跳出格式这个圈子来看待这个问题. 可以说, CPU执行单元的能力, 决定了指令集的范围. 比如, CPU的执行单元有能力执行16位加法, 32位加法, 64位加法, 那么指令集里一般就会有ADD 16, ADD 32, ADD 64这样的表达方式. 如果CPU的执行单元没有电路执行AVX指令, 那么指令集里一般就没有VINSERTF128这样的指令供使用. 所以, 强有力的执行单元能够提供更多的指令集.
再来看"CPU指令集在哪里"这个问题, 回答是, CPU本身就是CPU指令集. 指令集规定CPU可以做什么事, CPU就是具体做这件事的工具. 如果一定要指定一个狭义的CPU指令集的存放位置. 那就是CPU中的"译码电路".
首先, 题主"李建国"自问自答的部分说的是正确的, CPU的指令集是软件与CPU这两个层级之间的接口, 而CPU自己, 就是对于这一套CPU指令集的"实例化".
无论处于上层的软件多么的高级, 想要在CPU执行, 就必须被翻译成"机器码", 翻译这个工作由编译器来执行. 编译器在这个过程中, 要经过"编译", "汇编", "链接"几个步骤, 最后生成"可执行文件". 可执行文件中保存的是二进制机器码. 这串机器码可以直接被CPU读取和执行.
软件意义上, "指令集"实际上是一个规范, 规范汇编的文件格式.
以下为一条x86汇编代码:
mov word ptr es:[eax + ecx * 8 + 0x11223344], 0x12345678
这里可以体现出指令集的格式限制:
1. 可以使用mov指令, 但它只能有2个操作数.
2. 它的操作数长度是16 (word), 不要看到后面0x12345678就认为是32位操作数.
3. 它带有段超越前缀, 这里使用了es, 还可以使用ds, cs, ss, fs, gs. 但是只能用这几个.
4. 第一个操作数是一个内存地址, 第二个是立即数. 但是, 这个内存地址不能乱写, 写成[eax+ecx*10+0x11223344]就错了.
实际上, 一条汇编指令与一段机器码是一一对应的. 上面这段汇, 可以被x86编译器翻译成几乎唯一的一段机器码:
26 66 c7 84 c8 44 33 22 11 78 56
上面提到的1,2,3,4点如果有一个弄错, 这一步就会失败.
可以看出来, 指令集的作用, 就是告诉程序员/编译器, 汇编一定要有格式. 支持什么指令, 指令带什么限制条件, 用什么操作数, 用什么地址, 都是指令集规范的内容, 要是写错了, 就无法翻译成机器码.
指令集规范汇编, 汇编可以翻译成机器码, 机器码告诉CPU每个周期去做什么. 因此, CPU指令集是描述CPU能实现什么功能的一个集合, 就是描述"CPU能使用哪些机器码"的集合".
那机器码进入到CPU后又做什么呢?
=====================编译器和CPU的分界线========================
需要被执行的机器码先要被OS调度到内存之中, 程序执行时, 机器码依次经过了Memory--Cache--CPU fetch, 进入CPU流水线, 接着就要对它进行译码了, 译码工作生成的象是CPU内部数据格式, 微码(或者类似的格式, 这个格式不同的厂商会自己设计).
这个过程画成图就是:
软件层: 汇编语言
------------------------------------------------------------------------
接口: 汇编语言所对应的机器码
------------------------------------------------------------------------
硬件层: CPU使用内部数据结构进行运算
如果机器码代表的功能是在指令集规范内的, 这条机器码就可以生产微码, 并在CPU内正常流动. 假设机器码是错误的, 是不可以通过CPU的译码阶段的, 控制电路一定会报错. 这种情况反映在Windows里往往都是蓝屏, 因为CPU无法继续执行, 它连下一条指令在哪都不知道.
那么指令集在CPU里就代表: 只有CPU指令集范围内的指令可以被成功的译码, 并送往CPU流水线后端去执行.
和常规的想法不一样, CPU不需要任何形式的存储介质去存储指令集, 因为"译码"这个步骤就是在对指令集里规范的机器码做解码. 硬件上, 译码这件事需要庞大数目的逻辑门阵列来实现.
跳出格式这个圈子来看待这个问题. 可以说, CPU执行单元的能力, 决定了指令集的范围. 比如, CPU的执行单元有能力执行16位加法, 32位加法, 64位加法, 那么指令集里一般就会有ADD 16, ADD 32, ADD 64这样的表达方式. 如果CPU的执行单元没有电路执行AVX指令, 那么指令集里一般就没有VINSERTF128这样的指令供使用. 所以, 强有力的执行单元能够提供更多的指令集.
再来看"CPU指令集在哪里"这个问题, 回答是, CPU本身就是CPU指令集. 指令集规定CPU可以做什么事, CPU就是具体做这件事的工具. 如果一定要指定一个狭义的CPU指令集的存放位置. 那就是CPU中的"译码电路".
是,这个解释起来有点长。Be patient
现代的CPU没拆过,我只在计算机组成原理实验课上用VHDL在某个实验平台上做过一个模拟的CPU。举个例子你可能比较好理解。
比如我们设计一套指令集,其中肯定有条加法指令。比如Add R1 R2 。我们可以认为这条指令的意思是计算寄存器R1中的内容和R2的和,然后把结果存到R1寄存器中。
那么经过编译后这条指令会变成二进制,比如010100010010 。这条二进制指令一共12位。明显可以分为三大部分。最前面的0101表示这是条加法指令,后面0001说的是第一个操作数是寄存器1,最后0010说的是第二个数就是寄存器2(其实实际没有这么简单的指令,至少应该区分操作数是寄存器还是直接的数据,但为了把这说的更容易理解作了简化)。我们可以通过十二根导线把这条指令输入一个CPU中。导线通电就是1,不通电就是0 。为了叙述方便我们从左到右用A0-A11给这12根导线编上号。
然后计算机会分析这条指令。步骤如下:
当然,从汇编到机器码这步是汇编程序翻译的。汇编程序当然知道某条指令要翻译成什么样的机器码。
现代的CPU没拆过,我只在计算机组成原理实验课上用VHDL在某个实验平台上做过一个模拟的CPU。举个例子你可能比较好理解。
比如我们设计一套指令集,其中肯定有条加法指令。比如Add R1 R2 。我们可以认为这条指令的意思是计算寄存器R1中的内容和R2的和,然后把结果存到R1寄存器中。
那么经过编译后这条指令会变成二进制,比如010100010010 。这条二进制指令一共12位。明显可以分为三大部分。最前面的0101表示这是条加法指令,后面0001说的是第一个操作数是寄存器1,最后0010说的是第二个数就是寄存器2(其实实际没有这么简单的指令,至少应该区分操作数是寄存器还是直接的数据,但为了把这说的更容易理解作了简化)。我们可以通过十二根导线把这条指令输入一个CPU中。导线通电就是1,不通电就是0 。为了叙述方便我们从左到右用A0-A11给这12根导线编上号。
然后计算机会分析这条指令。步骤如下:
- 最开始的两根导线A0和A1,第一根有电第二根没电,就能知道这是一条运算指令(而非存储器操作或者跳转等指令)。那么指令将被送入逻辑运算单元(ALU)去进行计算。其实很简单。只要这两根线控制接下来那部分电路开关即可。
- 接下来的A2和A3,01表示加法,那么就走加法运算那部分电路,关闭减法等运算电路。
- A4-A7将被送入寄存器电路,从中读取寄存器保存的值。送到ALU的第一个数据接口电路上。
- 后面的A8-A11同样被送入寄存器选择电路,接通R2寄存器,然后R2就把值送出来,放到ALU的第二个数据接口上。
- ALU开始运算,把两个接口电路上的数据加起来,然后输出。
- 最后结果又被送回R1。
当然,从汇编到机器码这步是汇编程序翻译的。汇编程序当然知道某条指令要翻译成什么样的机器码。
评分最高的(@Cascade )回答的应该是指令是如何执行的,对题主提的指令集存在CPU哪里,我的回答是:
1. 指令集是在设计CPU时规定的,指令集规定了有哪些CPU能够“一条条”执行的“指令”是合法的。
2. 指令集是一个集合,它的功能性体现在每条能够执行的“指令”上面。涉及到具体指令后,就涉及到谁是“操作对象”,和“如何找到操作对象”,“对操作对象进行什么操作”,这些问题上来。
3. 指令的功能是通过CPU中功能逻辑电路来实现的,之前的“指令设计”决定了如何进行“逻辑电路设计”。而相同的指令可以采取不同的逻辑电路设计。
4. 因为“指令集”本身没有功能性,所以在CPU中你不能找到“指令集”这个实体,但是指令集中的指令与CPU的内部晶体管组成的逻辑电路是等价的(除了指令部分还有中断,外设等这些暂不考虑),那么非要化等价的话,就是实现指令集中各指令的整体晶体管逻辑电路。
5. 如果你非要在没给出指令集的CPU来找到它的指令集的话,你可以通过修改PC指向的下一条指令的内容,并观察内存及各寄存器的变化,来试探每条指令编码的功能(这显然没有什么实际价值)(你可以用这种方法去理解指令的功能)。
1. 指令集是在设计CPU时规定的,指令集规定了有哪些CPU能够“一条条”执行的“指令”是合法的。
2. 指令集是一个集合,它的功能性体现在每条能够执行的“指令”上面。涉及到具体指令后,就涉及到谁是“操作对象”,和“如何找到操作对象”,“对操作对象进行什么操作”,这些问题上来。
3. 指令的功能是通过CPU中功能逻辑电路来实现的,之前的“指令设计”决定了如何进行“逻辑电路设计”。而相同的指令可以采取不同的逻辑电路设计。
4. 因为“指令集”本身没有功能性,所以在CPU中你不能找到“指令集”这个实体,但是指令集中的指令与CPU的内部晶体管组成的逻辑电路是等价的(除了指令部分还有中断,外设等这些暂不考虑),那么非要化等价的话,就是实现指令集中各指令的整体晶体管逻辑电路。
5. 如果你非要在没给出指令集的CPU来找到它的指令集的话,你可以通过修改PC指向的下一条指令的内容,并观察内存及各寄存器的变化,来试探每条指令编码的功能(这显然没有什么实际价值)(你可以用这种方法去理解指令的功能)。