深入理解 Linux 文件 IO:从基础接口到实战应用

在 Linux 系统编程中,文件 IO 是核心基础能力之一。相比于我们日常开发中接触的标准 IO,文件 IO 作为内核提供的系统调用接口,在处理设备文件、通信文件等场景下具备不可替代的优势。本文将从文件 IO 与标准 IO 的核心区别入手,系统讲解文件 IO、目录 IO、时间相关的核心接口,并通过实战案例帮助你掌握文件 IO 的实际应用。

一、文件 IO vs 标准 IO:核心区别解析

在开始学习文件 IO 之前,我们首先要明确它与标准 IO(stdio)的核心差异,这能帮助我们在实际开发中做出正确的技术选型。

表格

特性 标准 IO (stdio) 文件 IO (系统调用)
缓存机制 有用户态缓存(全缓冲 / 行缓冲) 无缓存(直接操作内核缓冲区)
实现层面 库函数(对系统调用的封装) 系统调用(内核函数接口)
适用场景 普通文件(文本 / 二进制文件) 设备文件、通信文件(管道 / 套接字)
接口示例 fopen/fclose/fread/fwrite open/close/read/write

简单来说:

  • 标准 IO 为了提升普通文件的读写效率,在用户层增加了缓存机制,是对文件 IO 的封装;
  • 文件 IO 直接与内核交互,无额外缓存,更适合对实时性要求高的设备 / 通信场景。

二、文件 IO 核心接口详解

文件 IO 的核心接口包括open/close/read/write/lseek,这些接口是操作文件的基础,我们逐一拆解学习。

1. open:打开 / 创建文件

open是文件 IO 的入口,用于打开或创建文件,并返回一个文件描述符(非负整数,内核用于标识打开的文件)。

函数原型

c

运行

复制代码
// 打开已存在的文件
int open(const char *pathname, int flags);
// 创建并打开新文件(文件不存在时)
int open(const char *pathname, int flags, mode_t mode);
关键参数说明
  • pathname:文件路径(绝对 / 相对路径);
  • flags :打开方式(必选 + 可选组合):
    • 必选(三选一):O_RDONLY(只读)、O_WRONLY(只写)、O_RDWR(读写);
    • 可选:O_CREAT(创建文件)、O_TRUNC(清空文件)、O_EXCL(文件存在则报错)、O_APPEND(追加写入);
  • mode :文件权限(仅O_CREAT时有效),如0664(rw-rw-r--)、0777(rwxrwxrwx)(最终权限会受 umask 掩码影响)。
常用 flags 组合(对应标准 IO 的 r/r+/w/w+/a/a+)

表格

标准 IO 模式 文件 IO flags 组合 说明
r O_RDONLY 只读打开已存在文件
r+ O_RDWR 读写打开已存在文件
w O_WRONLY | O_CREAT | O_TRUNC, 0664 只写,不存在则创建,存在则清空
w+ O_RDWR | O_CREAT | O_TRUNC, 0664 读写,不存在则创建,存在则清空
a O_WRONLY | O_CREAT | O_APPEND, 0664 追加写入,不存在则创建
a+ O_RDWR | O_CREAT | O_APPEND, 0664 追加 + 读取,不存在则创建
返回值
  • 成功:返回新的文件描述符(Linux 默认已打开 0/1/2:标准输入 / 输出 / 错误);
  • 失败:返回 - 1(需检查 errno)。

2. close:关闭文件描述符

c

运行

复制代码
int close(int fd);
  • 功能:释放文件描述符,关闭对应文件;
  • 参数:fdopen返回的文件描述符;
  • 返回值:成功返回 0,失败返回 - 1。

3. write:写入文件

c

运行

复制代码
ssize_t write(int fd, const void *buf, size_t count);
  • 功能:将buf指向的内存中count个字节写入fd对应的文件;
  • 参数:
    • fd:文件描述符;
    • buf:待写入数据的内存首地址;
    • count:期望写入的字节数;
  • 返回值:
    • 成功:实际写入的字节数(可能小于 count,如磁盘满);
    • 失败:返回 - 1。

