MIT6.S081
  • 简介
  • Lec01 Introduction and Examples (Robert)
    • 1.1 课程内容简介
    • 1.2 操作系统结构
    • 1.3 Why Hard and Interesting
    • 1.4 课程结构和资源
    • 1.5 read, write, exit系统调用
    • 1.6 open系统调用
    • 1.7 Shell
    • 1.8 fork系统调用
    • 1.9 exec, wait系统调用
    • 1.10 I/O Redirect
  • Lec03 OS Organization and System Calls (Frans)
    • 3.1 上一节课回顾
    • 3.2 操作系统隔离性(isolation)
    • 3.3 操作系统防御性(Defensive)
    • 3.4 硬件对于强隔离的支持
    • 3.5 User/Kernel mode切换
    • 3.6 宏内核 vs 微内核 (Monolithic Kernel vs Micro Kernel)
    • 3.7 编译运行kernel
    • 3.8 QEMU
    • 3.9 XV6 启动过程
  • Lec04 Page tables (Frans)
    • 4.1 课程内容简介
    • 4.2 地址空间(Address Spaces)
    • 4.3 页表(Page Table)
    • 4.4 页表缓存(Translation Lookaside Buffer)
    • 4.5 Kernel Page Table
    • 4.6 kvminit 函数
    • 4.7 kvminithart 函数
    • 4.8 walk 函数
  • Lec05 Calling conventions and stack frames RISC-V (TA)
    • 5.1 C程序到汇编程序的转换
    • 5.2 RISC-V vs x86
    • 5.3 gdb和汇编代码执行
    • 5.4 RISC-V寄存器
    • 5.5 Stack
    • 5.6 Struct
  • Lec06 Isolation & system call entry/exit (Robert)
    • 6.1 Trap机制
    • 6.2 Trap代码执行流程
    • 6.3 ECALL指令之前的状态
    • 6.4 ECALL指令之后的状态
    • 6.5 uservec函数
    • 6.6 usertrap函数
    • 6.7 usertrapret函数
    • 6.8 userret函数
  • Lec08 Page faults (Frans)
    • 8.1 Page Fault Basics
    • 8.2 Lazy page allocation
    • 8.3 Zero Fill On Demand
    • 8.4 Copy On Write Fork
    • 8.5 Demand Paging
    • 8.6 Memory Mapped Files
  • Lec09 Interrupts (Frans)
    • 9.1 真实操作系统内存使用情况
    • 9.2 Interrupt硬件部分
    • 9.3 设备驱动概述
    • 9.4 在XV6中设置中断
    • 9.5 UART驱动的top部分
    • 9.6 UART驱动的bottom部分
    • 9.7 Interrupt相关的并发
    • 9.8 UART读取键盘输入
    • 9.9 Interrupt的演进
  • Lec10 Multiprocessors and locking (Frans)
    • 10.1 为什么要使用锁?
    • 10.2 锁如何避免race condition?
    • 10.3 什么时候使用锁?
    • 10.4 锁的特性和死锁
    • 10.5 锁与性能
    • 10.6 XV6中UART模块对于锁的使用
    • 10.7 自旋锁(Spin lock)的实现(一)
    • 10.8 自旋锁(Spin lock)的实现(二)
  • Lec11 Thread switching (Robert)
    • 11.1 线程(Thread)概述
    • 11.2 XV6线程调度
    • 11.3 XV6线程切换(一)
    • 11.4 XV6线程切换(二)
    • 11.5 XV6进程切换示例程序
    • 11.6 XV6线程切换 --- yield/sched函数
    • 11.7 XV6线程切换 --- switch函数
    • 11.8 XV6线程切换 --- scheduler函数
    • 11.9 XV6线程第一次调用switch函数
  • Lec13 Sleep & Wake up (Robert)
    • 13.1 线程切换过程中锁的限制
    • 13.2 Sleep&Wakeup 接口
    • 13.3 Lost wakeup
    • 13.4 如何避免Lost wakeup
    • 13.5 Pipe中的sleep和wakeup
    • 13.6 exit系统调用
    • 13.7 wait系统调用
    • 13.8 kill系统调用
  • Lec14 File systems (Frans)
    • 14.1 Why Interesting
    • 14.2 File system实现概述
    • 14.3 How file system uses disk
    • 14.4 inode
    • 14.5 File system工作示例
    • 14.6 XV6创建inode代码展示
    • 14.7 Sleep Lock
  • Lec15 Crash recovery (Frans)
    • 15.1 File system crash概述
    • 15.2 File system crash示例
    • 15.3 File system logging
    • 15.4 log_write函数
    • 15.5 end_op函数
    • 15.6 File system recovering
    • 15.7 Log写磁盘流程
    • 15.8 File system challenges
  • Lec16 File system performance and fast crash recovery (Robert)
    • 16.1 Why logging
    • 16.2 XV6 File system logging回顾
    • 16.3 ext3 file system log format
    • 16.4 ext3如何提升性能
    • 16.5 ext3文件系统调用格式
    • 16.6 ext3 transaction commit步骤
    • 16.7 ext3 file system恢复过程
    • 16.8 为什么新transaction需要等前一个transaction中系统调用执行完成
    • 16.9 总结
  • Lec17 Virtual memory for applications (Frans)
    • 17.1 应用程序使用虚拟内存所需要的特性
    • 17.2 支持应用程序使用虚拟内存的系统调用
    • 17.3 虚拟内存系统如何支持用户应用程序
    • 17.4 构建大的缓存表
    • 17.5 Baker's Real-Time Copying Garbage Collector
    • 17.6 使用虚拟内存特性的GC
    • 17.7 使用虚拟内存特性的GC代码展示
  • Lec18 OS organization (Robert)
    • 18.1 Monolithic kernel
    • 18.2 Micro kernel
    • 18.3 Why micro kernel?
    • 18.4 L4 micro kernel
    • 18.5 Improving IPC by Kernel Design
    • 18.6 Run Linux on top of L4 micro kernel
    • 18.7 L4 Linux性能分析
  • Lec19 Virtual Machines (Robert)
    • 19.1 Why Virtual Machine?
    • 19.2 Trap-and-Emulate --- Trap
    • 19.3 Trap-and-Emulate --- Emulate
    • 19.4 Trap-and-Emulate --- Page Table
    • 19.5 Trap-and-Emulate --- Devices
    • 19.6 硬件对虚拟机的支持
    • 19.7 Dune: Safe User-level Access to Privileged CPU Features
  • Lec20 Kernels and HLL (Frans)
    • 20.1 C语言实现操作系统的优劣势
    • 20.2 高级编程语言实现操作系统的优劣势
    • 20.3 高级编程语言选择 --- Golang
    • 20.4 Biscuit
    • 20.5 Heap exhaustion
    • 20.6 Heap exhaustion solution
    • 20.7 Evaluation: HLL benefits
    • 20.8 Evaluation: HLL performance cost(1)
    • 20.9 Evaluation: HLL performance cost(2)
    • 20.10 Should one use HLL for a new kernel?
  • Lec21 Networking (Robert)
    • 21.1计算机网络概述
    • 21.2 二层网络 --- Ethernet
    • 21.3 二/三层地址转换 --- ARP
    • 21.4 三层网络 --- Internet
    • 21.5 四层网络 --- UDP
    • 21.6 网络协议栈(Network Stack)
    • 21.7 Ring Buffer
    • 21.8 Receive Livelock
    • 21.9 如何解决Livelock
  • Lec22 Meltdown (Robert)
    • 22.1 Meltdown发生的背景
    • 22.2 Speculative execution(1)
    • 22.3 Speculative execution(2)
    • 22.4 CPU caches
    • 22.5 Flush and Reload
    • 22.6 Meltdown Attack
    • 22.7 Meltdown Fix
  • Lec23 RCU (Robert)
    • 23.1 使用锁带来的问题
    • 23.2 读写锁 (Read-Write Lock)
    • 23.3 RCU实现(1) - 基本实现
    • 23.4 RCU实现(2) - Memory barrier
    • 23.5 RCU实现(3) - 读写规则
    • 23.6 RCU用例代码
    • 23.7 RCU总结
