深入理解 Linux 基础 IO:从 C 库到系统调用的完整剖析

深入理解 Linux 基础 IO:从 C 库到系统调用的完整剖析

🔥个人主页:Cx330🌸

❄️个人专栏:《C语言》《LeetCode刷题集》《数据结构-初阶》《C++知识分享》

《优选算法指南-必刷经典100题》《Linux操作系统》:从入门到入魔

《Git深度解析》:版本管理实战全解

🌟心向往之行必能至


🎥Cx330🌸的简介:


目录

前言:

一、理解 “文件”

二、温故知新:C 标准库的文件 IO 操作

 2.1 C语言文件操作常用函数

2.2 文件写入:fwrite(附加其他函数)

2.3 文件读取:fread(附加其他函数)

2.4 标准输入输出:stdin、stdout、stderr

三、走进内核:文件相关的系统调用接口

3.1 系统调用接口介绍

3.2 open函数详解

3.3 系统调用实战:实现文件写入

3.4 系统调用实战:实现文件读取

总结与展望


前言:

在 Linux 世界里,“一切皆文件” 是最核心的设计哲学之一。小到终端输入输出,大到网络通信、设备交互,底层都依赖基础 IO完成数据流转。无论你是后端开发、嵌入式工程师,还是内核爱好者,掌握基础 IO 的底层逻辑,都是打通 Linux 开发任督二脉的关键一步。

一、理解 “文件”

“文件” 的概念远比我们想象的宽泛,这是理解 IO 的前提:

  • 狭义文件:磁盘上的永久性存储文件,由 属性(元数据)+ 内容 组成,即使是 0KB 的空文件,也会占用磁盘空间存储属性;
  • 广义文件Linux 下 “一切皆文件”,键盘、显示器、网卡、进程等都被抽象为文件,统一通过 IO 接口操作,在之后的学习中会深入理解这一概念;
  • 系统角度:文件操作的本质是 进程对文件的操作,磁盘由操作系统管理,任何文件读写最终都要通过系统调用接口实现,C 库函数只是封装层。

二、温故知新:C 标准库的文件 IO 操作

在学习 Linux 系统 IO 之前,我们大多已经接触过 C 标准库的文件操作 —— 这是跨平台的 “上层工具”,也是理解底层原理的起点。

C 标准库提供了fopenfclosefreadfwritefprintf等封装好的函数,它们最大的特点是自带用户态缓冲区,并通过标准化接口实现了跨平台兼容。

举个简单的例子,用 C 库写入文件:

#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 等平台使用。

