本文是小编巩固自身而作,如有错误,欢迎指出!

目录

一、IO共识原理

二、C语言文件接口

fopen/fclose

fwrite

fprintf

输出重定向 >和追加重定向 >>

1. 输入重定向 >

2. 追加重定向 >>

三、文件系统调用

文件系统调用概念

比特位方式传递标志位(Bit Flags)

open

close

注意事项

write

read

四、访问文件的本质

文件是内核的数据结构

文件描述符(fd)

核心总结


 

一、IO共识原理

在正式学习IO之前,我们先回忆一下之前学习的关于文件的知识:

文件 = 内容 + 属性

文件分为打开的文件和没打开的文件

对于打开的文件:谁来打开?其实是由进程打开,本质是研究进程和文件的关系

对于没打开的文件:是放在哪里?其实是放在磁盘上。我们最关注的是什么问题?其实也就是是文件的存储问题,即没有被打开的文件非常多,那么文件如何被分门别类的放置好?文件被分门别类的放置好后,进而我们就可以快速的对文件进行增删查改,也就是快速的找到文件


二、C语言文件接口

fopen/fclose

函数原型

#include <stdio.h>

FILE *fopen(const char *pathname, const char *mode);
  • pathname:文件路径,可以是相对路径或绝对路径。
  • mode:打开模式,常用选项包括:
    • "r":只读,文件必须存在
    • "w":只写,如果文件存在则清空,不存在则创建
    • "a":追加写,如果文件不存在则创建
    • "r+":读写,文件必须存在
    • "w+":读写,文件存在则清空,不存在则创建
    • "a+":读写,写入操作追加到文件末尾
#include <stdio.h>

int fclose(FILE *stream);
  • stream:指向 fopen 返回的 FILE*
  • 返回值
    • 0:关闭成功
    • EOF:关闭失败

这些基础用法我们在之前就已经学习过了,现在重提的原因主要在于,我们今天要重点了解文件换缓冲机制

fopen 返回的 FILE* 会有缓冲区(默认全缓冲或行缓冲),所以写入磁盘不一定是即时的。

当使用 fwriteprintf 写入文件时,数据先进入用户级缓冲区(由 FILE* 管理),并不会立即写入磁盘。

fclose 会确保缓冲区的数据被写入磁盘,从而保证文件内容完整

我们先在看一看下面的示例代码

#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main() {
    const char *filename = "buffer_test.txt";
    const char *msg = "Hello, Linux I/O buffer!\n";

    // 打开文件用于写入
    FILE *fp = fopen(filename, "w");
    if (!fp) {
        perror("fopen");
        return 1;
    }

    // 写入缓冲区,但不调用 fclose
    fwrite(msg, strlen(msg), 1, fp);

    printf("Data written to buffer, but not yet flushed to disk.\n");
    printf("Check the file content with: cat %s\n", filename);

    // 这里注释掉 fclose 查看缓冲区效果
    // fclose(fp);

    // 如果你取消上面 fclose 注释,缓冲区会被刷新到磁盘
    printf("Now closing file to flush buffer...\n");
    fclose(fp);

    printf("File closed. Buffer flushed. Check the file content again.\n");

    return 0;
}
  1. 打开文件
    • 使用 fopen("buffer_test.txt", "w") 打开一个文件用于写入
    • 得到一个 FILE* 指针,内部维护一个 用户级缓冲区
  2. 写入数据到缓冲区
    • 使用 fwrite(msg, strlen(msg), 1, fp) 将字符串写入缓冲区
    • 注意,这时候 数据还未写入磁盘,仍在内存中
  3. 输出提示
    • 提示用户数据已写入缓冲区,但尚未刷新到磁盘
    • 可以用 cat buffer_test.txt 查看文件内容,此时可能为空
  4. 关闭文件(可选)
    • 调用 fclose(fp)
    • fclose刷新缓冲区:把缓冲区内的数据写入磁盘
    • 同时释放文件描述符,关闭文件
  5. 观察效果
    • 不调用 fclose:文件内容可能为空(数据仍在缓冲区)
    • 调用 fclose:数据写入磁盘,文件显示完整内容

fwrite

#include <stdio.h>

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
  • ptr:指向要写入的数据起始地址
  • size:每个元素的字节数
  • nmemb:要写入元素的个数
  • stream:目标文件的 FILE*
  • 返回值:成功写入的元素个数(不是字节数)

 

  • fwrite 写入的是 用户缓冲区,不是直接写磁盘
  • fclose刷新缓冲区并关闭文件
  • 可以用 fflush(fp) 代替 fclose 实现中途刷新缓冲区

fprintf

#include <stdio.h>

int fprintf(FILE *stream, const char *format, ...);
  • stream:目标文件的 FILE*
  • format:格式化字符串
  • ...:对应格式的变量
  • 返回值:成功写入的字符数,如果出错返回负值
#include <stdio.h>

int main() {
    FILE *fp = fopen("fprintf_test.txt", "w");
    if (!fp) {
        perror("fopen 出错");
        return 1;
    }

    int a = 42;
    const char *name = "Linux I/O";

    // 写入格式化内容
    fprintf(fp, "这是一个测试:变量 a = %d, name = %s\n", a, name);

    // 刷新缓冲区并关闭文件
    fclose(fp);

    printf("已写入 fprintf_test.txt,缓冲区已刷新。\n");

    return 0;
}

