以Q&A的方式:

第一章 处理器架构

  1. 一条指令在流水线的过程?

    指令首先进入流水线(pipeline)的前端(front-end),包括预取(fetch)和译码(decode),经过分发(dispatch)和调度(schedule)后进入执行单元,最后提交执行结果。

    所有的指令采用顺序方式通过前端,并采用乱序的方式进行发射,然后乱序执行,最后用顺序方式提交执行结果。

  2. 既然指令是顺序提交结果,那为什么还需要内存屏障

    在CPU0上执行下面操作:

    data = 1;        // 操作1
    flag = 1;        // 操作2
    

    在单核的角度上,乱序执行 + 顺序提交 确保了单核程序的局部正确性。

    在多核的角度上,由于MESI协议的store buffer特性和invaild queue特性,可能会先观察到flag=1,后观察到data=1,从而误读旧data值。

  3. 顺序提交的必要性?

    1. 保证上下文的行为一致性;
    2. 保障了异常处理的精确性;(比如Inst1 执行中触发异常(如缺页、除零),尚未执行的 Inst2 必须处于未提交状态,以便异常处理完成后可回滚到一致的程序状态)。
  4. 寄存器重命名技术的概念?

    寄存器重命名技术能够消除 假性依赖(寄存器读后写相关(Write-after-Read ,WAR )和写后写相关(Write-after-Write,WAW),允许更多指令乱序执行,最大化利用处理器流水线的并行能力,但无法消除真性依赖(RAW(写后读))。

    WAR例子:

    ADD R1, R2, R3  ; R1 = R2 + R3 (读 R2、R3,写 R1)
    MUL R2, R4, R5  ; R2 = R4 * R5 (读 R4、R5,写 R2)
    

    分析:因为指令2要写R2,指令1需要读R2,所以造成了WAR依赖。若 指令2 因乱序执行先完成,指令1 将读取被覆盖后的错误 R2 值。

    解决:修改指令2的目标寄存器为P2,则消除了WAR冲突。

    WAW例子:

    MUL R1, R2, R3  ; R1 = R2 * R3 (写 R1)  
    ADD R1, R4, R5  ; R1 = R4 + R5 (写 R1)
    

    **分析:**理想情况下,指令2 结果应覆盖 指令1,但是在乱序执行的情况下,R1会被污染,导致最终结果错误。

    **解决:**将指令1的R1改为P1,指令2的R1改为P2。

  5. 分支预测于likely()有关吗?

    **分支预测技术(动态技术)**与likely()/unlikely()宏的关联性主要体现在 编译器的静态优化 层面,而非硬件动态分支预测的直接控制。

    likely(x) 宏的本质是通过 __builtin_expect 提示编译器 分支的更可能走向,从而在编译阶段优化指令布局:

    这个跟冷热代码布局有关。

  6. 内存屏障产生的原因?

    内存乱序访问主要发生在如下两个阶段:

    (1)编译时,编译器优化导致内存乱序访问。 (2)执行时,多个CPU 间交互引起的内存乱序访问。

  7. 编译器造成的内存屏障怎么解决?

    编译时的乱序访问可以通过barrier()函数来规避。这个是静态的。

  8. 多个CPU间交互引起的内存乱序访问怎么解决?

    image.png

    SMP:多核。UP:单核。

    rmb()smp_rmb()的区别是什么,分别用在什么场景?

    smp_rmb()仅针对多核的数据共享,而rmb()可能涉及更广泛的域(如系统级,比如说DMA),因此后者开销更大。内核未启用CONFIG_SMP(即单核编译)时,smp_rmb()可能退化为编译器屏障(barrier()),而rmb()仍会生成硬件屏障指令。

    场景 使用屏障 原因
    多核间的读顺序同步(如自旋锁) smp_rmb() 仅需约束 CPU 核间的读顺序,开销小(dmb ishld)。
    设备驱动的 DMA 操作 rmb() 需约束 CPU 和设备间的读顺序,必须用全系统屏障(dmb sy)。
    单核环境但需硬件屏障 rmb() smp_rmb() 在单核编译时可能退化为空操作,而 rmb() 始终生成硬件指令。
  9. 单片机什么场景下需要内存屏障?

    单独写一篇文章。

  10. Linux下的READ_ONCEWRITE_ONCE的作用?

    READ_ONCEWRITE_ONCE是编译器屏障+轻量级内存屏障,等同于volatile+LDAR

  11. PIPT ,VIVT ,VIPT 的区别?

在查询cache时,到底用虚拟地址还是物理地址?

- VIVT:使用虚拟地址的索引域和虚拟地址的标记域,相当于虚拟高速缓存。
- PIPT:使用物理地址的索引域和物理地址的标记域,相当于物理高速缓存。
- VIPT:使用**虚拟地址的索引域**和**物理地址的标记域**。

**PIPT**:

![image.png](attachment:e8deb1fb-b237-411d-b5fc-94c9841d4334:image.png)

**VIVT**:

![image.png](attachment:c1bb02ef-c2e1-4032-a44d-93504c179a89:image.png)

**VIPT:**

![image.png](attachment:6778883e-ce02-4a5c-bb74-020ecbec1518:image.png)

处理器输出的**虚拟地址会同时发送到TLB/MMU 进行地址翻译**,以及在高速缓存中进行索引和查询高速缓存。在TLB/MMU 里,会把VPN 翻译成PFN,**同时用虚拟地址的索引域和偏移量来查询高速缓存**。高速缓存和TLB/MMU 可以同时工作,**当TLB/MMU 完成地址翻译后,再用物理标记域来匹配高速缓存行**。

![image.png](attachment:0ed2fc07-3dbf-470e-8e49-863ee43f08c3:image.png)
  1. 虚拟高速缓存引入的问题?

    VIVT容易引入重名问题同名问题

    重名问题:多个不同的虚拟地址映射到同一个物理地址。造成cache浪费和缓存一致性问题。

    同名问题:相同的虚拟地址在不同进程中映射到不同的物理地址。经常出现在进程切换时,进程可能会访问到旧进程遗留的缓存数据,这些数据对新进程来说是错误的。

  2. MESI协议的作用?

    MESI协议是用于维护多核处理器缓存一致性的核心机制,确保多个CPU核心对同一内存位置的访问不会导致数据冲突或脏数据问题。

  3. MESI协议的4种状态?

    1. Modified(修改,M)
      • 数据仅存在于当前缓存中,且已被修改(与内存不一致)
      • 必须将数据写回内存后,其他核心才能访问。
    2. Exclusive(独占,E)
      • 数据仅存在于当前缓存中,未被修改(与内存一致)。
      • 其他核心未缓存该数据,可随时写入(无需广播)。
    3. Shared(共享,S)
      • 数据存在于多个缓存中,所有副本与内存一致
      • 任何核心在写入前需通知其他核心将副本设为无效(Invalid)。
    4. Invalid(无效,I)
      • 缓存行未加载有效数据,或数据已过期。
  4. MESI协议的状态转换?

    场景:4个CPU访问同一数据A

    1. T0时刻(初始化)
    2. T1时刻(CPU0读A)
    3. T2时刻(CPU1读A)
    4. T3时刻(CPU3写A)
    5. T4时刻(CPU2读A)