news 2026/9/11 18:46:55

第二十一讲:文件描述符和打开模式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
第二十一讲:文件描述符和打开模式

大家好,接下来的一段时间我将开始学习野火的Linux系统课程并将学习到的干货逐步更新到我的CSDN博客中。没时间刷课的同学可以把我的博客喂给AI 突击一下。

目录

1. 内容概览

2. 文件描述符fd

2.1 基本概念

2.2 标准文件描述符

2.3 打开文件对象的共享特性

3. 打开文件

3.1 打开/创建文件函数:open ()

3.2 返回值与错误处理

5. 关闭文件

5.1 关闭文件函数close ():

5.2 返回值

5.3 核心原理

6. 读取文件

6.1 读取文件函数read ():

6.2 返回值

6.3 短读与中断

7. 写入数据

7.1 写入数据函数write ():

7.2 write_all 函数

8.文件偏移控制

8.1 文件偏移控制函数 lseek ():

8.2 返回值

8.3 偏移基准

8.4 文件空洞原理

9. 页缓存与数据落盘机制

9.1 IO 数据流转路径

9.2 三个同步函数对比

sync()

fsync() / fdatasync()

10. 完整文件复制工程代码(标准严谨版)

11. 高频易错点总结


1. 内容概览

Linux 中对文件的基本操作通常通过系统调用完成:

open → read / write → lseek(可选)→ sync / fsync(可选)→ close

常用接口:

  • open():打开或创建文件,返回文件描述符。
  • read():从文件读取数据。
  • write():向文件写入数据。
  • lseek():修改当前文件偏移量。
  • close():关闭文件描述符。
  • sync():请求回写系统中所有文件系统的脏数据。
  • fsync():同步指定文件的数据和必要元数据。
  • fdatasync():主要同步指定文件的数据。

2. 文件描述符fd

  • task_struct:进程 PCB,描述进程所有信息;struct files_struct *files成员,指向该进程的打开文件管理结构。每个进程独立一份 files_struct
  • files_struct:本进程打开文件总管理对象。内部核心是fd_array[]数组(fd 表)。
    • fd 就是这个数组的索引下标(非负整数:0,1,2,3...)
    • 数组里存的是struct file*指针,不是文件本身
  • fd 表(fd_array):下标 = fd;每个表项:指针 + fd 私有标志(如 FD_CLOEXEC)。fd 是进程私有的!不同进程相同 fd 数字,指向完全不同的 struct file
  • struct file一次打开操作的上下文对象(内核对象)。同一个文件,可以 open 多次,生成多个独立 struct file。
    • 里面保存本次打开的状态:偏移、打开标志、访问模式、文件操作函数集等。
    • 成员含义考点重点
      f_pos文件偏移量(off_t)读写位置;存在 struct file,不是 fd!多个 fd 共享同一个 struct file 就共享同一个偏移;两次 open 生成两个 file,偏移互相独立。lseek 修改 f_pos。
      f_flags文件状态标志open 传入的 flags(O_RDONLY/O_APPEND/O_NONBLOCK 等);属于打开文件对象属性,多个 fd 共享。可以用 fcntl 修改。
      f_mode访问模式文件读写权限,标识只读 / 只写 / 读写,内核用来校验读写是否合法。
      f_opstruct file_operations *文件操作函数集内核回调函数表:read、write、llseek、release 等。不同设备(普通文件 / 管道 /socket)挂载不同的操作函数。read/write 系统调用最终调用 f_op->read/f_op->write。
      f_path保存路径相关信息,dentry+super_block用来拿到 inode、文件系统信息;记录这个打开文件对应的目录项。
  • inode:描述磁盘上文件本身(文件名、大小、权限、磁盘块)。多个 struct file 可以指向同一个 inode

2.1 基本概念

文件描述符(file descriptor,fd)是用户程序用来引用已打开文件或其他 I/O 对象的非负整数。

从概念上看,fd 是当前进程文件描述符表中的索引。表项进一步指向内核中的打开文件对象struct file。

