
大家好接下来的一段时间我将开始学习野火的Linux系统课程并将学习到的干货逐步更新到我的CSDN博客中。没时间刷课的同学可以把我的博客喂给AI 突击一下。目录1. 内容概览2. 文件描述符fd2.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. 文件描述符fdtask_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 filestruct file一次打开操作的上下文对象内核对象。同一个文件可以 open 多次生成多个独立 struct file。里面保存本次打开的状态偏移、打开标志、访问模式、文件操作函数集等。成员含义考点重点f_pos文件偏移量off_t读写位置存在 struct file不是 fd多个 fd 共享同一个 struct file 就共享同一个偏移两次 open 生成两个 file偏移互相独立。lseek 修改 f_pos。f_flags文件状态标志open 传入的 flagsO_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保存路径相关信息dentrysuper_block用来拿到 inode、文件系统信息记录这个打开文件对应的目录项。inode描述磁盘上文件本身文件名、大小、权限、磁盘块。多个 struct file 可以指向同一个 inode。2.1 基本概念文件描述符file descriptorfd是用户程序用来引用已打开文件或其他 I/O 对象的非负整数。从概念上看fd 是当前进程文件描述符表中的索引。表项进一步指向内核中的打开文件对象struct file。int fd; fd open(pathname, flags, mode);2.2 标准文件描述符进程启动后fd表fd_array默认的三个fdfd宏含义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可写打开已有文件清空文件内容截断为 0O_APPEND原子追加每次写自动跳到文件末尾多进程安全O_NONBLOCK非阻塞 IO读写不等待O_SYNC同步写数据 元数据强制落盘O_DSYNC仅同步数据 必要元数据效率高于 O_SYNCO_DIRECT绕过页缓存读写需要内存对齐O_CLOEXECexec 执行时自动关闭 fd防文件描述符泄露注意O_DIRECT并非完全无条件绕过缓存也不保证数据永久落盘。仅供了解mode 权限与 umask创建文件最终权限公式最终权限 mode ~umask1、mode 是什么open 函数手动传入的预设权限仅创建文件O_CREAT时生效。 示例0666预设所有者、同组、其他用户 全部可读可写。2、umask 是什么系统全局权限掩码作用删掉不安全的权限。 系统默认一般0002禁止 “其他用户” 写权限。3、 ~ 是什么运算~umask把系统要禁止的权限翻转成允许的权限mode amp; ~umask在你预设权限里保留系统允许的、剔除系统禁止的4、为什么必须写八进制、必须带前缀 0Linux 权限是三组三位数权限正好对应八进制 0~7。0666 八进制权限合法666 编译器识别为十进制完全错乱权限彻底错误3.2 返回值与错误处理成功返回非负整数 fd失败返回-1设置 errnoint fd open(a.txt, O_RDONLY); if (fd -1) { perror(open); return 1; }注意关闭 0/1/2 后open 可能返回 0、1、2不能用 fd2 判断合法性。5. 关闭文件5.1 关闭文件函数close ()头文件和函数原型 #include unistd.h int close(int fd);5.2 返回值成功0失败-15.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 返回值返回值含义0count成功读取全部字节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文件开头offsetSEEK_CUR当前偏移当前 offsetSEEK_END文件末尾文件长度 offset8.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. 高频易错点总结read 返回 0 是 EOF不是错误。read/write 返回值必须用ssize_t不能用 int。write 存在短写必须循环写完。O_CREAT 必须带八进制 mode权限受 umask 影响。O_APPEND 是原子追加比手动 lseek 安全多进程。write 成功 ≠ 数据落盘持久化需要 fsync/fdatasync。两次 open两个独立 struct file文件偏移 f_pos 互相独立dup多个 fd 共用同一个 struct file共享同一份 f_poslseek 不能用于管道、套接字等流式文件。