第 39 章 插叙:文件和目录

持久存储设备(如硬盘、固态硬盘)在断电时能保持数据不变。操作系统通过文件系统管理持久存储,提供了两个关键抽象:文件和目录。

核心抽象

  • 文件 (File):一个线性字节数组。每个文件拥有一个低级名称(通常称为 inode 号)。
  • 目录 (Directory):一种特殊的文件,其内容是一个包含 (用户可读名字, 低级名字) 对的列表。目录通过包含其他目录形成任意层级的目录树 (Directory Tree)

文件操作 API

创建文件

通过 open() 系统调用创建文件,返回一个进程私有的文件描述符 (File Descriptor)。文件描述符是一个整数,作为访问文件的句柄。

int fd = open("foo", O_CREAT | O_WRONLY | O_TRUNC);
  • O_CREAT:创建新文件。
  • O_WRONLY:只写模式。
  • O_TRUNC:若文件存在则截断为 0 字节。

读写文件

使用 read()write() 进行顺序访问。进程默认打开 3 个文件描述符:0(标准输入)、1(标准输出)、2(标准错误)。

// read 返回读取的字节数
read(fd, buffer, size);

随机访问

使用 lseek() 改变文件读写的当前偏移量。

off_t lseek(int fildes, off_t offset, int whence);
  • whence 决定偏移基准:SEEK_SET(文件开头)、SEEK_CUR(当前位置)或 SEEK_END(文件末尾)。
  • 注意lseek() 仅改变操作系统内存中维护的偏移量变量,不会直接触发磁盘的物理寻道 (Disk Seek)

强制同步写入

write() 调用通常只将数据写入内存缓冲区。为了保证数据持久化(如数据库应用),需调用 fsync() 强制将脏数据写入磁盘。

int rc = write(fd, buffer, size);
rc = fsync(fd); // 强制写入磁盘

为确保文件在目录中的元数据也持久化,有时还需对包含该文件的目录调用 fsync()

文件重命名

rename(char *old, char *new) 提供原子 (Atomic) 更新保证。系统崩溃时,文件要么是旧名称,要么是新名称。常用于安全更新文件:将新内容写入临时文件,调用 fsync(),最后 rename 覆盖原文件。

获取文件元数据

使用 stat()fstat() 获取文件的元数据(如大小、inode 号、权限、时间戳),这些信息通常保存在文件系统的 inode 结构中。

struct stat {
    ino_t st_ino;    /* inode number */
    off_t st_size;   /* total size, in bytes */
    // ... 其他元数据
};

删除文件

删除文件的系统调用是 unlink()。文件系统通过引用计数 (Reference Count / Link Count) 跟踪链接到特定 inode 的文件名数量。unlink() 会移除文件名与 inode 的链接并递减引用计数。只有当引用计数降为 0 时,文件系统才会真正释放 inode 和数据块。

目录操作 API

目录的格式被视为文件系统元数据,只能通过系统调用间接更新。

  • 创建目录mkdir()。新创建的空目录包含两个默认条目:.(自身)和 ..(父目录)。
  • 读取目录:使用 opendir()readdir()closedir() 遍历目录条目。
DIR *dp = opendir(".");
struct dirent *d;
while ((d = readdir(dp)) != NULL) {
    printf("%d %s\n", (int) d->d_ino, d->d_name);
}
closedir(dp);
  • 删除目录rmdir()。为了防止意外删除大量数据,rmdir() 要求目录必须为空。

硬链接与符号链接

通过 link()(命令行 ln)创建。硬链接在目录中创建一个新名称,指向与原文件相同的 inode 号。

  • 限制:不能对目录创建硬链接(防止目录树成环),不能跨文件系统链接。

通过 ln -s 创建。符号链接是文件系统中的第三种对象类型(类型标记为 l)。

  • 链接文件的数据是其指向的目标文件的路径名(因此链接文件的大小等于路径字符串的长度)。
  • 允许悬空引用 (Dangling Reference):如果原文件被删除,符号链接依然存在,但指向无效路径。

文件系统的创建与挂载

  • 创建mkfs 工具在目标设备分区(如 /dev/sda1)上写入一个空的指定类型(如 ext3)文件系统。
  • 挂载mount 程序将底层文件系统粘贴到现有目录树的挂载点 (Mount Point) 上,将多个独立的文件系统统一到单一的目录树命名空间中。