int fd; fd = open(pathname, flags, mode);

2.2 标准文件描述符

进程启动后fd表fd_array)默认的三个fd:

fd含义
0STDIN_FILENO标准输入
1STDOUT_FILENO标准输出
2STDERR_FILENO标准错误

这是约定而非永久绑定,支持关闭、重定向到文件、管道、套接字等。

2.3 打开文件对象的共享特性

  • 两次 open:生成两个独立struct file偏移独立
  • 复制 fd:多个 fd 共享同一个struct file共享文件偏移
  • FD_CLOEXEC:属于fd 私有标志,不共享。
  • 父子进程:fd 表独立,但指向同一个内核 file 对象。

3. 打开文件

3.1 打开/创建文件函数:open ()

头文件与函数原型: #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open(const char *pathname, int flags, ... /* mode_t mode */); 常用写法: int fd = open("a.txt", O_RDONLY); int fd = open("b.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666); !!只有带 O_CREAT 时,第三个权限参数才生效。

open()flags=唯一主模式+多个副模式按位或

访问主模式(三选一,不可叠加)
标志含义
O_RDONLY只读打开
O_WRONLY只写打开
O_RDWR读写打开
常用附加副模式(可叠加)
标志作用说明
O_CREAT文件不存在则创建,必须搭配 mode 参数
O_EXCL配合 O_CREAT,文件存在则 open 失败,防竞争创建
O_TRUNC可写打开已有文件,清空文件内容(截断为 0)
O_APPEND原子追加,每次写自动跳到文件末尾(多进程安全)
O_NONBLOCK非阻塞 IO,读写不等待
O_SYNC同步写,数据 + 元数据强制落盘
O_DSYNC仅同步数据 + 必要元数据,效率高于 O_SYNC
O_DIRECT绕过页缓存读写,需要内存对齐
O_CLOEXECexec 执行时自动关闭 fd,防文件描述符泄露

注意:O_DIRECT并非完全无条件绕过缓存,也不保证数据永久落盘。

仅供了解:

mode 权限与 umask

创建文件最终权限公式:最终权限 = mode & ~umask

1、mode 是什么open 函数手动传入的预设权限,仅创建文件(O_CREAT)时生效。 示例:0666预设:所有者、同组、其他用户 全部可读可写。

2、umask 是什么系统全局权限掩码,作用:删掉不安全的权限。 系统默认一般0002:禁止 “其他用户” 写权限。

3、& ~ 是什么运算

~umask:把系统要禁止的权限翻转成允许的权限

mode &amp; ~umask:在你预设权限里,保留系统允许的、剔除系统禁止的

4、为什么必须写八进制、必须带前缀 0?Linux 权限是三组三位数权限,正好对应八进制 0~7。

0666= 八进制权限(合法)

666= 编译器识别为十进制,完全错乱,权限彻底错误

3.2 返回值与错误处理

  • 成功:返回非负整数 fd
  • 失败:返回-1,设置 errno
int fd = open("a.txt", O_RDONLY); if (fd == -1) { perror("open"); return 1; }

注意:关闭 0/1/2 后,open 可能返回 0、1、2,不能用 fd>2 判断合法性。


5. 关闭文件

5.1 关闭文件函数close ():

头文件和函数原型 #include <unistd.h> int close(int fd);

5.2 返回值

  • 成功:0
  • 失败:-1

5.3 核心原理

close 仅删除当前进程 fd 表项、引用计数 - 1只有引用计数为 0,内核才真正释放 file 对象

注意:close 不删除磁盘文件,删除文件需要unlink


6. 读取文件

6.1 读取文件函数read ():

头文件和函数原型 #include <unistd.h> ssize_t read(int fd, void *buf, size_t count); #参数:fd 文件描述符、buf 用户缓冲区、count 期望读取字节数。

6.2 返回值

返回值含义
>0

count:成功读取全部字节
0~count:剩余文件长度小于count,或,读取期间被异步信号打断

=0读到文件末尾 EOF,不是错误
-1读取失败

