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. Lec16 File system performance and fast crash recovery (Robert)

16.6 ext3 transaction commit步骤

Previous16.5 ext3文件系统调用格式Next16.7 ext3 file system恢复过程

Last updated 4 years ago

Was this helpful?

基于上面的系统调用的结构,接下来我将介绍commit transaction完整的步骤。每隔5秒,文件系统都会commit当前的open transaction,下面是commit transaction涉及到的步骤:

  1. 首先需要阻止新的系统调用。当我们正在commit一个transaction时,我们不会想要有新增的系统调用,我们只会想要包含已经开始了的系统调用,所以我们需要阻止新的系统调用。这实际上会损害性能,因为在这段时间内系统调用需要等待并且不能执行。

  2. 第二,需要等待包含在transaction中的已经开始了的系统调用们结束。所以我们需要等待transaction中未完成的系统调用完成,这样transaction能够反映所有的写操作。

  3. 一旦transaction中的所有系统调用都完成了,也就是完成了更新cache中的数据,那么就可以开始一个新的transaction,并且让在第一步中等待的系统调用继续执行。所以现在需要为后续的系统调用开始一个新的transaction。

  4. 还记得ext3中的log包含了descriptor,data和commit block吗?现在我们知道了transaction中包含的所有的系统调用所修改的block,因为系统调用在调用get函数时都将handle作为参数传入,表明了block对应哪个transaction。接下来我们可以更新descriptor block,其中包含了所有在transaction中被修改了的block编号。

  5. 我们还需要将被修改了的block,从缓存中写入到磁盘的log中。之前有同学问过,新的transaction可能会修改相同的block,所以在这个阶段,我们写入到磁盘log中的是transaction结束时,对于相关block cache的拷贝。所以这一阶段是将实际的block写入到log中。

  6. 接下来,我们需要等待前两步中的写log结束。

  7. 之后我们可以写入commit block。

  8. 接下来我们需要等待写commit block结束。结束之后,从技术上来说,当前transaction已经到达了commit point,也就是说transaction中的写操作可以保证在面对crash并重启时还是可见的。如果crash发生在写commit block之前,那么transaction中的写操作在crash并重启时会丢失。

  9. 接下来我们可以将transaction包含的block写入到文件系统中的实际位置。

  10. 在第9步中的所有写操作完成之后,我们才能重用transaction对应的那部分log空间。

在一个非常繁忙的系统中,log的头指针一直追着尾指针在跑(注,也就是说一直没有新的log空间)。在当前最早的transaction的所有步骤都完成之前,或许不能开始commit一个新的transaction,因为我们需要重复利用最早的transaction对应的log空间。不过人们通常会将log设置的足够大,让这种情况就不太可能发生。

学生提问:你刚刚说没有进程会等待这些步骤完成,那么这些步骤是在哪里完成的呢?

Robert教授:这些是在后台的内核线程完成的。

学生提问:我有个有关重用log空间的问题,假设我们使用了一段特定的log空间,并且这段log空间占据了是刚刚释放出来的所有log空间,但是还不够,那么文件系统会等待另一部分的log空间释放出来吗,还是会做点别的?

Robert教授:是的,会等待。让我画张图来确保我回答的是正确的问题。我们可以认为log是磁盘中的一段线性空间,假设现存的transaction中最早的是T7,之后是T8,T9,我们想要将T10放在T9之后的空闲区域。

我们或许要等待T7将所有的block写入到文件系统对应的位置,这样我们才能释放T7对应的空间。这意味着T10中的步骤需要暂停以等待T7释放出来。这是你的问题吗?

同一个学生:是的,所以可能是这样,我先写入T10的block到现有的log空闲区域,但是如果最后log足够大并且我们用光了空闲区域,我们就需要等待T7的空间被释放出来,是吗?

Robert教授:是的,如果需要写入的数据足够多,并且log迅速的用光了。我们甚至都不能在释放log空间之前开始新的系统调用。如果你们关注细节的话,这里会有一些潜在的死锁。首先系统调用需要预声明需要多少个block,这样logging系统才知道对于该transaction需要多少log空间,因为我们不会在没有足够空间来commit transaction时,开始一个新的transaction(注,难道不能将不能写入到磁盘log中的transaction先缓存在内存中吗?虽然这样可能会导致堆积)。

学生提问:如果新的transaction需要的空间走到了T8,那么现在就需要等待T7,T8结束,这是怎么工作的呢?

Robert教授:图中的T7,T8,T9其中的系统调用都完成了,并且都已经在commit到log中了。在上面的图中,我们会直接开始T10,新的系统调用会写入到transaction T10,最终当T10需要commit到log中,并且它大到需用用到T8的空间时,它需要等待T7,T8结束。文件系统会记录每个transaction的大小,这样文件系统就知道要等待多少个之前的transaction结束。所以这里还有不少的记录工作,这样文件系统才能理解所有旧的transaction的状态。

有关如何重用log空间,这里有个小细节。在log的最开始有一个super block,所以在任何时候log都是由一个super block和一些transaction组成。假设T4是最新的transaction,之前是T1,T2,T3。

我们是否能重用一段log空间,取决于相应的transaction,例如T2,是否已经commit并且写入到文件系统的实际位置中,这样在crash并重启时就不需要重新执行这段transaction了。同时也取决于T2之前的的所有transaction是否已经被释放了。所有的这些条件都满足时,我们就可以释放并重用T2对应的log空间。