Unix文件系统有多种实现,下文主要讨论基于BSD的UFS(UNIX File System)。
一般硬盘被分为多个分区,每个分区可以包含一个文件系统。文件系统的结构一般如下:
自举块 | 超级块 | 柱面组0 | 柱面组1 | …… | 柱面组n |
超级块副本 | 配置信息 | i-node图 | 块位图 | i-node块 | 目录块和数据块 |
在目录块中或者说目录文件的目录项中存放了(i-node编号, 文件名)映射,据此可以找到i-node块中的某个i-node,而i-node中存放了文件信息:文件所有者、文件类型、文件访问权限位、文件长度、该文件所占用的数据块(可能有多个)的指针等,据此就可以访问磁盘上存放文件内容的数据块了。
在同一个目录块中或者多个目录块中可以有多个(i-node编号,文件名)映射的i-node编号相同而文件名不同,即指向同一个i-node,这意味着可以有多个路径上的文件名指向磁盘上的同一个文件。每个i-node中有一个引用计数,其值是指向该i-node的目录项个数。只有当链接计数减少为0时,才会删除该文件即释放该文件占用的数据块。这种链接类型称为hard link。这就是为什么删除一个目录项的函数被称为unlink而不是delete,这也是为什么“解除对一个文件的链接”操作并不总是会“释放该文件占用的磁盘块”。
与此相关的是symbol link。符号链接实际上是一个文件,文件的实际内容(数据块中内容)存放了该符号链接所指向的文件路径。在i-node中符号链接的文件类型是S_IFLNK,系统据此知道这是一个符号链接。
每个分区的文件系统各自对它们的i-node进行编号,互不影响。因此没法使一个目录项指向另一个文件系统的i-node。这就是为什么ln命令不能跨越文件系统。
对于目录,因为目录文件中包含了当前目录“.”和父目录“..”。因此一个目录文件myDir的i-node有(2 + n)个链接计数:父目录文件中(i-node编号, myDir),myDir文件中(i-node编号, 当前目录"."),n个子目录文件中的(i-node编号, 父目录"..")。
内核使用3种数据结构表示打开的文件:
(1) 每个进程在进程表中都有一个打开文件的描述符表,该表中每一项的内容是(文件描述符,指向打开文件表中某一文件项的指针)
(2) 内核为所有打开的文件维护一张打开文件表,其中每一项的内容是:(文件状态标志, 当前文件偏移量,指向该文件v-node的指针)
(3) 每个打开的文件(设备也是文件)都有一个v-node结构,v-node包含了文件i-node信息,这些信息是在文件打开时从磁盘上读入内存的,因此文件的相关信息是可以快速访问的。
看出上面的关联了么?进程中打开文件描述符表 --> 打开文件的文件表某一项 --> v-node --> i-node。找到文件的i-node就可以访问文件内容了。
创建v-node结构是为了对在一个计算机系统上的多文件系统类型提供支持,Sun称这种文件系统为Virtual File System,称与文件系统类型无关的i-node信息分出来作为v-node。Linux没有将相关数据结构分为i-node和v-node,而是采用了一个独立于文件系统的i-node(通用i-node结构)和一个依赖于文件系统的i-node。不过一些文章中仍然将Linux通用i-node称为vnode。