4. read:读取文件

c

运行

复制代码
ssize_t read(int fd, void *buf, size_t count);
  • 功能:从fd对应的文件中读取最多count个字节到buf指向的内存;
  • 参数:
    • fd:文件描述符;
    • buf:存放读取数据的内存首地址;
    • count:最大读取字节数;
  • 返回值:
    • 成功:实际读取的字节数;
    • 读到文件末尾:返回 0;
    • 失败:返回 - 1。

5. lseek:调整文件偏移量

文件 IO 通过 "偏移量" 记录当前读写位置,lseek用于调整该偏移量:

运行

复制代码
off_t lseek(int fd, off_t offset, int whence);
  • 参数:
    • offset:偏移量(可正可负);
    • whence:偏移基准:
      • SEEK_SET:文件开头;
      • SEEK_CUR:当前位置;
      • SEEK_END:文件末尾;
  • 返回值:成功返回调整后的偏移量(相对于文件开头),失败返回 - 1。
常用场景

c

运行

复制代码
// 移动到文件开头
lseek(fd, 0, SEEK_SET);
// 移动到文件末尾(可获取文件大小)
off_t file_size = lseek(fd, 0, SEEK_END);
// 从当前位置向后偏移100字节
lseek(fd, 100, SEEK_CUR);

三、实战:文件 IO 实现图片拷贝

掌握了核心接口后,我们通过 "图片拷贝" 实战巩固知识点。该案例通过open打开源文件和目标文件,循环read源文件数据并write到目标文件,实现二进制文件的完整拷贝。

完整代码

运行:

编译与运行

bash

运行

复制代码
# 编译
gcc copy_image.c -o copy_image
# 运行(替换为实际图片路径)
./copy_image ./source.jpg ./target.jpg

关键要点

  1. 使用4096字节缓冲区:平衡内存占用和系统调用次数,提升拷贝效率;
  2. 错误处理:每个系统调用后检查返回值,避免文件描述符泄漏;
  3. 处理write返回值:确保写入字节数与读取字节数一致,避免数据丢失。

四、目录 IO:操作目录的核心接口

除了普通文件,Linux 中目录也是一种文件类型,目录 IO 提供了专门的接口来操作目录:

1. opendir/closedir:打开 / 关闭目录

c

运行

复制代码
#include <dirent.h>
// 打开目录,返回目录流指针
DIR *opendir(const char *name);
// 关闭目录流
int closedir(DIR *dirp);

2. readdir:读取目录项

复制代码
struct dirent *readdir(DIR *dirp);
  • 功能:读取目录中的下一个目录项(文件 / 子目录);

  • 返回值:成功返回目录项结构体指针,读到末尾 / 失败返回 NULL;

  • 核心结构体struct dirent

    c

    运行

    复制代码
    struct dirent {
        ino_t d_ino;          // 索引节点号
        off_t d_off;          // 目录流偏移量
        unsigned short d_reclen; // 结构体长度
        unsigned char d_type; // 文件类型(如DT_REG普通文件、DT_DIR目录)
        char d_name[256];     // 文件名(核心字段)
    };

3. 目录操作辅助接口

表格

接口 原型 功能
mkdir int mkdir(const char *path, mode_t mode); 创建目录(权限通常 0777)
rmdir int rmdir(const char *path); 删除空目录
chdir int chdir(const char *path); 切换当前工作目录
getcwd char *getcwd(char *buf, size_t size); 获取当前工作目录绝对路径

五、时间相关接口:文件操作的时间辅助

在文件 IO 中,我们常需要记录文件操作时间(如创建 / 修改时间),Linux 提供了一套时间转换接口:

1. time:获取秒级时间戳

c

运行

复制代码
#include <time.h>
time_t time(time_t *tloc);
  • 功能:获取从 1970-01-01 00:00:00 UTC 到现在的秒数(时间戳);
  • 返回值:成功返回时间戳,失败返回 - 1。

