
🔥个人主页:Cx330🌸
❄️个人专栏:《C语言》《LeetCode刷题集》《数据结构-初阶》《C++知识分享》
《优选算法指南-必刷经典100题》《Linux操作系统》:从入门到入魔
🌟心向往之行必能至
🎥Cx330🌸的简介:

目录
[一、理解 "文件"](#一、理解 “文件”)
[二、温故知新:C 标准库的文件 IO 操作](#二、温故知新:C 标准库的文件 IO 操作)
[2.1 C语言文件操作常用函数](#2.1 C语言文件操作常用函数)
[2.2 文件写入:fwrite(附加其他函数)](#2.2 文件写入:fwrite(附加其他函数))
[2.3 文件读取:fread(附加其他函数)](#2.3 文件读取:fread(附加其他函数))
[2.4 标准输入输出:stdin、stdout、stderr](#2.4 标准输入输出:stdin、stdout、stderr)
[3.1 系统调用接口介绍](#3.1 系统调用接口介绍)
[3.2 open函数详解](#3.2 open函数详解)
[3.3 系统调用实战:实现文件写入](#3.3 系统调用实战:实现文件写入)
[3.4 系统调用实战:实现文件读取](#3.4 系统调用实战:实现文件读取)
前言:
在 Linux 世界里,"一切皆文件" 是最核心的设计哲学之一。小到终端输入输出,大到网络通信、设备交互,底层都依赖基础 IO完成数据流转。无论你是后端开发、嵌入式工程师,还是内核爱好者,掌握基础 IO 的底层逻辑,都是打通 Linux 开发任督二脉的关键一步。
一、理解 "文件"
"文件" 的概念远比我们想象的宽泛,这是理解 IO 的前提:
- 狭义文件 :磁盘上的永久性存储文件,由 属性(元数据)+ 内容 组成,即使是 0KB 的空文件,也会占用磁盘空间存储属性;
- 广义文件 :Linux 下 "一切皆文件",键盘、显示器、网卡、进程等都被抽象为文件,统一通过 IO 接口操作,在之后的学习中会深入理解这一概念;
- 系统角度 :文件操作的本质是 进程对文件的操作,磁盘由操作系统管理,任何文件读写最终都要通过系统调用接口实现,C 库函数只是封装层。
二、温故知新:C 标准库的文件 IO 操作
在学习 Linux 系统 IO 之前,我们大多已经接触过 C 标准库的文件操作 ------ 这是跨平台的 "上层工具",也是理解底层原理的起点。
C 标准库提供了fopen、fclose、fread、fwrite、fprintf等封装好的函数,它们最大的特点是自带用户态缓冲区,并通过标准化接口实现了跨平台兼容。
举个简单的例子,用 C 库写入文件:
cpp
#include <stdio.h>
int main()
{
FILE* fp = fopen("test.txt", "w");
if (fp == NULL)
return -1;
fputs("Hello Linux IO\n", fp);
fclose(fp);
return 0;
}
2.1 C语言文件操作常用函数
这类函数封装了底层系统调用,自带用户态缓冲区,可在 Windows、Linux、macOS 等平台使用。
| 功能分类 | 函数名 | 函数原型(核心简化版) | 功能说明 | 关键备注 |
|---|---|---|---|---|
| 打开 / 关闭 | fopen |
**FILE *fopen(const char path, const char mode); | 打开指定路径的文件,返回FILE结构体指针(文件句柄) |
mode常用值:"r"(只读)、"w"(只写,覆盖创建)、"a"(追加)、"r+"(读写);失败返回NULL |
fclose |
*int fclose(FILE stream); | 关闭已打开的FILE句柄,刷新缓冲区数据到文件 |
成功返回0,失败返回EOF;关闭后FILE指针不可再使用 |
|
| 字节 / 块读写 | fread |
**size_t fread(void ptr, size_t size, size_t nmemb, FILE stream); | 从文件读取数据到内存缓冲区 | 返回实际读取的 "数据块个数"(nmemb),而非字节数;到达文件末尾可能返回小于传入的nmemb |
fwrite |
**size_t fwrite(const void ptr, size_t size, size_t nmemb, FILE stream); | 把内存缓冲区数据写入文件 | 返回实际写入的 "数据块个数";数据先存入用户态缓冲区,不立即落盘 | |
| 字符读写 | fgetc/getc |
*int fgetc(FILE stream); | 从文件读取单个字符 | 返回读取的字符(强转为int),到达末尾或失败返回EOF |
fputc/putc |
*int fputc(int c, FILE stream); | 向文件写入单个字符 | 成功返回写入的字符,失败返回EOF |
|
| 字符串读写 | fgets |
**char *fgets(char str, int n, FILE stream); | 从文件读取一行字符串(最多n-1个字符),自动添加'\0'终止符 |
遇到换行符'\n'或文件末尾停止;返回str指针,失败 / 末尾返回NULL |
fputs |
**int fputs(const char str, FILE stream); | 向文件写入字符串(不自动添加换行符) | 成功返回非负整数,失败返回EOF |
|
| 格式化读写 | fprintf |
**int fprintf(FILE stream, const char format, ...); | 按指定格式向文件写入数据(类似printf,输出目标为文件) |
成功返回写入的字符总数,失败返回负数 |
fscanf |
**int fscanf(FILE stream, const char format, ...); | 按指定格式从文件读取数据到变量(类似scanf,输入来源为文件) |
成功返回匹配并赋值的变量个数,失败 / 末尾返回EOF |
|
| 文件定位 | fseek |
*int fseek(FILE stream, long offset, int whence); | 移动文件读写指针到指定位置 | whence:SEEK_SET(文件开头)、SEEK_CUR(当前位置)、SEEK_END(文件末尾);成功返回0 |
ftell |
*long ftell(FILE stream); | 获取当前文件读写指针相对于文件开头的偏移量(字节数) | 成功返回偏移量,失败返回-1L |
|
rewind |
*void rewind(FILE stream); | 把文件读写指针重置到文件开头(等价于fseek(stream, 0, SEEK_SET)) |
无返回值,会清除文件的错误标记 | |
| 缓冲区操作 | fflush |
*int fflush(FILE stream); | 强制刷新用户态缓冲区,把数据写入底层文件(内核缓冲区) | 成功返回0,失败返回EOF;fflush(NULL)刷新所有打开的文件句柄 |
setvbuf |
**int setvbuf(FILE stream, char buf, int mode, size_t size); | 设置文件句柄的缓冲区类型和大小 | mode:_IOFBF(全缓冲)、_IOLBF(行缓冲)、_IONBF(无缓冲);成功返回0 |
|
| 错误处理 | ferror |
*int ferror(FILE stream); | 检查文件操作是否发生错误 | 有错误返回非 0,无错误返回0 |
feof |
*int feof(FILE stream); | 检查是否到达文件末尾 |
- 打开模式 :r (只读)、w(只写,清空创建)、a(追加)、r+(读写)、w+(读写,清空创建)、a+(读写,追加);
2.2 文件写入:fwrite(附加其他函数)
fwrite用于向文件写入数据,适用于二进制文件和文本文件
cpp
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main()
{
FILE *fp = fopen("log.txt", "w");
if (!fp)
{
perror("fopen");
return -1;
}
const char *message = "hello fwrite\n";
int count = 10;
// 循环写入
while (count--)
{
fwrite(message, strlen(message), 1, fp); // 不用 + 1
// fputs(message, fp);
// fprintf(fp, "hello fwrite: %d\n", cnt);
}
fclose(fp);
return 0;
}
2.3 文件读取:fread(附加其他函数)
fread用于从文件读取数据,需通过返回值判断读取结果
cpp
#include <stdio.h>
#include <string.h>
int main()
{
FILE *fp = fopen("load.txt", "r");
if (!fp)
{
perror("fopen");
return -1;
}
char outbuf[1024];
const char *msg = "hello Lotso!\n";
while (1)
{
size_t s = fread(outbuf, 1, strlen(msg), fp);
if (s > 0)
{
outbuf[s] = '\0';
printf("%s", buf);
}
// 判断是否到达文件末尾
if (feof(fp))
{
break;
}
}
fclose(fp);
return 0;
}
cpp
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main()
{
FILE *fp = fopen("log.txt", "r");
if(NULL == fp)
{
perror("fopen");
return 0;
}
char inbuffer[1024];
while(1)
{
// ftell的使用
// long pos = ftell(fp);
// printf("pos: %ld\n", pos);
// int ch = fgetc(fp);
// if(ch == EOF)
// {
// break;
// }
printf("%c\n", ch);
if(!fgets(inbuffer, sizeof(inbuffer), fp))
{
break;
}
printf("file : %s", inbuffer);
}
fclose(fp);
return 0;
}
2.4 标准输入输出:stdin、stdout、stderr
C 语言默认打开 3 个标准流,类型均为FILE*,对应系统的 3 个默认文件描述符:
- stdin:标准输入(键盘),对应文件描述符 0;
- stdout:标准输出(显示器),对应文件描述符 1;
- stderr:标准错误(显示器),对应文件描述符 2。
三、走进内核:文件相关的系统调用接口
如果说 C 库函数是 "快捷方式",那么系统调用 就是直接和内核对话的 "原生接口"。Linux 提供了open、close、read、write等系统调用,它们跳过了用户态缓冲区,直接操作内核态资源。
3.1 系统调用接口介绍
头文件:
<sys/types.h>、<sys/stat.h>、<fcntl.h>、<unistd.h>
|-----------|--------------|-----------|
| 系统调用 | 对应 C 库函数 | 功能描述 |
| open | fopen | 打开 / 创建文件 |
| read | fread | 从文件读取数据 |
| write | fwrite | 向文件写入数据 |
| close | fclose | 关闭文件 |
3.2 open函数详解

参数说明:
- pathname:文件路径(相对路径或绝对路径);
- flags :打开方式标志,必须包含以下之一,可搭配其他标志使用:
- 核心标志:O_RDONLY(只读)、O_WRONLY(只写)、O_RDWR(读写);
- 辅助标志:O_CREAT(文件不存在则创建)、O_APPEND(追加模式)、O_TRUNC(清空文件);
- mode :文件权限(如
0644、0755),仅当flags包含O_CREAT时有效; - 返回值 :成功返回文件描述符(非负整数),失败返回-
1
权限说明 :
mode参数指指定的是文件的 "默认权限",最终权限会被**umask** (权限掩码,以前就学习过了)修正,公式为:最终权限 = mode & ~umask。举例:默认umask为0002,因此mode=0666时,最终权限为0644。在下面我们还会再涉及到这个的,并且提到了一个就近原则

一个小demo理解位图:
cpp
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#define ONE (1<<0) // 1
#define TWO (1<<1) // 1
#define THREE (1<<2) // 4
#define FOUR (1<<3) // 8
#define FIVE (1<<4) // 16
void Print(int flags)
{
if(flags & ONE)
printf("ONE\n");
if(flags & TWO)
printf("TWO\n");
if(flags & THREE)
printf("THREE\n");
if(flags & FOUR)
printf("FOUR\n");
if(flags & FIVE)
printf("FIVE\n");
}
int main()
{
Print(ONE);
printf("\n");
Print(TWO);
printf("\n");
Print(ONE | TWO);
printf("\n");
Print(ONE | TWO | THREE);
printf("\n");
Print(ONE | TWO | THREE | FOUR);
printf("\n");
Print(TWO | THREE | FOUR | FIVE);
}
3.3 系统调用实战:实现文件写入
用open、write、close实现与 C 库fopen相同的功能:
cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
umask(0); // 清除默认权限掩码,确保创建文件权限正确
// 打开文件:只写模式,不存在则创建,权限0666
int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
if (fd < 0)
{
// 打开失败,fd为-1
perror("open"); // 打印错误信息
return 1;
}
const char *msg = "hello open\n";
int len = strlen(msg);
int count = 5;
while (count--)
{
// 写入数据:参数(文件描述符、数据地址、写入字节数)
write(fd, msg, len);
}
close(fd); // 关闭文件,释放文件描述符
return 0;
}

追加模式:
cpp
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main()
{
umask(0);
int fd = open("log.txt", O_CREAT | O_WRONLY | O_APPEND, 0666);
if(fd < 0)
{
perror("open");
return 1;
}
const char *msg = "hello world\n";
int cnt = 10;
while(cnt--)
{
write(fd, msg, strlen(msg));
}
close(fd);
return 0;
}
3.4 系统调用实战:实现文件读取
用open、read、close实现文件读取:
cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
// 只读模式打开文件
int fd = open("load.txt", O_RDONLY);
if (fd < 0)
{
perror("open");
return 1;
}
const char *msg = "hello world\n";
char buf[1024];
while (1)
{
// 读取数据:参数(文件描述符、缓冲区地址、读取字节数)
ssize_t s = read(fd, buf, strlen(msg));
if (s > 0)
{
// 成功读取到s个字节
printf("%s", buf);
}
else
{
// s=0表示文件末尾,s<0表示错误
break;
}
}
close(fd);
return 0;
}
补充:
cpp
// cat file.txt
int main(int argc, char *argv[])
{
if(argc != 2)
{
printf("Usage: %s filename\n", argv[0]); // ./myfile filename
return 1;
}
int fd = open(argv[1], O_RDONLY);
if(fd < 0)
{
perror("open");
return 2;
}
char inbuffer[128];
while(1)
{
ssize_t n = read(fd, inbuffer, sizeof(inbuffer)-1);
if(n > 0)
{
inbuffer[n] = 0;
printf("%s", inbuffer);
}
else if(n == 0)
{
printf("end of file!\n");
break;
}
else
{
perror("read");
break;
}
}
close(fd);
return 0;
}
总结与展望
Linux 基础 IO 的核心,是理解 "用户态 - 内核态 - 硬件" 三层数据流转的逻辑:
- C 库函数通过用户态缓冲区减少系统调用开销;
- 系统调用直接操作内核的文件描述符和页缓存;
- 内核通过延迟写、页缓存等机制提升磁盘 IO 性能。
掌握这些知识,不仅能帮你写出更高效的 IO 代码,也为后续学习网络 IO 、进程间通信 、内核驱动打下了坚实基础。