0%

File System in xv6

xv6 文件系统分为7层:

  1. File Descriptor
    • 用于抽象 Unix 系统资源(管道,设备,文件等)
  2. Pathname
    • 提供层级式的路径名
  3. Directory
    • 每个目录被实现为一种特殊的 inode
    • 包含一组目录项,每项包含一个文件名和 i-number
  4. Inode
    • 提供单独的文件, 每个文件由一个 inode 表示
    • 每个 inode 有唯一的 i-number 和一些 blocks 来存储文件的数据
  5. Logging
    • 允许更高层级将针对多个 blocks 的更新打包进一个 transaction
    • 并确保一个 transaction 内的块都会被原子地更新(确保崩溃一致性)
  6. Buffer Cache
    • 缓存来自 disk 的 blocks ,并和 disk 同步对于他们的访问
    • 确保一次只有一个内核进程可以修改任何 block 上的数据
  7. Disk
    • 在 virtio 硬件驱动上读写 blocks

Disk

512 字节的 block 所组成的标号序列的设备

操作系统使用的 block size 和 disk 实际实现的 block size 可能不同(但通常情况下一致)

xv6 将读入内存的 block 放入结构体 struct buf

inode block 和 content block 在 Disk 中的存储

xv6 disk 分为几个 sections:

  1. block 0: boot sector
  2. block 1: superblock 包含文件系统元数据
    • 文件系统大小(blocks), data blocks 数量, inodes 数量, log blocks 数量
    • 通过 mkfs 程序写入
  3. block 2-m : log blocks
  4. block m-n : inode blocks
    • 每个 block 内有多个 inodes
  5. block n-x : bitmap blocks 追踪哪些 data blocks 被使用
  6. block x-y : data blocks
    • 每个 data block 都在 bitmap 中被标记: free or hold

Buffer Cache

两个任务:

  1. 同步对于 disk blocks 的访问,确保每个 block 在内存中只有一个 copy, 且一次只有一个内核线程可以使用该 copy
  2. 缓存常用 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 可用)
  • bwrite(): 调用 virtio_disk_rw(b, 1) 将修改过的 buffer 写入 disk
  • brelse(): 释放 sleeplock 并将 buffer 加入到 buffer 链表的前端(为了使未使用的 buffer 尽可能在链表前端, 以减少链表扫描的时间)