XV6

XV6 中的文件,即 file 结构体,是对磁盘文件 inode 的一次动态抽象和封装。对 file 结构体而言,文件内容是一个一维的字节数组,可以用一个简单的偏移量而访问到文件中的任意数据。向上与进程的文件描述符表建立联系,使得进程对自己所打开的文件使用一个简单的索引值来识别。向下与磁盘文件 inode 对象相关联,以便可以读写文件中的数据(定位数据所在的磁盘盘块号)。file 抽象出的线性字节序列的文件和磁盘上乱序存储的盘块的联系,是通过 inode 的索引地址进行映射的。

1. 文件 & 索引节点

文件 file 特指磁盘文件的一次动态打开,是动态概念。而 inode 代表的磁盘文件则是静态概念。每一此打开的文件都是用 file 结构体来描述, 主要记录该文件的类型、引用数、读写模式,如果是管道则由 pipe 成员指向管道数据,file->ip 指向内存中的 inode(是磁盘 dinode 在内存的动态存在)。off 用于记录当前读写位置,通常称作文件的游标。

磁盘文件 dinode 作为静态内容,被打开之后在内存中创建一个 inode 动态对象,其主要内容来源于磁盘的的 dinode。例如 inode 需要有 inum 记录 inode 号,而磁盘上则每个 dinode 都唯一对应一个 inode 号。inode 的结构体后半部分成员,和磁盘上的 dinode 完全一致,直接从磁盘读入即可。内存中的 inode 对象由 icache 缓冲区管理,内含 inode[NINODE] 数组,其中 NINODE=50

也就是说 XV6 文件系统 和 Linux 类似,有内存 inode 和相对应的磁盘 dinode。Linux 则是内存 inode 是 VFS 的抽象 inode,而磁盘上的则可以是各种具体不同的如 ext2dinodexfsdinode 等。

1. 已打开文件列表 & 文件描述符表

各进程内部使用一个 “文件描述符表” 来管理自己所打开的文件,而 “已打开文件列表” ftable 是全系统已打开文件的列表。前者是一个简单的引用,指向一打开文件的的 file 结构体, 后者则是 file 结构体自身。多个文件描述符可能指向同一个已打开文件,例如终端设备会被多个程序所使用。

file.h 的一开头就定义了 file 结构体,用于一个动态概念的已打开文件。

虽然设备都抽象为设备文件,但是各自的读写操作细节是不同的,因此使用了 devsw[] 数组来记录给出的读写操作函数,用设备号来索引。从这里可以看出 XV6 的设备操作非常简陋, 只有读写操作,而没有 Linux 设备中的 ioctl() 等操作。

3. 文件对象的操作

fileinit() 用于对已打开文件列表 ftable[] 进行初始化,即初始化 ftable.lock 自旋锁。

filealloc() 分配一个空闲的 file 对象。它扫描 ftable.file[],如果发现未使用(即 ref==0),则将其标记为已分配使用(ref=1),并返回该 file 指针。

当用户进程对某个文件描述符进行复制时,将引起对应的 file 对象引用次数增 1,因此 filedup() 就是将制定的 file 对象的 file->ref ++

fileclose() 要考虑多个进程打开文件的情况,因此如果不是最后一个使用该文件的进程,只需要简单地将 file->ref-- 即可。否则,将完成真正的关闭操作,对于 PIPE 管道将使用 pipeclose(),对其他文件则通过 iput() 释放相应的索引节点。

filestat() 用于读取文件的元数据(meta data),它通过 stati() 将元数据管理信息填写到 stat 结构体中。

## 3.1 文件读写

fileread() 是通用的文件读操作函数,它内部再根据文件类型(FD_PIPEFD_INODE)区分执行 piperead()readi() 两种操作。对于磁盘文件,需要定位数据偏移所对应的磁盘盘块所在的位置,这个就必须通过 readi() 来完成。

stat.h 中定义了三种文件类型 T_DIR 目录、T_FILE 普通文件和 T_DEV 设备文件。其次是 stat 结构体。

file.c 涉及文件系统中关于 file 层面的操作,上接系统调用代码的接口层面的代码,向下衔接 inode 操作层面的操作代码。

系统中所有已经打开的文件记录在 ftable 中,由于 ftable.file[NFILE]NFILE=100,因此 XV6 系统最多允许打开 100 个文件。