mit-6.828 - Chapter 8: File System

  1. xv6 chapter8: File System
    1. Buffer Cache Layer

xv6 chapter8: File System

文件系统的目的是组织和存储数据,这里的存储数据是指将用户和应用的数据持久化到磁盘以供任何其它时候使用。
xv6提供了Unix-like的文件,目录和相关的操作,作为单机使用的文件系统,通常需要解决的问题包括:

  • 如何在磁盘上组织数据结构,数据在用户使用过程中和在磁盘上的存储形态可以是不同的,磁盘上的组织结构常常需要考虑如何进行高效率的IO
  • 必须支持crash-recovery,如果系统因为断电,故障等导致文件系统突然被强行挂掉,当系统重启时文件系统需要能恢复到一个一致性的状态
  • 并发控制,多个进程可能同时操作同一文件,文件系统需要能在这种情况下保持一致性
  • 磁盘的IO比起内存非常慢,需要组织内存缓冲提高访问速度

xv6通过如下的分层设计来解决上述问题:

xv6文件系统分层设计

简要描述各层功能:

  • Disk Layer: 负责磁盘的读写,提供一个抽象的接口供上层使用,屏蔽了磁盘的物理细节,由于是使用qemu模拟的磁盘,相关操作是对qemu模拟磁盘的操作,实现在kernel/virtio_disk.c中,主要为上层buffer提供了virtio_disk_rw函数来进行读写操作

  • Buffer Cache Layer: 缓冲的手段之一,这层维护一个缓冲池,池中每个buffer对应了磁盘上的一个块(block),当上层需要访问某个块时先在缓冲池中查找,如果没有则从磁盘上读入到缓冲池中,之后的访问就直接访问缓冲池中的数据,直到需要将数据写回磁盘时才会调用disk layer的接口,这样就减少了对磁盘的访问,提高了效率,实现在kernel/bio.c中,主要提供了bread,bwrite,brelse等函数供上层调用

  • Logging Layer: 用于保证文件系统的crash-recovery能力,xv6使用了WAL(Write-Ahead Logging)的机制来实现这个功能,WAL的核心思想是将所有对文件系统的修改先记录到一个日志中,只有当日志被安全地写入磁盘后才会真正修改文件系统,这样当系统崩溃时可以通过重放日志来恢复到一个一致性的状态,实现在kernel/log.c中,主要提供了log_write函数供上层调用,上层模块的操作对文件系统的修改都需要调用这个函数来记录日志

  • Inode Layer: 负责将文件系统的抽象概念inode映射到磁盘上的数据结构,提供了对inode的操作接口,实现在kernel/fs.c中,主要提供了ialloc,iupdate,ilock等函数供上层调用,一个inode就对应了一个文件,但它本身不含名字,它主要关注一个文件应该包含哪些元信息以及文件内容如何在磁盘上组织,文件名是由目录层来管理,这样就实现了文件名与文件内容的分离,使得不同的文件名可以指向同一个文件内容

  • Directory Layer: 负责管理文件系统中的目录结构,提供了对目录的操作接口,实现在kernel/fs.c中,主要提供了dirlookup,dirlink等函数供上层调用,目录是一个特殊的文件,它包含了文件名与对应inode的映射关系,这样用户就可以通过文件名来访问文件内容

  • Pathname Layer: 负责解析用户输入的路径,找到对应的inode,提供了对路径解析的接口,实现在kernel/fs.c中,主要提供了namei,nameiparent等函数供上层调用,用户在使用文件系统时通常会通过路径来访问文件,这层的功能就是将路径解析成对应的inode,以供上层使用

  • File Descriptor Layer: 负责将文件抽象成文件描述符,这是用户程序与文件系统交互的接口,提供了对文件描述符的操作接口,实现在kernel/file.c中,主要提供了filealloc,fileclose等函数供上层调用,文件描述符是一个整数,它代表了一个打开的文件,用户程序通过文件描述符来读写文件内容,这层的功能就是管理这些文件描述符以及它们对应的文件

上面是xv6在逻辑概念上的文件系统设计,文件系统还需要对磁盘的物理布局进行设计,xv6的磁盘布局如下图所示:

xv6磁盘布局

下面重点说下几个重要的层的设计与实现:

Buffer Cache Layer

这一层的核心职责包括两个:

  1. 保证内存里只有一份相应块的副本,同时保证只有一个内核线程能访问该副本
  2. 提供一个接口对上层使用,上层模块只需要这个接口对磁盘块进行读写,无需关心这个块在内存还是磁盘上,且多个访问会被正确的序列化。

实现细节
整个buffer cache layer的核心数据结构是一个buffer的双向链表,链表每个节点就是一个buffer,每个buffer对应磁盘上的一个块,包含了该块的内容以及一些状态信息,比如是否被修改过,是否正在被访问等。为了保证并发访问的正确性,每个buffer还维护了一个锁,当一个线程要访问某个buffer时需要先获取该buffer的锁,这样就保证了同一时间只有一个线程能访问该buffer。

由于要支持同一个buffer被多个线程访问,buffer一方面设置了一个锁来保证同一时间只有一个线程能访问。外层整个链表有一个大锁,这个大锁用来控制对链表的修改和对buffer元信息的修改,这些属于链表的信息。每个buffer的refcnt用来记录当前buffer有多少线程正在访问,这里的目的是为了不让bget收回一个正在被使的用buffer。如果没有refcnt,bget无哪些buffer还在


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 2128099421@qq.com

×

喜欢就点赞,疼爱就打赏