必须用ssize_t(有符号整型)接收返回值。

6.3 短读与中断

短读是正常现象:文件末尾、管道 / 套接字、信号中断、非阻塞 IO 都会导致读取不足 count。errno == EINTR属于可重试错误。


7. 写入数据

7.1 写入数据函数write ():

头文件和函数原型 #include <unistd.h> ssize_t write(int fd, const void *buf, size_t count);

7.2 write_all 函数

短写:write返回正数,但实际写入字节少于期望写入数量,属于正常现象(管道、socket 容易出现,普通文件较少)。

功能:解决write存在短写的问题,保证把指定全部字节完整写入;同时处理系统调用被信号中断(EINTR)的可重试情况。

函数源码

#include <errno.h> static int write_all(int fd, const void *data, size_t size) { const char *p = data; while (size > 0) { ssize_t n = write(fd, p, size); if (n > 0) { p += n; size -= (size_t)n; } else if (n == -1 && errno == EINTR) { continue; } else { return -1; } } return 0; }

参数:

  • fd:目标文件描述符
  • data:待写入数据的起始地址
  • size:需要写入的总字节数

返回值:

  • 0:全部数据写入成功
  • -1:发生 IO 错误,写入失败

使用示例:

char str[] = "hello world"; int ret = write_all(fd, str, sizeof(str)); if(ret == -1){ perror("write_all"); }

重点:write 成功仅代表数据进入内核页缓存,不代表落盘


8.文件偏移控制

8.1 文件偏移控制函数 lseek ():

#include <unistd.h> off_t lseek(int fd, off_t offset, int whence); 若whence为SEEK_SET,基准点为文件开头 若whence为SEEK_CUR,基准点为当前位置 若whence为SEEK_END,基准点为文件末尾

8.2 返回值

  • 成功:返回当前文件偏移,
  • 失败:返回 - 1。

8.3 偏移基准

基准位置最终偏移
SEEK_SET文件开头offset
SEEK_CUR当前偏移当前 + offset
SEEK_END文件末尾文件长度 + offset

8.4 文件空洞原理

write(fd, "123", 3); // 偏移=3 lseek(fd, 100, SEEK_CUR); // 偏移=103 write(fd, "abc", 3); // 103位置写入

中间空白区域形成文件空洞:逻辑存在、磁盘不占空间,读取默认补 0。

注意:管道、套接字、FIFO不支持 lseek


9. 页缓存与数据落盘机制

9.1 IO 数据流转路径

读:磁盘 → 内核页缓存 → 用户缓冲区

写:用户缓冲区 → 内核页缓存 → 磁盘(延迟回写)

write 返回成功只是写入缓存,断电丢失风险存在。

9.2 三个同步函数对比

sync()
void sync(void);

全局刷新所有文件系统脏页,效率低,不精准。

fsync() / fdatasync()
int fsync(int fd); int fdatasync(int fd);
  • fsync:同步数据 + 全部元数据(权限、时间等)
  • fdatasync:仅同步数据 + 必要元数据,性能更高

10. 完整文件复制工程代码(标准严谨版)

#include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> static int write_all(int fd, const void *data, size_t size) { const char *p = data; while (size &gt; 0) { ssize_t n = write(fd, p, size); if (n &gt; 0) { p += n; size -= (size_t)n; } else if (n == -1 &amp;&amp; errno == EINTR) { continue; } else { return -1; } } return 0; } int main(int argc, char **argv) { char buf[4096]; int src = -1; int dst = -1; int result = EXIT_FAILURE; if (argc != 3) { fprintf(stderr, "用法:%s &lt;源文件&gt; &lt;目标文件&gt;\n", argv[0]); return EXIT_FAILURE; } src = open(argv[1], O_RDONLY); if (src == -1) { perror("open source"); goto cleanup; } dst = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0666); if (dst == -1) { perror("open destination"); goto cleanup; } for (;;) { ssize_t nread = read(src, buf, sizeof buf); if (nread &gt; 0) { if (write_all(dst, buf, (size_t)nread) == -1) { perror("write"); goto cleanup; } } else if (nread == 0) { break; } else if (errno != EINTR) { perror("read"); goto cleanup; } } result = EXIT_SUCCESS; cleanup: if (dst != -1 &amp;&amp; close(dst) == -1) perror("close destination"); if (src != -1 &amp;&amp; close(src) == -1) perror("close source"); return result; }