Powered by GitBook
On this page

Was this helpful?

  1. Lec22 Meltdown (Robert)

22.4 CPU caches

Previous22.3 Speculative execution(2)Next22.5 Flush and Reload

Last updated 4 years ago

Was this helpful?

接下来我将介绍Micro-Architectural的另一个部分,也就是缓存。我知道大家都知道CPU有cache,但是缓存或多或少应该是也透明的。让我画个图描述一下cache,因为我认为cache与Meltdown最相关。

首先,你有CPU核,这是CPU的一部分,它会解析指令,它包含了寄存器,它有加法单元,除法单元等等。所以这是CPU的执行部分。

当CPU核需要执行load/store指令时,CPU核会与内存系统通信。内存系统一些cache其中包含了数据的缓存。首先是L1 data cache,它或许有64KB,虽然不太大,但是它特别的快。如果你需要的数据在L1 cache中,只通过几个CPU cycle就可以将数据取回。L1 cache的结构包含了一些线路,每个线路持有了可能是64字节的数据。这些线路是个表单,它们通过虚拟内存地址索引。如果一个虚拟内存地址在cache中,并且cache为这个虚拟内存地址持有了数据,那么实际中可以认为L1 cache中也包含了来自对应于虚拟内存地址的PTE的权限。

L1 cache是一个表单,当CPU核执行load指令时,首先硬件会检查L1 cache是否包含了匹配load指令的虚拟内存地址,如果有的话,CPU会直接将L1 cache中的数据返回,这样可以很快完成指令。