2. localtime:时间戳转本地日历时间

c

运行

复制代码
struct tm *localtime(const time_t *timep);
  • 功能:将时间戳转换为包含时区的日历时间结构体;

  • 核心结构体struct tm

    c

    运行

    复制代码
    struct tm {
        int tm_sec;   // 秒(0-60)
        int tm_min;   // 分(0-59)
        int tm_hour;  // 时(0-23)
        int tm_mday;  // 日(1-31)
        int tm_mon;   // 月(0-11,需+1)
        int tm_year;  // 年(需+1900)
        int tm_wday;  // 星期(0-6,周日为0)
        int tm_yday;  // 年中天数(0-365)
        int tm_isdst; // 夏令时标识
    };

3. mktime:日历时间转时间戳

c

运行

复制代码
time_t mktime(struct tm *tm);
  • 功能:将struct tm转换回时间戳,常用于时间计算。

示例:获取当前时间并格式化输出

c

运行

复制代码
#include <stdio.h>
#include <time.h>

int main() {
    // 1. 获取时间戳
    time_t now = time(NULL);
    // 2. 转换为本地时间
    struct tm *tm_now = localtime(&now);
    // 3. 格式化输出(注意tm_year和tm_mon的偏移)
    printf("当前时间:%d年%d月%d日 %d:%d:%d\n",
           tm_now->tm_year + 1900,
           tm_now->tm_mon + 1,
           tm_now->tm_mday,
           tm_now->tm_hour,
           tm_now->tm_min,
           tm_now->tm_sec);
    return 0;
}

六、文件描述符的核心特性

  1. 上限限制 :系统对每个进程的文件描述符数量有上限(可通过ulimit -n查看 / 修改);
  2. 最小未使用原则:新创建的文件描述符总是选择当前最小的、未被使用的非负整数;
  3. 默认打开:进程启动时默认打开 3 个文件描述符:0(标准输入)、1(标准输出)、2(标准错误)。

总结

本文系统讲解了 Linux 文件 IO 的核心知识点,关键总结如下:

  1. 文件 IO 与标准 IO 的核心差异:文件 IO 是无缓存的系统调用,适合设备 / 通信文件;标准 IO 是有缓存的库函数,适合普通文件;
  2. 文件 IO 核心流程open(打开 / 创建文件)→ read/write(读写数据)→ lseek(调整偏移)→ close(关闭文件);
  3. 实战关键:二进制文件拷贝需使用固定缓冲区循环读写,且要严格处理系统调用的返回值,避免数据丢失或文件描述符泄漏。

掌握文件 IO 是 Linux 系统编程的基础,后续无论是网络编程、设备驱动开发还是进程间通信,都离不开这些核心接口的应用。建议结合本文的示例代码多做实战,加深对接口的理解和使用熟练度。

相关推荐
Lupino3 分钟前
被 React “玩弄”的 24 小时:为了修一个不存在的 Bug,我给大模型送了顿火锅钱
前端·react.js
米丘10 分钟前
了解 Javascript 模块化,更好地掌握 Vite 、Webpack、Rollup 等打包工具
前端
Heo11 分钟前
深入 React19 Diff 算法
前端·javascript·面试
滕青山12 分钟前
个人所得税计算器 在线工具核心JS实现
前端·javascript·vue.js
小怪点点13 分钟前
手写promise
前端·promise
国思RDIF框架22 分钟前
RDIFramework.NET Web 敏捷开发框架 V6.3 发布 (.NET8+、Framework 双引擎)
前端
颜酱23 分钟前
从0到1实现LFU缓存:思路拆解+代码落地
javascript·后端·算法
Mintopia23 分钟前
如何在有限的时间里,活出几倍的人生
前端
炫饭第一名24 分钟前
速通Canvas指北🦮——变形、渐变与阴影篇
前端·javascript·程序员
Neptune125 分钟前
让我带你迅速吃透React组件通信:从入门到精通(上篇)
前端·javascript