文件是什么

文件是一组有意义的信息或数据集合。用户通常通过文件名、路径和应用程序来使用文件;操作系统则需要进一步记录文件的属性、外存位置、访问权限和打开状态。

常见文件属性:

属性 含义
文件名 由用户创建,便于用户查找;同一目录下通常不允许重名
标识符 系统内部唯一标识,对用户不要求可读
类型 指明文件类型,如文本、图片、目录、链接文件等
位置 用户看到的是路径;操作系统还要记录文件在外存中的位置
大小 文件当前占用的数据量
时间 创建时间、上次修改时间等
所有者 文件所属用户或用户组
保护信息 访问权限、口令、加密信息等

文件系统要解决的问题

文件管理不是只保存“文件内容”。它至少要回答五类问题:

  1. 文件内部的数据如何组织。
  2. 文件之间如何组织成目录树。
  3. 用户和应用程序如何创建、打开、读写、关闭、删除文件。
  4. 文件数据如何落到外存的磁盘块上。
  5. 如何实现共享、保护、空闲空间管理和设备 I/O。

1000

从路径名到磁盘块的过程通常会经过多层:

层次 主要任务
用户接口 处理 createopenreadwriteclosedelete 等系统调用
文件目录系统 根据路径查找目录项、FCB 或 inode
存取控制模块 检查用户是否有访问权限
逻辑文件系统与文件信息缓冲区 将记录号、文件偏移转换为逻辑地址
物理文件系统 将逻辑块号转换为物理块号
设备管理模块 进行磁盘调度、启动设备、完成块读写
辅助分配模块 分配和回收外存空闲块

其中,目录、FCB、inode 和分配结构属于文件系统自己的管理对象;真正启动磁盘读写时,会继续下沉到 磁盘调度与磁盘管理I/O 控制方式 和设备驱动程序。也就是说,文件系统决定“读哪个文件块”,I/O 子系统负责“怎样让设备把这个块传到主存”。

文件的基本操作

文件操作看起来是在操作“文件内容”,但操作系统实际处理的是三类对象:

  • 目录项、FCB 或 inode:记录文件名、属性、权限、外存位置等元数据。
  • 打开文件表:记录本次打开的状态,例如读写指针、访问模式、打开计数。
  • 文件数据块:真正保存文件内容的外存块。

因此,openclose 不是把整个文件搬入或搬出内存。它们主要建立或撤销访问文件所需的内存数据结构。真正把文件内容读入内存的是 read、缺页调入,或其他 I/O 过程。

创建文件

创建文件时,用户通常给出文件路径、文件名和初始属性。操作系统需要:

  1. 根据路径找到目标目录。
  2. 检查同一目录下是否已有同名文件。
  3. 在目录中建立目录项、FCB 或 inode。
  4. 记录文件名、类型、权限、时间、所有者等属性。
  5. 必要时为文件分配初始外存块;若创建的是空文件,也可以暂不分配数据块。

创建文件的核心是建立文件名到文件控制信息的映射。它不一定立即写入大量文件数据。

删除文件

删除文件时,用户通常给出路径和文件名。操作系统需要:

  1. 根据路径找到目标目录文件。
  2. 找到文件名对应的目录项。
  3. 检查删除权限。
  4. 根据目录项、FCB 或 inode 记录的物理位置回收文件占用的磁盘块。
  5. 删除目录项,并更新空闲空间管理结构。

删除文件通常表示文件系统不再把这些磁盘块视为该文件所有;它不等于立刻把磁盘上的每个字节清零。

打开文件

系统要求读写前先 open。打开文件时,操作系统会查找目录项、检查权限,并把文件相关信息复制到内存中的打开文件表。

1000

打开后,系统返回一个编号,也就是文件描述符。之后 readwrite 用文件描述符指明文件,不必每次重新用路径查目录。

Important

打开文件并不是把文件数据全部读入内存open 主要把目录项、FCB 或 inode 中的必要信息调入内存,并建立进程打开文件表和系统打开文件表。

一次 `open` 通常会形成两级记录:
结构 保存内容
进程打开文件表 文件描述符、访问模式、当前读写指针、指向系统打开文件表的指针
系统打开文件表 文件外存位置、文件属性、打开计数、指向 inode 或 FCB 的指针

若多个进程打开同一个文件,系统打开文件表中的同一项可以被多个进程表项引用。打开计数用来判断最后一次关闭何时发生。

读文件

read 通常需要指明:

  • 文件描述符。
  • 要读入的数据量。
  • 数据放入内存的目标地址。

操作系统根据文件描述符找到进程打开文件表,再找到系统打开文件表和文件物理结构。随后根据当前读写指针计算逻辑块号,再把逻辑块号转换成物理块号,从外存读入用户指定的内存区域。

读完后,读写指针通常向后移动。若读到文件末尾,实际读入的数据量可能小于请求的数据量。

写文件