关键:目标文件必须加O_TRUNC,防止旧文件残留尾部数据。


11. 高频易错点总结

  1. read 返回 0 是 EOF,不是错误
  2. read/write 返回值必须用ssize_t,不能用 int。
  3. write 存在短写,必须循环写完
  4. O_CREAT 必须带八进制 mode,权限受 umask 影响。
  5. O_APPEND 是原子追加,比手动 lseek 安全(多进程)。
  6. write 成功 ≠ 数据落盘,持久化需要 fsync/fdatasync。
  7. 两次 open:两个独立 struct file,文件偏移 f_pos 互相独立;
  8. dup:多个 fd 共用同一个 struct file,共享同一份 f_pos
  9. lseek 不能用于管道、套接字等流式文件。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 18:45:27

HDFS架构解析与性能调优实战

1. HDFS核心架构解析HDFS作为Hadoop生态系统的基石&#xff0c;其设计哲学源于Google File System论文&#xff0c;但针对开源社区需求做了大量优化。这个分布式文件系统最显著的特点是"移动计算比移动数据更划算"的设计理念&#xff0c;这直接影响了它的架构决策。1…

作者头像 李华
网站建设 2026/9/11 18:45:07

mise 与 pacman:Arch 系 Linux 的声明式系统包管理

mise 与 pacman&#xff1a;Arch 系 Linux 的声明式系统包管理 【免费下载链接】mise dev tools, env vars, task runner 项目地址: https://gitcode.com/GitHub_Trending/mi/mise 在 Arch 系 Linux&#xff08;Arch、Manjaro、EndeavourOS 等&#xff09;上&#xff0c…

作者头像 李华
网站建设 2026/9/11 18:43:50

面试知识题

问题答案类型transformer中的attention结构用户语句&#xff08;query&#xff09;经过三个矩阵三次运算得到Q&#xff0c;K&#xff0c;V计算公式&#xff1a;Q与K倒置进行点积运算&#xff0c;在使用softmax得到每个词权重占比&#xff0c;因为softmax的缩放能力&#xff0c;…

作者头像 李华
网站建设 2026/9/11 18:42:52

【通信工程】5G-A/6G通信-感知-计算一体化网络中的装备与软件01

编号 类型 5G-A产品/6G产品及解决方案 生产/制造/运输/配送/建设/实施/规划/加工/设计所需要的机床/装备/工业母机和设备/软件列表 关联知识和法律法规 1 射频前端芯片(Sub-6/毫米波/FR3) 5G-A 射频收发、6G 太赫兹/亚太赫兹前端、PA/LNA/开关/调谐器、RF SoC 设计:…

作者头像 李华
网站建设 2026/9/11 18:42:29

Linux 进程池实战:匿名管道、轮询调度与隐藏的阻塞陷阱

文章目录前言代码展示代码功能Task.hppProcessPool.hpp整体运行思路运行演示隐藏的问题前言 本次介绍的是使用匿名管道创建进程池的操作&#xff0c;其中会存在一个隐藏的堵塞问题&#xff0c;这个在后面会提及。本人所认为的重要部分会以注释的形式展出&#xff0c;有一些也会…

作者头像 李华
网站建设 2026/9/11 18:40:45

第二十讲:Linux 文件系统

大家好&#xff0c;接下来的一段时间我将开始学习野火的Linux 系统课程并将学习到的干货逐步更新到我的CSDN博客中。没时间刷课的同学可以把我的博客喂给AI 突击一下。 目录 一、一切皆文件思想 二、VFS 虚拟文件系统 VFS 内部核心结构体 两套操作函数集&#xff08;函数指…

作者头像 李华