今天和几位单位大佬聊天时,讨论到一个非常有趣的问题-当程序执行MOV [0001H], 01H计算机实际上都做了哪些工作?乍一看这个问题平平无奇,CPU只是把立即数01H放在了地址为0001的内存里,但仔细想想这个问题远没有那么简单,由于现代计算机体系中CPU速度比内存要快2到3个个数量级,因此从CPU执行MOV指令,到实际把01H写入内存之间,还有非常漫长而复杂的过程。
为了回答好这个问题,我翻阅了一些资料,发现不同时代的CPU在执行这个操作时,行为也不一样,下面分享一下我的成果。
一.386及之前的CPU
从现在的角度来看,386及以前的CPU完全是上古时期的物件,还没有指令流水线的概念,当CPU执行这条汇编指令时,会和我们的第一印象差不多,基本会是按照以下步骤进行工作:
1.取指、译码,简单说就是从可执行文件中把MOV的二进制表示100010翻译出来。
2. 取操作数,将立即数01H加载到内部寄存器。
3. 取内存地址,将内存地址0001H(地址为0001的内存单元,不考虑段寄存器加偏移量的形式)加载到内部寄存器。
4. 指令执行,将内存地址0001H所指向的内存单元与内部寄存器中的数据(即01H)进行写入操作,完成将01H写入到内存单元000H1的操作。
二.486与奔腾时代之前的CPU
这个阶段的CPU和386相比增加了指令流水线的概念,我们知道CPU的每个动作都需要用晶体震荡而触发,想完成一个最简单的指令也需要取指、译码、取操作数、执行以及获取操作结果等若干步骤,而每个步骤都需要一次晶体震荡才能推进,因此在流水线技术出现之前执行一条指令至少需要5到6次晶体震荡周期才能完成。
指令/时刻 | T1 | T2 | T3 | T4 | T5 |
MOV | 取指 | 译码 | 取操作数 | 执行 | 取结果 |
而流水线的思想就是将不同指令的译码、寻址、执行等操作,放在一个震荡周期中并发完成。
指令/时刻 | T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8 |
指令1 | 取指 | 译码 | 取操作数 | 执行 | 取结果 |
|
|
|
指令2 |
| 取指 | 译码 | 取操作数 | 执行 | 取结果 |
|
|
指令3 |
|
| 取指 | 译码 | 取操作数 | 执行 | 取结果 |
|
指令4 |
|
|
| 取指 | 译码 | 取操作数 | 执行 | 取结果 |
指令5 |
|
|
|
| 取指 | 译码 | 取操作数 | 执行 |
指令6 |
|
|
|
|
| 取指 | 译码 | 取操作数 |
指令7 |
|
|
|
|
|
| 取指 | 译码 |
指令8 |
|
|
|
|
|
|
| 取指 |
但上述升级对于MOV [0001H] 01H指令的执行没有带来太多本质变化。
三、现代多核CPU
在2006年英特尔发布一代酷睿处理器之后,现代桌面CPU正式进入了多核时代,现代CPU在执行指令时会和内存交互,但CPU本身只是直接操作高速缓存而不会直接操作内存。
MESI协议简介
现代X86 CPU的高速缓存(Cache),包括多级缓存(L1、L2、L3)。高速缓存是位于CPU内部的一块快速访问的存储区域,用于临时存储从主内存中读取的指令和数据。当CPU需要访问内存时,它首先会检查高速缓存,如果所需数据已经存在于高速缓存中,CPU会直接从高速缓存中读取或写入数据,无需访问主内存。其中L1和L2为每个CPU核心的专属缓存,L3为各核心的共享使用。 CPU多核心之间的高速缓存同步一般按照MESI协议进行,即每个缓存行都有四个状态,分别是E(exclusive)、M(modified)、S(shared)、I(invalid),说明如下:
M:代表该缓存行中的内容被修改,并且该缓存行只被缓存在该CPU中。这个状态代表缓存行的数据和内存中的数据不同。
E:代表该缓存行对应内存中的内容只被该CPU缓存,其他CPU没有缓存该缓存对应内存行中的内容。这个状态的缓存行中的数据与内存的数据一致。
I:代表该缓存行中的内容无效。
::该状态意味着数不只存在在本地CPU缓存中,还存在其它CPU的缓存中。这个状态的数据和内存中的数据也是一致的。不过只要有CPU修改该缓存行都会使该行状态变成 I 。
四种状态的状态转移图如下:
在初始执行MOV [0001H] 01H,实际执行这条语句的核心上会触发local write的动作,这实际会把内存地址对应的缓址行置为Modify状态,而Modify状态下内存和缓存的内容实际是不一致的。而01H到底会不会被写回内存也是需要讨论的。
01H被显式写入内存0001H:这种情况也有其他其它核心也发起了对于0001H地址的读取操作,即发起remote read,在这种情况缓存的状态会从Modify迁移到Share状态,而Share状态下缓存中的数据会被显示的写回内存。也就是MOV [0001H] 01H被实际执行完成。
01H未被写入内存0001H:这种情有其他其它核心发对于地于0001H的写操作,即发起remote wri,e,这种情况下,在这种情况缓存的状态会从Modify迁移到Invalid状态,而Invalid状态下的调整缓存数据不会再被写入内存了。
内存写入状态未知:如果0001H地址的读写一直由同一核心操作,那么这种情况下缓存的状态就一直是Modify,那么这时候01H是否被写入内存,就取决于0001H这个内存地址是否能始终处于热点访问状态了,如果该地址一直在缓存中不被换出的话,那么01H的值有可能不会被写入内存,只有当该地址从缓存中换出时才会触发内存的回写操作。
总之,现代的CPU在执行MOV [0001H] 01H这样一条平平无奇的指令时,其实背后也做了很多工作,甚至操作完成后01H的值到底有没有写入到内存地址0001H也是很不确定的,希望通过本文梳理各阶段CPU执行MOV [0001H] 01H指令的过程,可以帮助读者更好的理解现代CPU的工作原理。