write 通常需要指明:

  • 文件描述符。
  • 要写出的数据量。
  • 数据在内存中的来源地址。

操作系统从用户指定的内存区域取数据,写到当前读写指针对应的文件位置。若写入超过文件原有长度,文件可能需要扩展;这时文件系统还要分配新的磁盘块,并更新 FCB、inode、索引表、FAT 或其他分配结构。

写操作还会影响一致性问题。若数据先进入缓冲区,磁盘上的文件内容可能稍后才真正更新。

mmap

readwrite 是显式文件 I/O。mmap 则把文件区间映射到进程虚拟地址空间,程序用普通内存访问触发文件页调入或写回。它仍然依赖文件描述符建立映射,但映射建立后,访问路径进入虚拟存储和缺页处理流程。

关闭文件

进程使用完文件后执行 close。操作系统会:

  1. 删除该进程打开文件表中的对应表项。
  2. 回收与本次打开相关的内存资源。
  3. 将系统打开文件表中的打开计数减 1。
  4. 若打开计数变为 0,释放系统打开文件表中的对应表项。

close 关闭的是本进程的这次打开关系。若其他进程仍打开同一个文件,文件不会因为当前进程执行 close 而不可用。

`close` 没有将文件从内存写回外存

普通 open 本来就不会把整个文件读入内存,所以 close 也谈不上把整个文件搬回磁盘。

那真正的写回时机是什么时候?

取决于文件系统、页缓存、缓冲区和同步策略。

时机 含义
后台回写 操作系统周期性把脏页或脏缓冲区写回外存
缓存压力 内存紧张或缓存需要回收时,脏页必须先写回才能释放
显式同步 程序调用 fsync(fd)fdatasync(fd)sync(),要求同步数据或元数据
解除映射 mmap 的共享映射,msync()、后台回写、munmap() 或进程退出都可能与脏页写回有关
卸载文件系统 卸载前需要把尚未落盘的脏数据和元数据处理完
Note

写回不只包括文件内容。文件大小、时间戳、目录项、inode、位示图、FAT、索引结构等元数据也可能被缓存并延迟写回。

# 文件共享与保护

文件共享让多个目录项、用户或进程访问同一份文件数据。常见方式包括基于 inode 的硬链接,以及保存目标路径的软链接。

硬链接通常用于普通文件,不允许普通用户随意给目录文件建立硬链接。若目录可以任意硬链接,目录结构可能出现环,路径遍历、递归删除、备份和引用计数回收都会变得不可控。目录中的 ... 是由文件系统维护的特殊目录项,不是普通用户创建的目录硬链接。

文件保护决定谁能读、写、执行或删除文件。常见方式包括口令保护、加密保护和访问控制表。更具体的共享、保护机制见 文件共享文件保护

文件系统的全局结构

磁盘成为可用文件系统通常要经历:

  1. 物理格式化:划分扇区,检测坏扇区,并用备用扇区替换坏扇区。
  2. 磁盘分区:把物理磁盘划分为一个或多个分区,也称文件卷、逻辑卷或逻辑盘。
  3. 逻辑格式化:在分区上创建文件系统,初始化目录区、文件区和空闲空间管理结构。

文件系统在外存中通常包括:

1000

区域 作用
主引导记录 MBR 与系统启动有关
引导块 保存启动相关信息
超级块或卷控制块 保存文件系统总体信息
目录结构 保存目录和目录项
文件控制结构 保存 FCB、inode 等元数据
数据区 保存文件数据块
空闲空间管理结构 记录哪些磁盘块空闲

文件系统在内存中会缓存近期使用的信息,例如目录文件、打开文件表、系统打开文件表、挂载表和文件系统控制块。目录缓存可以减少重复目录检索造成的磁盘 I/O。

1000

用户进程执行 read(fd, ...) 时,fd 先作为索引号查本进程的打开文件表;进程打开文件表再指向系统范围的打开文件表;系统打开文件表保存或指向文件控制信息,并据此定位外存中的数据块。

VFS 与挂载

不同文件系统的内部数据结构不同,但用户进程希望使用统一接口,例如 openreadwrite

VFS 是虚拟文件系统。它向上提供统一系统调用接口,向下要求具体文件系统实现规定的函数。一个新文件系统要接入操作系统,就要满足 VFS 的接口要求。

VFS 的关键作用:

  • 屏蔽不同文件系统的实现差异。
  • 要求下层文件系统提供函数地址,如 openreadwrite
  • 文件打开后,在主存中建立统一的 vnode 表示。
vnode 与 inode

vnode 只存在于主存中,用于让 VFS 统一表示已打开文件。inode 既可以在外存中保存,也可以被调入主存。

**挂载**就是把一个文件系统接入到操作系统目录树中。挂载时通常要:
  1. 在 VFS 中注册新文件系统,更新挂载表。
  2. 向 VFS 提供函数地址列表。
  3. 把文件系统挂到某个挂载点,也就是某个父目录下。