XV6 中的文件,即 file 结构体,是对磁盘文件 inode
的一次动态抽象和封装。对 file
结构体而言,文件内容是一个一维的字节数组,可以用一个简单的偏移量而访问到文件中的任意数据。向上与进程的文件描述符表建立联系,使得进程对自己所打开的文件使用一个简单的索引值来识别。向下与磁盘文件 inode
对象相关联,以便可以读写文件中的数据(定位数据所在的磁盘盘块号)。file
抽象出的线性字节序列的文件和磁盘上乱序存储的盘块的联系,是通过 inode
的索引地址进行映射的。
文件 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
,而磁盘上的则可以是各种具体不同的如 ext2
的 dinode
、xfs
的 dinode
等。
各进程内部使用一个 “文件描述符表” 来管理自己所打开的文件,而 “已打开文件列表” ftable 是全系统已打开文件的列表。前者是一个简单的引用,指向一打开文件的的 file
结构体, 后者则是 file
结构体自身。多个文件描述符可能指向同一个已打开文件,例如终端设备会被多个程序所使用。
在 file.h 的一开头就定义了 file
结构体,用于一个动态概念的已打开文件。
虽然设备都抽象为设备文件,但是各自的读写操作细节是不同的,因此使用了 devsw[] 数组来记录给出的读写操作函数,用设备号来索引。从这里可以看出 XV6 的设备操作非常简陋, 只有读写操作,而没有 Linux 设备中的 ioctl()
等操作。
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_PIPE
、FD_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 个文件。