功能分类函数名函数原型(核心简化版)功能说明关键备注
打开 / 关闭fopenFILE *fopen(const char *path, const char *mode);打开指定路径的文件,返回FILE结构体指针(文件句柄)mode常用值:"r"(只读)、"w"(只写,覆盖创建)、"a"(追加)、"r+"(读写);失败返回NULL
fcloseint fclose(FILE *stream);关闭已打开的FILE句柄,刷新缓冲区数据到文件成功返回0,失败返回EOF;关闭后FILE指针不可再使用
字节 / 块读写freadsize_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);从文件读取数据到内存缓冲区返回实际读取的 “数据块个数”(nmemb),而非字节数;到达文件末尾可能返回小于传入的nmemb
fwritesize_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);把内存缓冲区数据写入文件返回实际写入的 “数据块个数”;数据先存入用户态缓冲区,不立即落盘
字符读写fgetc/getcint fgetc(FILE *stream);从文件读取单个字符返回读取的字符(强转为int),到达末尾或失败返回EOF
fputc/putcint fputc(int c, FILE *stream);向文件写入单个字符成功返回写入的字符,失败返回EOF
字符串读写fgetschar *fgets(char *str, int n, FILE *stream);从文件读取一行字符串(最多n-1个字符),自动添加'\0'终止符遇到换行符'\n'或文件末尾停止;返回str指针,失败 / 末尾返回NULL
fputsint fputs(const char *str, FILE *stream);向文件写入字符串(不自动添加换行符)成功返回非负整数,失败返回EOF
格式化读写fprintfint fprintf(FILE *stream, const char *format, ...);按指定格式向文件写入数据(类似printf,输出目标为文件)成功返回写入的字符总数,失败返回负数
fscanfint fscanf(FILE *stream, const char *format, ...);按指定格式从文件读取数据到变量(类似scanf,输入来源为文件)成功返回匹配并赋值的变量个数,失败 / 末尾返回EOF
文件定位fseekint fseek(FILE *stream, long offset, int whence);移动文件读写指针到指定位置whenceSEEK_SET(文件开头)、SEEK_CUR(当前位置)、SEEK_END(文件末尾);成功返回0
ftelllong ftell(FILE *stream);获取当前文件读写指针相对于文件开头的偏移量(字节数)成功返回偏移量,失败返回-1L
rewindvoid rewind(FILE *stream);把文件读写指针重置到文件开头(等价于fseek(stream, 0, SEEK_SET)无返回值,会清除文件的错误标记
缓冲区操作fflushint fflush(FILE *stream);强制刷新用户态缓冲区,把数据写入底层文件(内核缓冲区)成功返回0,失败返回EOFfflush(NULL)刷新所有打开的文件句柄
setvbufint setvbuf(FILE *stream, char *buf, int mode, size_t size);设置文件句柄的缓冲区类型和大小mode_IOFBF(全缓冲)、_IOLBF(行缓冲)、_IONBF(无缓冲);成功返回0
错误处理ferrorint ferror(FILE *stream);检查文件操作是否发生错误有错误返回非 0,无错误返回0
feofint feof(FILE *stream);检查是否到达文件末尾
  • 打开模式r(只读)、w(只写,清空创建)、a(追加)、r+(读写)、w+(读写,清空创建)、a+(读写,追加);

2.2 文件写入:fwrite(附加其他函数)

fwrite用于向文件写入数据,适用于二进制文件和文本文件

#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用于从文件读取数据,需通过返回值判断读取结果

#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; } 
#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 提供了openclosereadwrite等系统调用,它们跳过了用户态缓冲区,直接操作内核态资源。

3.1 系统调用接口介绍

头文件:

<sys/types.h><sys/stat.h><fcntl.h><unistd.h>
系统调用对应 C 库函数功能描述
openfopen打开 / 创建文件
readfread从文件读取数据
writefwrite向文件写入数据
closefclose关闭文件

3.2 open函数详解

参数说明

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

一个小demo理解位图:

#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 系统调用实战:实现文件写入

openwriteclose实现与 C 库fopen相同的功能:

#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; } 

追加模式:

#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 系统调用实战:实现文件读取

openreadclose实现文件读取:

#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; } 

补充:

// 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进程间通信内核驱动打下了坚实基础。

Read more

深挖 DeepSeek 隐藏玩法·智能炼金术2.0版本

深挖 DeepSeek 隐藏玩法·智能炼金术2.0版本

前引:屏幕前的你还在AI智能搜索框这样搜索吗?“这道题怎么写”“苹果为什么红”“怎么不被发现翘课” ,。看到此篇文章的小伙伴们!请准备好你的思维魔杖,开启【霍格沃茨模式】,看我如何更新秘密的【知识炼金术】,我们一起来解锁更加刺激的剧情!友情提醒:《《《前方高能》》》 目录 在哪使用DeepSeek 如何对提需求  隐藏玩法总结 几个高阶提示词 职场打工人 自媒体创作 电商实战 程序员开挂 非适用场地 “服务器繁忙”如何解决 (1)硅基流动平台 (2)Chatbox + API集成方案 (3)各大云平台 搭建个人知识库 前置准备 下载安装AnythingLLM 选择DeepSeek作为AI提供商 创作工作区 导入文档 编辑  编辑 小编寄语 ——————————————————————————————————————————— 在哪使用DeepSeek 我们解锁剧情前,肯定要知道在哪用DeepSeek!咯,为了照顾一些萌新朋友,它的下载方式我放在下面了,拿走不谢!  (1)

By Ne0inhk
【AI大模型】DeepSeek + 通义万相高效制作AI视频实战详解

【AI大模型】DeepSeek + 通义万相高效制作AI视频实战详解

目录 一、前言 二、AI视频概述 2.1 什么是AI视频 2.2 AI视频核心特点 2.3 AI视频应用场景 三、通义万相介绍 3.1 通义万相概述 3.1.1 什么是通义万相 3.2 通义万相核心特点 3.3 通义万相技术特点 3.4 通义万相应用场景 四、DeepSeek + 通义万相制作AI视频流程 4.1 DeepSeek + 通义万相制作视频优势 4.1.1 DeepSeek 优势 4.1.2 通义万相视频生成优势 4.2

By Ne0inhk
【DeepSeek微调实践】DeepSeek-R1大模型基于MS-Swift框架部署/推理/微调实践大全

【DeepSeek微调实践】DeepSeek-R1大模型基于MS-Swift框架部署/推理/微调实践大全

系列篇章💥 No.文章01【DeepSeek应用实践】DeepSeek接入Word、WPS方法详解:无需代码,轻松实现智能办公助手功能02【DeepSeek应用实践】通义灵码 + DeepSeek:AI 编程助手的实战指南03【DeepSeek应用实践】Cline集成DeepSeek:开源AI编程助手,终端与Web开发的超强助力04【DeepSeek开发入门】DeepSeek API 开发初体验05【DeepSeek开发入门】DeepSeek API高级开发指南(推理与多轮对话机器人实践)06【DeepSeek开发入门】Function Calling 函数功能应用实战指南07【DeepSeek部署实战】DeepSeek-R1-Distill-Qwen-7B:本地部署与API服务快速上手08【DeepSeek部署实战】DeepSeek-R1-Distill-Qwen-7B:Web聊天机器人部署指南09【DeepSeek部署实战】DeepSeek-R1-Distill-Qwen-7B:基于vLLM 搭建高性能推理服务器10【DeepSeek部署实战】基于Ollama快速部署Dee

By Ne0inhk

DeepSeek各版本说明与优缺点分析_deepseek各版本区别

DeepSeek各版本说明与优缺点分析 DeepSeek是最近人工智能领域备受瞩目的一个语言模型系列,其在不同版本的发布过程中,逐步加强了对多种任务的处理能力。本文将详细介绍DeepSeek的各版本,从版本的发布时间、特点、优势以及不足之处,为广大AI技术爱好者和开发者提供一份参考指南。 1. DeepSeek-V1:起步与编码强劲 DeepSeek-V1是DeepSeek的起步版本,这里不过多赘述,主要分析它的优缺点。 发布时间: 2024年1月 特点: DeepSeek-V1是DeepSeek系列的首个版本,预训练于2TB的标记数据,主打自然语言处理和编码任务。它支持多种编程语言,具有强大的编码能力,适合程序开发人员和技术研究人员使用。 优势: * 强大编码能力:支持多种编程语言,能够理解和生成代码,适合开发者进行自动化代码生成与调试。 * 高上下文窗口:支持高达128K标记的上下文窗口,能够处理较为复杂的文本理解和生成任务。 缺点: * 多模态能力有限:该版本主要集中在文本处理上,缺少对图像、语音等多模态任务的支持。 * 推理能力较弱:尽管在自然语言

By Ne0inhk