Linux文件I/O与系统调用详解
2026/8/24 20:52:22 网站建设 项目流程

摘要

在 Linux 操作系统中,“一切皆文件”(Everything is a file)是极其核心的设计哲学。除了普通的文本与二进制文件外,网络 Socket、管道、硬件设备(如串口、显示屏、触摸屏等)在应用层均被抽象为文件进行操作。

本文将聚焦于 Linux 系统层面的文件 I/O(File I/O,又称系统调用 I/O),深入讲解其核心 API(open、read、write、lseek、close)、文件描述符(FD)的分配机制,并深度对比标准 I/O文件 I/O在实现原理、缓冲区机制及适用场景上的本质区别。

一、什么是文件 I/O?

文件 I/O 是 Linux内核直接提供的文件操作接口,属于系统调用(System Call)

与 ANSI C 标准库提供的标准 I/O(如 fopen、fread、fwrite)不同,文件 I/O 不经过 C 库缓冲,直接由内核处理,因此实时性更高,常用于设备文件操作或底层的网络编程中。

标准 I/O vs 文件 I/O 架构对比

[file-tag: code-generated-file-fdtablepng]

二、关键概念:文件描述符(File Descriptor, FD)

在标准 I/O 中,我们使用文件流指针 FILE * 来标记打开的文件;而在文件 I/O 中,内核使用文件描述符(fd)来表示被打开的文件。

1. 文件描述符的本质

文件描述符是一个小的非负整数

每当进程打开一个文件,内核会在进程的“文件描述符表”中分配一个索引,并将其返回给应用层。

Linux 操作系统默认可分配的文件描述符范围通常为 0 ~ 1023(共 1024 个)。

2. 系统默认打开的三个文件描述符

每个 Linux 进程在启动时,默认都已经打开了三个文件描述符,分别对应标准 I/O 中的三个流:

标准 I/O 文件流指针

文件 I/O 描述符

宏定义(<unistd.h>)

对应设备

stdin

0

STDIN_FILENO

标准输入(键盘)

stdout

1

STDOUT_FILENO

标准输出(显示屏/终端)

stderr

2

STDERR_FILENO

标准错误(显示屏/终端)

3. FD 的分配原则

最小未被使用原则:操作系统分配 FD 时,总是挑选当前列表中最小的、且未被占用的非负整数。

[file-tag: code-generated-file-fileioarchitecturepng]

三、文件 I/O 核心 API 详解

需要包含的头文件:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

1. 打开/创建文件:open()

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_APPEND: 追加写入

mode: 当创建新文件(指定 O_CREAT)时设置权限(如 0664)。

返回值:成功返回文件描述符 (fd),失败返回 -1。

标准 I/O 与 文件 I/O 模式对应关系

标准 I/O (fopen)

