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.9 总结

Previous16.8 为什么新transaction需要等前一个transaction中系统调用执行完成NextLec17 Virtual memory for applications (Frans)

Last updated 4 years ago

Was this helpful?

最后我希望同学们记住的有关logging和ext3的是:

  • log是为了保证多个步骤的写磁盘操作具备原子性。在发生crash时,要么这些写操作都发生,要么都不发生。这是logging的主要作用。

  • logging的正确性由write ahead rule来保证。你们将会在故障恢复相关的业务中经常看到write ahead rule或者write ahead log(WAL)。write ahead rule的意思是,你必须在做任何实际修改之前,将所有的更新commit到log中。在稍后的恢复过程中完全依赖write ahead rule。对于文件系统来说,logging的意义在于简单的快速恢复。log中可能包含了数百个block,你可以在一秒中之内重新执行这数百个block,不管你的文件系统有多大,之后又能正常使用了。

  • 最后有关ext3的一个细节点是,它使用了批量执行和并发来获得可观的性能提升,不过同时也带来了可观的复杂性的提升。

以上就是今天的内容,接下来是问答环节:

学生提问:你刚刚说有一个文件系统线程会做这里所有的工作,那么只能有一个这样的线程,否则的话就会有不同步的问题了,对吗?

Robert教授:或许真的只有一个线程,我其实不知道有多少个线程,但是1是个不错的数字,因为logging的正确性取决于旧的transaction要在新的transaction之前提交。但是逻辑上来说又没有必要只有一个线程,你可以想象不同的transaction使用不同的线程来提交(注,只要锁加的合适多个线程应该也是没问题的)。

学生提问:当你在讨论crash的时候,你有一个图是T8正在使用之前释放的T5的空间,如果T8在crash的时候还没有commit,并且T5的commit block正好在T8的descriptor block所指定的位置,这样会不会不正确的表明T8已经被commit了(注,这时T8有一个假的commit block)?

Robert教授:让我尝试画出这里的场景。首先我们有一个古老的transaction T5,因为log的循环特性,在顺序上T8位于T5之前。因为T5已经被释放了,T8正在蚕食T5的log空间。假设T8没有完成commit,但是如果完成commit的话,T8的commit block会写到T5的commit block位置。

T8并没有能写入commit block,T8前面所有的block都写入了,但是最后跟的是T5的commit block。这里的答案是,descriptor block和commit block都有transaction的序列号,所以T8的descriptor block里面的序列号是8,但是T5的commit block里面的序列号是5,所以两者不能匹配。

学生提问:我们可以在transaction T8开始的时候就知道它的大小吗?

Robert教授:这是个复杂的问题。当T8作为活跃的transaction开始时,系统调用会写入数据,这时文件系统并不知道T8有多大。当文件系统开始commit T8时,是知道T8有多大的,因为文件系统只会在T8中所有的系统调用都结束之后才commit它,而在那个时间点,文件系统知道所有的写操作,所以就知道T8究竟有多大。除此之外,descriptor block里面包含了所有block的实际编号,所以当写入transaction的第一个block,也就是descriptor block时,logging系统知道T8会包含多少个block。

学生提问:为什么不在descriptor block里面记录commit信息。虽然这样可能不太好,因为要回到之前的一个位置去更新之前的一个block。

Robert教授:所以这里的提议是,与其要一个专门的commit block,可以让descriptor block来实现commit block的功能。XV6与这个提议非常像,我认为可以这么做,至少在ext3中这么做了不会牺牲性能。你需要像XV6一样来组织这里的结构,也就是需要在descriptor block包含某个数据表明这是一个已经提交过的transaction。

这样做的话,可以节省一个commit block的空间,但是不能节省整个时间。Linux文件系统的后续版本实现了你的提议,ext4做了以下工作来更有效的写commit block。ext4会同时写入所有的data block和commit block,它并不是等待所有的data block写完了之后才写的commit block。但是这里有个问题,磁盘可以无序的执行写操作,所以磁盘可能会先写commit block之后再写data block。如果中间有了crash,那么我们有了commit block,但是却没有全部的data block。ext4通过在commit block中增加校验和来避免这种问题。所以commit block写入之后发生了crash,如果data block没有全写入那么校验和不能得出正确的结果,恢复软件可以据此判断出错了。ext4可以通过这种方式在机械硬盘上写入一批block而避免磁碟旋转,进而提升磁盘性能。

学生提问:log中的data block是怎么写入到文件系统中的?

Robert教授:这个问题有多个答案。对于data block,ext3有三种模式,但是我只记得两个,journaled data和ordered data(注,第三种是writeback)。当你在配置ext3文件系统时,你需要告诉Linux你想要哪种模式。如果你想要的是journaled data,文件内容就是写入到log中,如果你向一个文件写数据,这会导致inode更新,log中会包含文件数据和更新了的inode,也就是说任何更新了的block都会记录在log中。这种方法非常慢,因为数据需要先写在log中,再写到文件系统中。所以journaled data很直观,但是很慢。

ordered data是最流行的模式,它不会将文件数据写入到log中,只会将metadata block,例如inode,目录block,写入到log中,文件的内容会直接写到文件系统的实际位置中。所以这种模式要快得多,因为你不用将文件内容写两次。但是它也会导致更多的复杂性,因为你不能随时写入文件内容。假设你执行一个写操作导致一个新的block被分配给一个文件,并将包含了新分配block编号的inode写入到log中并commit,在实际写入文件内容至刚刚分配的data block之前发生crash。在稍后的恢复流程中,你将会看到包含了新分配的block编号的inode,但是对应data block里面的内容却属于之前使用了这个data block的旧的文件。如果你运行的是一个类似Athena的多用户系统,那么可能就是一个用户拥有一个文件,其中的内容又属于另一个用户已经删除的文件,如果我们不是非常小心的处理写入数据和inode的顺序就会有这样的问题。

ext3的ordered data通过先写入文件内容到磁盘中,再commit修改了的inode来解决这里的问题。如果你是个应用程序,你写了一个文件并导致一个新的文件系统data block被分配出来,文件系统会将新的文件内容写到新分配的data block中,之后才会commit transaction,进而导致inode更新并包含新分配的data block编号。如果在写文件数据和更新inode之间发生了crash,你也看不到其他人的旧的数据,因为这时就算有了更新了的data block,但是也没关系,因为现在不仅inode没有更新,连bitmap block也没更新,相应的data block还处于空闲状态,并且可以分配给其他的程序,你并不会因此丢失block。这里的效果就是我们写了一个data block但是最终并没有被任何文件所使用。