如果不在L1 cache,那么数据位于物理内存中,所以现在我们需要物理内存地址,这里需要Translation Lookaside Buffer(TLB),TLB是PTE的缓存。现在我们会检查load指令中的虚拟内存地址是否包含在TLB中。如果不在TLB,我们就需要做大量的工作,我们需要从内存中读取相关的PTE。让我们假设TLB中包含了虚拟内存地址对应的物理内存Page地址,我们就可以获取到所需要的物理内存地址。通常来说会有一个更大的cache(L2 cache),它是由物理内存地址索引。

现在通过TLB我们找到了物理内存地址,再通过L2 cache,我们有可能可以获取到数据。如果我们没有在L2 cache中找到物理内存地址对应的数据。我们需要将物理内存地址发送给RAM系统。这会花费很长的时间,当我们最终获得了数据时,我们可以将从RAM读取到的数据加入到L1和L2 cache中,最终将数据返回给CPU核。

以上就是CPU的cache。如果L1 cache命中的话可能只要几个CPU cycle,L2 cache命中的话,可能要几十个CPU cycle,如果都没有命中最后需要从内存中读取那么会需要几百个CPU cycle。一个CPU cycle在一个2GHZ的CPU上花费0.5纳秒。所以拥有cache是极其有利的,如果没有cache的话,你将会牺牲掉几百倍的性能。所以cache对于性能来说是非常关键的。

在Meltdown Attack的目标系统中,如果我们运行在用户空间,L1和L2 cache可以既包含用户数据,也包含内核数据。L2 cache可以包含内核数据因为它只是物理内存地址。L1 cache有点棘手,因为它是虚拟内存地址,当我们更换Page Table时,L1 cache的内容不再有效。因为更换Page Table意味着虚拟内存地址的意义变了,所以这时你需要清空L1 cache。不过实际中会有更多复杂的细节,可以使得你避免清空L1 cache。

