xv6学习记录1 xv6 fs系统中Logging layer机制的进一步分析
是什么东西改变了?是磁盘盘片上的磁极(HDD)或浮栅晶体管里的电子(SSD)。这种东西只有两种状态,所以是原子的,本质是软件系统层面的原子操作就是把不确定性状态给收敛到了物理层面。是什么东西有如此之快的变化速度?没有任何物理过程是极快的!物理过程很慢,操作系统乖乖等了很久。那靠什么保证安全?靠的是磁盘控制器的“读校验拒绝”。它把一个“缓慢的时间过程”,在逻辑上变成了一个“要么 0 要么 1”的结果
在学习xv6的fs过程中,已知了buffer cache是进程和disk之间的缓冲区,Logging 层的根本目的,就是为了收拾 Buffer Cache 带来的“烂摊子”,解决在把内存里的脏数据写回磁盘时,如果突然断电(或内核崩溃)导致的“文件系统损坏”问题。
崩溃一致性(Crash Consistency)
没有 Logging 的文件系统出现的问题:
假设你想新建一个文件 hello.txt,里面写上 "A"。在底层,这个看似简单的动作需要修改 3 个不同的磁盘块:
-
修改 Bitmap 块:找一个空闲的数据块,把它标记为“已使用”。
-
修改 Inode 块:分配一个新的 Inode,把刚找的数据块地址写进去,并填上文件大小。
-
修改 Directory 块:在当前目录的数据块里,加上一行
hello.txt -> Inode编号。
Buffer Cache 会把这 3 个块都读到内存里,修改完毕。现在,它们是内存里的 3 个“脏块”,需要写回物理磁盘。 灾难发生了: 磁盘每次只能写一个块。假设系统刚把 Bitmap 块和 Inode 块写进物理磁盘,突然拔掉电源(或发生 Kernel Panic)! 第三个动作(修改目录块)没来得及写盘。 重启后,你会发现:磁盘上的某个数据块和 Inode 被永久占用了(成了孤儿),但你在任何目录里都找不到这个文件。文件系统被永久性地破坏(Corrupted)了!
救世主:Write-Ahead Logging (预写式日志)的解决方案:
为了防止这种悲剧,xv6 引入了 Logging 层,它采用了一种极其经典的机制:预写式日志(WAL)。它强制规定:任何对文件系统的修改,绝对不准直接写到它原来的物理位置,必须先打个草稿,
Logging 层把刚才的 3 个块的写入动作,打包成一个事务(Transaction)。整个过程分为四步:
-
Log Writes(写草稿): 把这 3 个脏块的数据,全部写到磁盘上一个专门划出来的区域——日志区(Log Area)。
-
Commit(盖章确认 - 最关键的一步!): 当 3 个块的草稿全部落盘后,Logging 层在日志区的头部(Log Header)写下一个标记:“事务包含 3 个块,已准备就绪”。一旦写下这个标记,这个事务就不可撤销了。
-
Install(正式搬运): 现在,把日志区里的这 3 个块的数据,从容不迫地搬运到它们真正在磁盘上的老家(Bitmap区、Inode区、Data区)。
-
Clean(清理草稿): 搬运完成后,把 Log Header 清空,表示天下太平。
为什么这样就不怕断电了?
-
如果在盖章(Commit)之前断电:重启时,系统发现 Log Header 是空的,直接把草稿当垃圾扔掉。虽然你的文件没创建成功,但文件系统是完好无损的。
-
如果在盖章(Commit)之后,搬运(Install)途中断电:重启时,系统发现 Log Header 盖了章!系统会重新执行搬运工作(Replay Log),把草稿区的数据再往老家搬一遍。你的文件依然创建成功,文件系统依然完好无损!
那么问题来了?为什么不害怕断电或者panic发生在commit的途中呢?
答案是肯定会发生在commit途中。
如果系统在 Commit 的时候断电了,难道不会出现“半盖章”的薛定谔状态吗?
答案是:不会。因为操作系统在这里向底层硬件(硬盘)借用了一个物理级别的绝对保证——单扇区写入原子性(Single Sector Write Atomicity)。
1. Commit 的本质:只写一个扇区
在 xv6 中,commit在底层代码中其实仅仅对应一个极其微小的动作: 把一个数字(即这个事务包含的脏块数量,比如 3),写到磁盘日志区的第 1 个物理块(Log Header)里。
这个操作的数据量极小,通常远远小于 512 字节(一个标准磁盘扇区的大小)。这一点是整个文件系统能保命的终极前提! 如果 Commit 需要写两个不同的物理块,那系统就彻底完蛋了。
2. 硬件的终极承诺:全有或全无 (All-or-Nothing)
现代硬盘(无论是机械硬盘 HDD 还是固态硬盘 SSD)在设计时,都在物理层面提供了一个强保证:向一个扇区(512 字节或 4KB)写入数据,是一个不可分割的“原子物理过程”。
这意味着,当磁盘的磁头正在向 Log Header 这个扇区写入数据时,如果突然被拔掉电源,物理层面只会出现两种极其明确的结果:
-
结果 A(写进去了): 哪怕是在断电前最后一微秒,只要磁盘主控宣布这个扇区写完了,那就是彻底写完了。重启后,系统一读,发现数字是 3。系统判定:已提交,执行重播(Replay)。
-
结果 B(没写进去 / 写坏了): 如果断电刚好卡在磁头刚写了 10 个字节的时候。此时扇区里的数据是残缺的、混乱的。当你重启系统去读这个 Log Header 时,磁盘内部的 ECC 校验(错误检查和纠正) 会立刻发现这个扇区的数据校验码对不上,硬件直接报错;或者系统读出来一堆毫无意义的乱码垃圾,根本不是合法的标记。系统判定:这根本不是一个有效的盖章,当作 0 处理。文件系统毫发无损,草稿被直接丢弃。
所谓的原子级的物理过程,并不是和“速度极快(耗时极短)”划了等号。为什么?
如果是一块机械硬盘(HDD),物理过程是这样的:
-
磁盘的马达把盘片转到特定的角度。
-
磁头摇臂移动到特定的磁道。
-
磁头通电,产生极微小的磁场,去改变盘片上那 512 字节区域内的磁极方向(N极/S极代表 0/1)。
这个物理过程极其缓慢,通常需要几个毫秒(几百万个 CPU 时钟周期)。 在这几毫秒里,发起 Commit 的那个进程在睡觉(Sleep) 操作系统早就知道这事儿很慢,所以调用底层的 virtio_disk_rw 时,当前进程就会交出 CPU,直到磁盘主控芯片发来一个硬件中断:“大哥,磁极改变完了”,进程才会被唤醒。
既然改磁极这么慢,如果在改写那 512 字节的过程中(比如刚写了 100 字节)断电了怎么办?
软件层面的“瞬间(原子性)”,并不是靠物理设备跑得快来实现的,而是靠底层硬件的“自毁/校验机制”兜底的。
这个兜底机制叫 ECC(Error Correction Code,错误校验码)。
在硬盘的物理扇区中,512 字节其实不是真正的大小。硬盘厂商在出厂时,给每个扇区后面都偷偷加了一段隐藏的空间(比如额外 64 字节),用来存这 512 字节数据的复杂数学校验和(ECC 签名)。
硬件控制器向操作系统提供了一个极其强硬的物理承诺:
-
不成功,便成仁: 磁盘主控在写那 512 字节数据时,必须把配套的 ECC 签名也写进去。
-
如果断电卡在中间: 扇区里的磁极是一团糟的(一半新,一半旧)。
-
重启后的审判: 当操作系统重启,去读取这个 Log Header 扇区时。磁盘主控内部的硬件电路会先计算读出来的数据,然后和后面的 ECC 签名对账。因为断电卡在中间,账绝对对不上!
-
硬件级报错: 磁盘主控根本不会把那团糟的数据交给内存,而是直接向 CPU 甩出一个硬件级错误信号(Hardware Read Error / Unrecoverable Sector)。
-
操作系统的判断: 操作系统收到读取错误,或者读出一团乱码,直接判定:“这根本不是一个合法的盖章!当做没发生过,草稿作废!”
总结疑问:
-
是什么东西改变了? 是磁盘盘片上的磁极(HDD)或浮栅晶体管里的电子(SSD)。这种东西只有两种状态,所以是原子的,本质是软件系统层面的原子操作就是把不确定性状态给收敛到了物理层面。
-
是什么东西有如此之快的变化速度? 没有任何物理过程是极快的! 物理过程很慢,操作系统乖乖等了很久。
-
那靠什么保证安全? 靠的是磁盘控制器的“读校验拒绝”。它把一个“缓慢的时间过程”,在逻辑上变成了一个“要么 0 要么 1”的结果。只要没写完,读出来就是报错,等同于没写。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)