本篇文章将向大家介绍riscv-xv6中的文件系统的设计和实现思路,在操作系统中,文件是一个更宽泛的概念,其包含对多种资源的抽象,如外围交互设备,存储设备以及网络等,这篇文章将简要介绍我们日常使用的狭义的文件系统(file system)的实现机制。
磁盘的物理结构和读写逻辑结构的理解,与硬件物理结构有关的有设备号,磁道,扇区,柱面等,与读写逻辑结构有关的有设备号和块号(blockno),关于磁盘的物理结构和读写逻辑结构以及读写方法可以参考[1]。
缓冲区缓存(buffer cache,在kernel/bio.c中实现)是为磁盘访问提供缓存的实现,可以提高磁盘读写的效率。LRU采用了双向循环链表进行的实现,完整实现思路可以参考[2],但在riscv-xv6的实现中没有用到哈希函数,而是采用遍历的方式进行的查找,riscv-xv6中的具体的实现参考[3],当前的head节点为最近访问的节点(哨兵节点),head节点的前驱节点为最久未使用的节点,而后继节点为离当前head最近时间使用过的节点,依此类推。在缓冲区的实现代码中,bget函数用于根据dev和blockno来获取磁盘缓冲区,如果已经在缓存区中缓存,则引用计数加一并用睡眠锁互斥访问;如果在缓冲区中没有,则从LRU中找到离当前最久的未使用的缓冲区并通过睡眠锁实现互斥访问。bread和bwrite是实现磁盘块读写的函数,其中都是基于缓冲区的buf(和磁盘块对应)作为媒介,在riscv-xv6中磁盘块读操作通过函数virtio_disk_rw(b, 0)将磁盘内容读到缓冲区,磁盘块写操作通过virtio_disk_rw(b, 1)函数将缓冲区的内容写到磁盘里,整个磁盘缓冲区以及块读写的实现参考[3]。
inode(索引节点)是 UNIX 文件系统(如 ext4、xfs)中用于描述文件元数据的结构体,它存储了文件的关键信息, inode和dinode结构体中addr数据项用来存放硬盘中具体数据的位置,详细的解释可以参考[4],整个文件的读写的实现见kernel/fs.c代码文件。在riscv-xv6中,当前运行的活动(active)的inode存放在itable结构里,itable没有实现类似buffer cache这样的LRU访问功能,inode 只有在 ref == 0 时才会被回收,但没有 LRU 淘汰策略。只要 ref > 0,inode 就不会被回收,即使它是最久未使用的 inode。
问题1:inode中的dev和inum和major和minor的关系,dinode中为什么不需要dev和inum?回答可以参考[5]中的结构体的字段的定义作用和对比分析。
问题2:inode加载到内容之后,readi去读取时还是调用到了bread,为什么会这样?回答:inode 是文件在内存中的表示,而 dinode(磁盘上的 inode)是文件在磁盘上的存储形式。它们的结构类似,但 dinode 是存储在磁盘上的,而 inode 是加载到内存中的表示,在dinode数据项的基础之上多了dev和inum以及睡眠锁等。inode 结构不存储文件内容,只包含文件的元信息(例如 size、addrs)。ilock函数会调用bread函数读取inode数据元数据。数据块号 (addrs[]) 是指向实际文件内容的磁盘块,但数据仍然需要 通过调用bread() 读取。inode索引节点以及文件本身数据读写操作实现参考[6]。
问题3:riscv-xv6系统中的文件目录结构组织方法介绍,xv6中文件路径如何和inode关联?回答:riscv-xv6中,文件或目录的结构体数据为dirent,包括名称和inum,inum=0表示目录。在磁盘上,一个目录的inode通过 inode->addrs 指向其数据块,而数据块的内容就是多个dirent 结构体的数组。该数组的每一项表示目录下的文件或子目录。整个文件系统中超级块保存了文件系统的结构,具体信息参考[7]。
问题4:riscv-xv6中读文件的过程中涉及哪些关键函数的调用路径,关键函数的实现机制是怎样的?回答:可以简要概括为sys_read->fileread->readi->bread等,其中sys_read为系统调用的入口,fileread为sys_read判断当前为读文件操作后调用的函数,readi为读取indoe节点的文件数据,其中首先会通过ilock函数实现inode节点的读取(如果inode节点不在内存,也需要通过bread从磁盘读取dinode数据到inode节点里),然后通过inode节点中的addr信息去调用bread函数读取文件的具体数据块内容。
问题5:sys_open打开文件如何实现文件名到inode的映射,具体调用了哪些关键函数路径?回答:主要通过namei,namex,dirlookup等关键函数实现文件路径到inode的映射,会调用iget从itable中获取inode节点。sys_read只读取已经打开的file结构体文件,ilock的作用表现在同一路径的文件被多个进程加载,实现数据的互斥访问(并且如果dinode没有加载,则通过bread等函数实现从磁盘加载对应的dinode数据到inode结构体中)。更多关于文件读写的相关说明见[8]。
References
- [1]、磁盘物理和逻辑结构以及读写方法:https://gitee.com/kindlytree/riscv-xv6/issues/IBK79P#note_36965587_link
- [2]、LRU缓存设计实现思路说明
- [3]、缓冲区缓存的实现: https://gitee.com/kindlytree/riscv-xv6/issues/IBK79P#note_36956738_link
- [4]、inode和dinode结构体中addr数据项的解释:https://gitee.com/kindlytree/riscv-xv6/issues/IBK79P#note_37004525_link
- [5]、inode和dinode中的字段的定义、作用和对比分析:https://gitee.com/kindlytree/riscv-xv6/issues/IBK79P#note_37005362_link
- [6]、inode索引节点元数据以及文件内容数据读写操作实现:riscv-xv6中文件系统的设计和实现 · Issue #IBK79P
- [7]、文件系统结构分析: https://gitee.com/kindlytree/riscv-xv6/issues/IBK79P#note_37018806_link
- [8]、关于文件结构体,文件描述符,inode结构体以及open,read等系统调用的相互关系说明:https://gitee.com/kindlytree/riscv-xv6/issues/IBK79P#note_37145137_link
Leave a Reply