文件管理(五)—— 文件的基本操作

32 阅读6分钟

前言

文件系统提供六种基本操作:创建、删除、打开、关闭、读、写。其他复杂操作均由这些基本操作组合完成。

1. 创建文件

系统调用参数:所需外存空间大小、文件存放路径、文件名。

系统做的两件事:

  1. 根据申请的大小,用存储空间管理策略(空闲表法/位视图法等)在外存中分配磁盘块;
  2. 在指定路径的目录表中插入一条新的目录项(FCB),记录文件名、存放位置等信息。

2. 删除文件

系统调用参数:文件存放路径、文件名。

系统做的三件事:

  1. 根据路径找到对应目录表,再按文件名找到目录项,读取文件占用的磁盘块信息;
  2. 按存储空间管理策略回收文件占用的磁盘块;
  3. 删除目录表中该文件的目录项。

3. 打开文件

在大多数操作系统中,对文件进行读写之前必须先调用 open。

系统调用参数:文件存放路径、文件名、操作类型(只读 / 读写等)。

系统做的两件事:

  1. 根据路径在外存中找到目录表,检查用户的访问权限(权限信息记录在目录项中),若无权限则拒绝;
  2. 权限合法后,将目录项复制到内存的打开文件表中,并将该表项的索引号(文件描述符)返回给用户。

打开文件时不会将文件的实际数据读入内存,只是将目录项信息载入打开文件表。

打开文件表

内存中存在两张打开文件表:

系统打开文件表进程打开文件表
数量全局唯一一张每个进程一张
记录内容所有正在被进程使用的文件信息该进程已打开的文件信息
特有字段打开计数器(记录当前有几个进程打开了该文件)读写指针(记录当前读写进行到的位置)、访问权限(该进程打开时申请的操作类型)

进程打开文件表中包含一个指向系统表对应表项的索引号,多个进程打开同一文件时各自持有独立的读写指针和权限,但共享系统表中的同一条记录。

打开计数器的作用:系统在删除文件前会先查系统打开文件表,若打开计数器 > 0(有进程正在使用),则拒绝删除(即 Windows 中「文件正在被占用,无法删除」的情形)。

为什么 read 之前需要先 open

1. 性能:setup 一次,操作多次

open 做的路径解析、权限检查、inode 查找是昂贵操作,只需执行一次。若合并进 read,读 1GB 文件分 1024 次就要解析 1024 次路径。

2. 状态持久:fd 携带偏移量等上下文

open 创建的 struct file 记录当前偏移量、访问模式、标志位(O_APPEND 等)。流式读取 while(read()) 依赖偏移量在多次调用间自动推进。合并后偏移量无处安放,无法接着上次的位置继续读。

3. 一开多用:fd 上不止 read

同一个 fd 可执行 read、write、lseek、fstat、mmap、flock 等多种操作。合并意味着每种操作都要重复路径解析逻辑,open_read、open_write、open_seek……反而更复杂。

4. 资源生命周期清晰

fd 是有边界的资源:open 创建、close 释放,支持引用计数和 dup/fork 共享,进程退出自动回收。read 是瞬时操作,不创建也不销毁资源,把资源管理揉进去会破坏生命周期模型。

5. 权限检查时机明确

经典 Unix 在 open 时检查一次权限,后续 read 不重复检查——"检查一次,多次使用",兼顾安全与性能。合并后语义模糊。

6. 合并不会真的变简单

若在 read(path) 内部缓存解析结果,就需要带生命周期、引用计数、状态的对象来装它——这正是 fd 机制本身,等于重新发明一遍。

7. 这是 OS 通用范式

open→操作→close 是"建立会话→反复操作→释放"的通用模式,socket、数据库连接、GPU context 皆如此。文件只是其中一例。

8. 便利封装留给上层库

"一步读小文件"的需求由库兜底:Go 的 os.ReadFile、Python 的 pathlib.read_text(),内部仍是 open+read+close。内核保持分离,兼顾通用性与性能。

4. 关闭文件

系统做的三件事:

  1. 删除进程打开文件表中该文件对应的表项,回收相关内存缓冲区等资源;
  2. 将系统打开文件表中对应表项的打开计数器减 1;
  3. 若打开计数器减为 0,说明无进程再使用该文件,则删除系统打开文件表中的该表项。

5. 读文件

读写操作必须在文件已打开的前提下进行。

系统调用参数:文件描述符(打开文件表索引号)、读入数据量(如 1 KB)、目标内存地址。

系统操作:根据进程打开文件表中的读写指针找到外存对应位置,将指定大小的数据读入用户指定的内存区域,并更新读写指针。

只有 read 操作才会将文件的实际数据从外存读入内存。

6. 写文件

系统调用参数:文件描述符、写回数据量(如 1 KB)、源内存地址。

系统操作:从用户指定的内存区域读取指定大小的数据,写回到写指针所指向的外存位置,并更新写指针。

写文件与读文件流程对称,二者操作参数结构完全相同。

7. 小结

操作是否访问外存目录是否将文件数据读入内存关键点
创建写目录分配磁盘块 + 插入目录项
删除读+删目录回收磁盘块 + 删除目录项
打开读目录否(仅复制目录项到打开文件表)权限检查;返回文件描述符
关闭打开计数器 -1;归零时删系统表项
否(查打开文件表)依据读写指针定位外存数据
否(查打开文件表)否(内存→外存)依据写指针写回外存

最容易混淆的点:

  • 打开文件 ≠ 读文件:open 只把目录项复制到内存打开文件表,不读取文件数据;read 才真正将文件数据从外存读入内存。
  • 读写操作时用户只需提供文件描述符(打开文件表索引号),无需再提供文件名或路径。
  • 文件描述符即进程打开文件表的索引号(序号),也称为"文件句柄"。