论文中描述的操作系统并没有在内核空间和用户空间之间切换的时候更换Page Table,因为两个空间的内存地址都映射在同一个Page Table中了。这意味着我们不必清空L1 cache,也意味着L1 cache会同时包含用户和内核数据,这使得系统调用更快。如果你执行系统调用,当系统调用返回时,L1 cache中还会有有用的用户数据,因为我们在这个过程中并没与更换Page Table。所以,当程序运行在用户空间时,L1 cache中也非常有可能有内核数据。L1 cache中的权限信息拷贝自TLB中的PTE,如果用户空间需要访问内核内存数据,尽管内核数据在L1 cache中,你也不允许使用它,如果使用的话会触发Page Fault。

尽管Micro-Architectural的初衷是完全透明,实际中不可能做到,因为Micro-Architectural优化的意义在于提升性能,所以至少从性能的角度来说,它们是可见的。也就是说你可以看出来你的CPU是否有cache,因为如果没有的话,它会慢几百倍。除此之外,如果你能足够精确测量时间,那么在你执行一个load指令时,如果load在几个CPU cycle就返回,数据必然是在cache中,如果load在几百个CPU cycle返回,数据可能是从RAM中读取,如果你能达到10纳秒级别的测量精度,你会发现这里区别还是挺大的。所以从性能角度来说,Micro-Architectural绝对不是透明的。我们现在讨论的分支预测,cache这类功能至少通过时间是间接可见的。

所以尽管Micro-Architectural设计的细节都是保密的,但是很多人对它都有强烈的兴趣,因为这影响了很多的性能。比如说编译器作者就知道很多Micro-Architectural的细节,因为很多编译器优化都基于人们对于CPU内部工作机制的猜测。实际中,CPU制造商发布的优化手册披露了一些基于Micro-Architectural的技巧,但是他们很少会介绍太多细节,肯定没有足够的细节来理解Meltdown是如何工作的。所以Micro-Architectural某种程度上说应该是透明的、隐藏的、不可见的,但同时很多人又知道一些随机细节。

(以下内容来自82:10 - 86:30,因为相关故移过来了)

学生提问:L1 cache是每个CPU都有一份,L2 cache是共享的对吧?

Robert教授:不同CPU厂商,甚至同一个厂商的不同型号CPU都有不同的cache结构。今天普遍的习惯稍微有点复杂,在一个多核CPU上,每一个CPU核都有一个L1 cache,它离CPU核很近,它很快但是很小。每个CPU核也还有一个大点的L2 cache。除此之外,通常还会有一个共享的L3 cache。

另一种方式是所有的L2 cache结合起来,以方便所有的CPU共用L2 cache,这样我可以非常高速的访问我自己的L2 cache,但是又可以稍微慢的访问别的CPU的L2 cache,这样有效的cache会更大。

所以通常你看到的要么是三级cache,或者是两级cache但是L2 cache是合并在一起的。典型场景下,L2和L3是物理内存地址索引,L1是虚拟内存地址索引。

学生提问:拥有物理内存地址的缓存有什么意义?

Robert教授:如果同一个数据被不同的虚拟内存地址索引,虚拟内存地址并不能帮助你更快的找到它。而L2 cache与虚拟内存地址无关,不管是什么样的虚拟内存地址,都会在L2 cache中有一条物理内存地址记录。

学生提问:MMU和TLB这里位于哪个位置?

Robert教授:我认为在实际中最重要的东西就是TLB,并且我认为它是与L1 cache并列的。如果你miss了L1 cache,你会查看TLB并获取物理内存地址。MMU并不是一个位于某个位置的单元,它是分布在整个CPU上的。

学生提问:但是MMU不是硬件吗?

Robert教授:是的,这里所有的东西都是硬件。CPU芯片有数十亿个晶体管,所以尽管是硬件,我们讨论的也是使用非常复杂的软件设计的非常复杂的硬件。所以CPU可以做非常复杂和高级的事情。所以是的,它是硬件,但是它并不简单直观。

学生提问:Page Table的映射如果没有在TLB中命中的话,还是要走到内存来获取数据,对吧?

Robert教授:从L2 cache的角度来说,TLB miss之后的查找Page Table就是访问物理内存,所以TLB需要从内存中加载一些内存页,因为这就是加载内存,这些内容可以很容易将Page Table的内容缓存在L2中。