xv6 文件系统分为7层:
- File Descriptor
- 用于抽象 Unix 系统资源(管道,设备,文件等)
- Pathname
- 提供层级式的路径名
- Directory
- 每个目录被实现为一种特殊的 inode
- 包含一组目录项,每项包含一个文件名和 i-number
- Inode
- 提供单独的文件, 每个文件由一个 inode 表示
- 每个 inode 有唯一的 i-number 和一些 blocks 来存储文件的数据
- Logging
- 允许更高层级将针对多个 blocks 的更新打包进一个 transaction
- 并确保一个 transaction 内的块都会被原子地更新(确保崩溃一致性)
- Buffer Cache
- 缓存来自 disk 的 blocks ,并和 disk 同步对于他们的访问
- 确保一次只有一个内核进程可以修改任何 block 上的数据
- Disk
- 在 virtio 硬件驱动上读写 blocks
Disk
512 字节的 block 所组成的标号序列的设备
操作系统使用的 block size 和 disk 实际实现的 block size 可能不同(但通常情况下一致)
xv6 将读入内存的 block 放入结构体 struct buf
inode block 和 content block 在 Disk 中的存储
xv6 disk 分为几个 sections:
- block 0: boot sector
- block 1: superblock 包含文件系统元数据
- 文件系统大小(blocks), data blocks 数量, inodes 数量, log blocks 数量
- 通过 mkfs 程序写入
- block 2-m : log blocks
- block m-n : inode blocks
- 每个 block 内有多个 inodes
- block n-x : bitmap blocks 追踪哪些 data blocks 被使用
- block x-y : data blocks
- 每个 data block 都在 bitmap 中被标记: free or hold
Buffer Cache
两个任务:
- 同步对于 disk blocks 的访问,确保每个 block 在内存中只有一个 copy, 且一次只有一个内核线程可以使用该 copy
- 缓存常用 blocks, 不需要反复从磁盘读 (功能同 cache 类似)
接口:
- bread: 获取一个 buf 结构体(slepp-locked),包含相应 block 的一个 copy, 可以直接在内存中读写
- bwrite: 将一个修改过的 buf 写入 disk 中相应的位置
- brelse: 释放 buf (内核线程在 bwrite 后必须调用 brelse)
每个 buf 有一个 sleep-lock 来确保每次只有一个内核线程可以访问 buf
实现 in kernel/bio.c
buffer cache 实现为所有 buffer 的双端链表
每个 buffer 有2个状态域,valid 表明该 buffer 是否持有一个 block 的 copy, disk 表明 buffer 的内容已经被提交给 disk
- binit(): 将所有 buffer 组成的数组初始化为双端链表,之后所有对 buffer 的访问均通过
bcache.head
访问而非静态数组buf
- bread(): 调用
bget()
从给定 sector 获取一个 buffer- 如果未找到,则从 disk 中读取 buffer:
virtio_disk_rw(b, 0)
- bget(): 扫描 buffer 链表
- 如果找到 buffer ,则给 buffer 上锁(sleep-lock)并返回
- 如果未找到 buffer ,则选择一个未使用的 buffer 替换掉(refcnt == 0),同时标记 valid = 0
- 如果没有找到未使用的 buffer,则 panic (说明系统中有太多的进程同时运行, 也可以 sleep 进程等待 buffer 可用)
- 如果未找到,则从 disk 中读取 buffer:
- bwrite(): 调用
virtio_disk_rw(b, 1)
将修改过的 buffer 写入 disk - brelse(): 释放 sleeplock 并将 buffer 加入到 buffer 链表的前端(为了使未使用的 buffer 尽可能在链表前端, 以减少链表扫描的时间)