文件 I/O 标志 (open 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

读写,不存在则创建,末尾追加

2. 关闭文件:close()

int close(int fd);

功能:关闭指定的文件描述符,释放内核资源。

注意:如果程序中打开文件后未进行 close(),会导致文件描述符泄漏(进程可用的 FD 资源被耗尽)。

3. 读取文件:read()

ssize_t read(int fd, void *buf, size_t count);

参数

fd: 打开的文件描述符。

buf: 存放读取数据的内存首地址。

count: 期望读取的字节数。

返回值

> 0: 实际读取到的字节数。

= 0: 读到文件末尾(EOF)。

-1: 读取失败。

4. 写入文件:write()

ssize_t write(int fd, const void *buf, size_t count);

参数

fd: 打开的文件描述符。

buf: 待写入数据的内存首地址。

count: 期望写入的字节数。

返回值:成功返回实际写入的字节数,失败返回 -1。

5. 文件读写位置定位:lseek()

off_t lseek(int fd, off_t offset, int whence);

参数

    • offset: 相对于起始位置的偏移量(字节,可正可负)。
    • whence: 基准位置:

SEEK_SET: 文件开头

SEEK_CUR: 当前读写位置

SEEK_END: 文件末尾

返回值:成功返回当前读写位置到文件开头的总字节数,失败返回 -1。

常用实用技巧:

1.获取文件大小
off_t filesize = lseek(fd, 0, SEEK_END);

2.创建空洞文件(抢占磁盘空间)
int fd = open("hole.file", O_WRONLY | O_CREAT | O_TRUNC, 0664);
lseek(fd, 1024 * 1024 * 10, SEEK_SET); // 偏移到 10MB 处
write(fd, "\0", 1); // 写入一个字节,形成空洞文件

四、实战演练:基于文件 I/O 实现文件复制(cp)

以下示例演示如何利用 open / read / write / close 实现高效的文件拷贝工具:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int file_copy(const char *srcfile, const char *dstfile)
{
// 1. 打开源文件(只读)
int fdsrc = open(srcfile, O_RDONLY);
if (fdsrc == -1) {
perror("open srcfile error");
return -1;
}

// 2. 打开目标文件(只写,不存在则创建,存在则清空,权限0664)
int fddst = open(dstfile, O_WRONLY | O_CREAT | O_TRUNC, 0664);
if (fddst == -1) {
perror("open dstfile error");
close(fdsrc);
return -1;
}

// 3. 循环读写
char buff[1024] = {0};
ssize_t size = 0;
while ((size = read(fdsrc, buff, sizeof(buff))) > 0) {
write(fddst, buff, size);
}

// 4. 关闭文件描述符
close(fdsrc);
close(fddst);

return 0;
}

int main(int argc, const char *argv[])
{
if (argc < 3) {
printf("Usage: %s <srcfile> <dstfile>\n", argv[0]);
return -1;
}

if (file_copy(argv[1], argv[2]) == 0) {
printf("File copy successfully!\n");
}

return 0;
}

五、文件 I/O 与 标准 I/O 全方位对比

对比维度

标准 I/O (Standard I/O)

文件 I/O (File I/O)

操作对象

FILE *(文件流指针)

int fd(文件描述符)

实现层级

ANSI C 标准库函数(高层抽象)

Linux 内核系统调用(底层接口)

可移植性

(支持 POSIX、Windows、macOS 等)

(特定于 POSIX / Linux 系统)

缓冲区机制

有缓冲区(行缓冲、全缓冲、无缓冲),减少内核切换,提高小数据频繁读写效率

无用户缓冲区,实时性高

适用场景

普通文本/二进制文件的顺序/格式化读写

设备文件(串口、网络 Socket、驱动硬件)

补充:标准 I/O 的三种缓冲区模式

1.行缓冲(Line Buffered):大小通常为1024 字节 (1KB)。常见于终端设备(stdin、stdout)。刷新条件:遇到 \n、缓冲区满、程序结束或调用 fflush()。

2.全缓冲(Fully Buffered):大小通常为4096 字节 (4KB)。常见于普通磁盘文件操作。刷新条件:缓冲区满、关闭文件、程序结束或调用 fflush()。

3.无缓冲(Unbuffered):数据直接写入目的地,如标准错误流 stderr。

六、辅助拓展函数:格式化与字符串截取

在 Linux C 编程中,结合文件操作常常用到以下辅助函数:

1. 格式化输出函数

fprintf(FILE *stream, const char *format, ...):向指定流写入格式化数据。

dprintf(int fd, const char *format, ...):向指定文件描述符写入格式化数据。

sprintf(char *str, const char *format, ...):格式化输出到字符串数组中,用于字符串拼接与格式转换。

2. 字符串截取:strtok()

char *strtok(char *str, const char *delim);

功能:根据分隔符 delim 切割字符串 str。

注意:strtok 会修改原字符串(将分隔符替换为 \0),首次调用传 str,后续继续切割需传 NULL。

总结

1.Linux 下一切皆文件,硬件设备、文件、管道、Socket 均可使用文件 I/O 统一操作。

2.文件 I/O 返回并使用的是文件描述符(fd),分配遵从最小未被使用原则

3.进行普通文件读写优先考虑标准 I/O(含缓冲区,减少系统调用开销);而在硬件交互、设备驱动、实时性要求高的场景下,则应使用文件 I/O

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询