输出重定向 >和追加重定向 >>

 

1. 输入重定向 >

作用:将程序的标准输出(stdout)写入文件,如果文件已存在,会覆盖原文件内容

2. 追加重定向 >>

作用:将程序的标准输出(stdout)写入文件,如果文件已存在,内容追加到末尾

三、文件系统调用

文件系统调用概念

  • 文件系统调用是 操作系统提供的接口,允许程序访问磁盘上的文件和设备
  • C 标准库函数(fopen/fwrite/fclose) 不同,系统调用是 直接与内核交互

比特位方式传递标志位(Bit Flags)

  • 在 Linux 系统调用(如 open)中,通常会有多个选项
  • 为了节省参数,同时可以组合多个选项,Linux 使用 比特位(bit) 表示每个选项
  • 每个选项对应 整数中的一位(或多位),通过 按位或 | 来组合
#define O_RDONLY  0x0000  // 0000 0000
#define O_WRONLY  0x0001  // 0000 0001
#define O_RDWR    0x0002  // 0000 0010
#define O_CREAT   0x0040  // 0100 0000
#define O_APPEND  0x0400  // 0100 0000 0000
int flags = O_WRONLY | O_CREAT | O_APPEND;

内存中的二进制表示:

0000 0000 0000 0001  (O_WRONLY)
0000 0000 0100 0000  (O_CREAT)
0100 0000 0000 0000  (O_APPEND)
-------------------
0100 0000 0100 0001  (组合后的 flags)
  • 节省参数:一个整数可以表示多个选项
  • 易于扩展:新选项只需分配新的一位
  • 组合灵活:用 | 组合,用 & 检查

open

#include <fcntl.h>

int open(const char *pathname, int flags, mode_t mode);
  • pathname:文件路径
  • flags:打开方式,例如:
    • O_RDONLY 只读
    • O_WRONLY 只写
    • O_RDWR 读写
    • O_CREAT 文件不存在则创建
    • O_APPEND 追加写
    • O_TRUNC 打开时清空文件
  • mode:权限位(仅在创建文件时使用,如 0666)
  • 返回值:文件描述符 fd(>=0 表示成功,-1 表示失败)

close

#include <unistd.h>

int close(int fd);
  • fd:文件描述符,由 open 或其他系统调用返回
  • 返回值
    • 0 → 成功
    • -1 → 出错(并设置 errno

注意事项

  1. 不要重复关闭同一个 fd
    • 重复调用 close(fd) 会导致未定义行为
  2. fd 会被操作系统重用
    • 下一个 open 可能会分配相同的 fd
  3. 和库函数 fclose 的区别
    • close(fd) 只关闭 fd
    • fclose(FILE* fp) 先刷新缓冲区再调用 close

write

#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);
  • fd:文件描述符,由 open 或其他系统调用返回
  • buf:指向要写入的数据内存地址
  • count:写入字节数
  • 返回值:实际写入的字节数,如果出错返回 -1 并设置 errno
特性writefwrite
缓冲区内核缓冲区用户缓冲区 + 内核缓冲区
调用层级系统调用(内核)C 标准库封装(调用 write)
输出类型文件描述符 fdFILE*(可以是 stdout、stderr 或文件)
刷新控制立即写入内核缓冲区需要 fflushfclose 才写入内核

read

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);
  • fd:文件描述符,由 open 或其他系统调用返回
  • buf:指向内存缓冲区,用于存储读取的数据
  • count:要读取的最大字节数
  • 返回值
    • 成功 → 实际读取的字节数
    • 到文件末尾 → 返回 0
    • 出错 → 返回 -1 并设置 errno

四、访问文件的本质

在 Linux 中,“访问文件”不仅仅是打开文件或读写文件,而是操作 内核中的文件系统和文件描述符

文件是内核的数据结构

  • 文件在 Linux 内核中不仅是磁盘上的字节流,也对应内核维护的 文件结构
    • struct file:内核维护每个打开文件的状态
    • 包含:
      • 文件指针(读写偏移位置)
      • 访问权限(只读/只写/读写)
      • 文件状态(打开、追加、锁定等)
      • 关联的 inode(文件的元信息,如大小、权限、设备类型)

文件在磁盘上的字节 + 内核维护的元信息 = 文件的本质

文件描述符(fd)

  • 用户程序通过 文件描述符 fd 操作文件
  • fd 是 一个索引或句柄,指向内核维护的 struct file
  • Linux 默认:
    • 0 → stdin
    • 1 → stdout
    • 2 → stderr
  • 每次 open 返回的 fd,其实就是 指向内核文件表的索引

文件操作 = 通过 fd 间接操作内核文件结构

核心总结

  • 文件不仅是磁盘上的数据,更是 内核维护的数据结构 + 元信息
  • 用户程序访问文件 = 通过 fd 间接操作内核文件结构
  • 所有 I/O(read / write / fread / fwrite / printf)本质都是 数据在用户缓冲区 ↔ 内核缓冲区 ↔ 磁盘之间流动
  • 文件描述符、缓冲区、系统调用三者构成文件访问的完整链路

本次分享就到这里结束了,后续会继续更新,感谢阅读!

 

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