【Linux 系统编程】文件 IO 与 Makefile 核心实战:从系统调用到工程编译

一、文件 IO 核心概念:系统调用 vs 标准 IO

在 Linux 中操作文件有两种核心方式:文件 IO(系统调用)标准 IO(C 库函数),二者底层关联但适用场景不同。

1.1 核心定义

类型 本质 核心特征 适用场景
文件 IO 操作系统对外提供的系统调用函数 无缓冲区、文件描述符(int)、功能强大 设备文件(如/dev下设备)、实时性要求高的场景
标准 IO C 标准库封装的文件操作函数 带缓冲区、文件流指针(FILE*)、跨平台 普通文件(文本 / 二进制)、通用文件操作

1.2 核心关系

  • 底层依赖:标准 IO(C 库)是对文件 IO(系统调用)的封装,跨平台特性由 C 库保证,而系统调用是 Linux 内核直接提供的功能;
  • 缓存差异:标准 IO 的缓冲区减少了系统调用次数(提升普通文件操作效率),文件 IO 无缓存,直接操作内核资源(适合设备文件);
  • 操作对象:文件 IO 用 "文件描述符(fd)" 标识文件,标准 IO 用 "文件流指针(FILE*)" 标识。

1.3 共同点与区别

维度 共同点 区别
核心目标 均用于文件读写操作 文件 IO:无缓存、fd、系统调用;标准 IO:有缓存、FILE*、C 库封装
跨平台性 - 文件 IO:依赖具体系统(如 Linux);标准 IO:跨平台(Windows/Linux 通用)
效率 - 普通文件:标准 IO 更高(缓存);设备文件:文件 IO 更优(实时)

二、文件 IO 核心函数:open/read/write/close

文件 IO 的操作流程遵循 "打开→读写→关闭" 的固定范式,核心依赖open/read/write/close四个系统调用。

2.1 打开文件:open

函数原型

c

运行

复制代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags, mode_t mode);
核心参数
参数 说明 常用取值
pathname 文件路径(绝对 / 相对路径) "1.txt""/etc/passwd"
flags 打开模式(必选 + 可选组合) 必选:O_RDONLY(只读)、O_WRONLY(只写)、O_RDWR(读写)可选:O_CREAT(创建文件)、O_TRUNC(清空文件)、O_APPEND(追加写)
mode 文件权限(仅O_CREAT时有效) 0666(读写权限)、0777(读写执行权限)
返回值
  • 成功:返回非负整数(文件描述符,fd,唯一标识打开的文件);
  • 失败:返回 - 1(可通过perror查看错误原因)。
示例

c

运行

复制代码
// 以只写+创建+清空模式打开1.txt,权限0666
int fd = open("1.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd == -1) {
    perror("open failed");
    return 1;
}

2.2 读取文件:read

函数原型

c

运行

复制代码
#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);
核心参数
参数 说明
fd 目标文件的文件描述符(open 返回值)
buf 接收数据的内存缓冲区(需提前分配空间)
count 本次读取的最大字节数(建议大于文件实际大小)
返回值
  • 0:实际读取到的字节数;

  • ==0:文件读取到末尾;
  • <0:读取失败(如 fd 无效、文件不存在)。
示例

c

运行

复制代码
char buf[1024] = {0};
// 从fd读取最多1024字节到buf
ssize_t n = read(fd, buf, sizeof(buf));
if (n < 0) {
    perror("read failed");
    close(fd);
    return 1;
}
printf("读取到%d字节:%s\n", n, buf);

2.3 写入文件:write

函数原型

c

运行

复制代码
#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);
核心参数
参数 说明
fd 目标文件的文件描述符
buf 待写入文件的数据缓冲区
count 待写入数据的有效长度(如strlen(buf)
返回值
  • 0:实际写入的字节数;

  • ==0:未写入任何字节(普通文件不会出现,设备文件可能);
  • <0:写入失败。
示例

c

运行

复制代码
char *data = "Hello Linux File IO!";
// 写入data到fd,长度为字符串有效长度
ssize_t n = write(fd, data, strlen(data));
if (n < 0) {
    perror("write failed");
    close(fd);
    return 1;
}
printf("写入%d字节\n", n);

2.4 关闭文件:close

函数原型

c

运行

复制代码
#include <unistd.h>

int close(int fd);
核心说明
  • 功能:释放文件描述符对应的内核资源(必须调用,否则会导致资源泄漏);
  • 返回值:成功返回 0,失败返回 - 1。
示例

c

运行

复制代码
if (close(fd) == -1) {
    perror("close failed");
    return 1;
}

三、目录操作:opendir/readdir/closedir

除了普通文件,Linux 中目录也是一种特殊文件,需通过专门的函数操作,流程为 "打开目录→读取目录→关闭目录"。

3.1 打开目录:opendir

函数原型

c

运行

复制代码
#include <dirent.h>

DIR *opendir(const char *name);
  • 功能:打开指定目录,返回目录流指针;
  • 参数:name为目录路径(如".""/home");
  • 返回值:成功返回DIR*(目录流指针),失败返回 NULL。

3.2 读取目录:readdir

函数原型

c

运行

复制代码
#include <dirent.h>

struct dirent *readdir(DIR *dirp);
  • 功能:从目录流中读取一个文件 / 子目录的信息;
  • 参数:dirpopendir返回的目录流指针;
  • 返回值:
    • 成功:返回struct dirent*(包含文件名、文件类型等信息);
    • 失败 / 目录末尾:返回 NULL。
核心结构体(struct dirent)

c

运行

复制代码
struct dirent {
    ino_t          d_ino;       // 索引节点号
    char           d_name[256]; // 文件名(核心字段)
    // 其他字段:文件类型、偏移量等
};

3.3 关闭目录:closedir

函数原型

c

运行

复制代码
#include <dirent.h>

int closedir(DIR *dirp);
  • 功能:关闭目录流,释放资源;
  • 返回值:成功返回 0,失败返回 - 1。

3.4 目录操作示例

c

运行

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

int main() {
    // 打开当前目录
    DIR *dir = opendir(".");
    if (dir == NULL) {
        perror("opendir failed");
        return 1;
    }

    // 遍历目录中的文件
    struct dirent *entry;
    while ((entry = readdir(dir)) != NULL) {
        printf("文件名:%s\n", entry->d_name);
    }

    // 关闭目录
    closedir(dir);
    return 0;
}

四、Makefile:Linux 工程编译的 "一键工具"

当工程包含多个源文件时,手动执行gcc编译效率极低,Makefile 可实现 "一键编译",自动管理编译依赖和规则。

4.1 Makefile 核心概念

  • 命令 :执行make命令时,系统会查找当前目录的makefile/Makefile文件并执行;
  • 核心规则目标:依赖\n\t规则(TAB 缩进是关键,不能用空格);
    • 目标:要生成的文件(如a.out)或操作(如clean);
    • 依赖:生成目标所需的文件(如main.cfunc.c);
    • 规则:生成目标的编译命令(如gcc main.c func.c -o a.out)。

4.2 Makefile 编写进阶(从基础到通用)

版本 1:基础规则(固定依赖)

makefile

复制代码
# 目标:a.out;依赖:main.c、func.c
a.out:main.c func.c
	gcc main.c func.c -o a.out

# 无依赖的目标(清理编译产物)
clean:
	rm -f a.out
  • 执行make:编译生成a.out(仅当依赖文件修改时重新编译);
  • 执行make clean:删除a.out
  • 缺点:依赖文件 / 编译命令固定,修改时需全量改动。
版本 2:内置变量(简化规则)

Makefile 提供内置变量简化编写,核心内置变量:

  • $^:当前规则的所有依赖文件;
  • $@:当前规则的目标文件。

makefile

复制代码
a.out:main.c func.c
	gcc $^ -o $@  # 等价于 gcc main.c func.c -o a.out

clean:
	rm -f $@      # 等价于 rm -f a.out
版本 3:自定义变量(通用模板)

通过自定义变量适配不同工程,可灵活修改源文件、目标名、编译选项:

makefile

复制代码
# 自定义变量:源文件、目标名、编译选项
SRC = main.c
SRC += func.c   # 追加源文件
APP = a.out
FLAG = -g       # 编译调试信息

# 规则:依赖为SRC,目标为APP
$(APP):$(SRC)
	gcc $^ -o $@ $(FLAG)  # 加入编译选项

# 清理规则
clean:
	rm -f $(APP)

4.3 Makefile 核心特性

  • 增量编译 :仅当依赖文件(如main.c)修改时,才重新编译生成目标(a.out),未修改则提示is up to date
  • 多目标支持 :可定义多个目标(如allcleaninstall),执行make 目标名即可触发对应规则;
  • 工程管理:支持跨目录源文件编译、自定义编译脚本,是大型 Linux 工程的标配。

五、核心实战:文件 IO + Makefile 完整示例

5.1 源文件(main.c)

c

运行

复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main() {
    // 1. 打开文件
    int fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if (fd == -1) {
        perror("open failed");
        return 1;
    }

    // 2. 写入文件
    char *data = "Hello File IO!\n";
    write(fd, data, strlen(data));

    // 3. 关闭文件
    close(fd);

    // 重新打开读取
    fd = open("test.txt", O_RDONLY);
    if (fd == -1) {
        perror("open failed");
        return 1;
    }

    char buf[1024] = {0};
    read(fd, buf, sizeof(buf));
    printf("读取内容:%s", buf);

    close(fd);
    return 0;
}

5.2 Makefile

makefile

复制代码
SRC = main.c
APP = file_io_demo
FLAG = -g

$(APP):$(SRC)
	gcc $^ -o $@ $(FLAG)

clean:
	rm -f $(APP) test.txt

5.3 编译 & 运行

bash

运行

复制代码
# 编译
make
# 运行
./file_io_demo
# 清理
make clean

六、核心总结

  1. 文件 IO:基于系统调用,无缓存、用文件描述符,适合设备文件 / 实时场景;标准 IO 基于 C 库封装,有缓存、跨平台,适合普通文件;
  2. 文件操作流程:打开(open)→ 读写(read/write)→ 关闭(close),目录操作需用 opendir/readdir/closedir;
  3. Makefile:核心规则为 "目标:依赖 + 规则",通过内置 / 自定义变量实现通用编译模板,支持增量编译,是 Linux 工程编译的核心工具;
  4. 实战中需结合场景选择文件操作方式,通过 Makefile 简化编译流程,提升开发效率。
相关推荐
IT_陈寒2 小时前
Vue3 性能优化实战:从10秒到1秒的5个关键技巧,让你的应用飞起来!
前端·人工智能·后端
en-route2 小时前
Spring 框架下 Redis 会话存储应用实践
java·redis·spring
gambool2 小时前
新版chrome Edge浏览器不再支持手动添加cookie
前端·chrome·edge
JIngJaneIL2 小时前
基于Java酒店管理系统(源码+数据库+文档)
java·开发语言·数据库·vue.js·spring boot
一只爱吃糖的小羊2 小时前
React 避坑指南:“闭包陷阱“
前端·javascript·react.js
weixin_446260852 小时前
八、微调后模型使用及效果验证-1
前端·人工智能·chrome·微调模型
by__csdn2 小时前
大前端:定义、演进与实践全景解析
前端·javascript·vue.js·react.js·typescript·ecmascript·动画
颜颜yan_2 小时前
DevUI自定义开发实践:从零开始构建自定义组件和插件
android·java·数据库
带刺的坐椅3 小时前
Java 低代码平台的“动态引擎”:Liquor
java·javascript·低代码·groovy·liquor