炸了!Linux 文件操作的 “终极密码”:缓冲区是 “数据客栈”,描述符是 “通关密匙”,连 C 库都在偷偷这么玩!

炸了!Linux 文件操作的 “终极密码”:缓冲区是 “数据客栈”,描述符是 “通关密匙”,连 C 库都在偷偷这么玩!

文 章 目 录

💻作 者 简 介:曾 与 你 一 样 迷 茫,现 以 经 验 助 你 入 门 Linux。
💡个 人 主 页:@笑口常开xpr 的 个 人 主 页
📚系 列 专 栏:Linux 探 索 之 旅:从 命 令 行 到 系 统 内 核
✨代 码 趣 语:缓 冲 区 是 数 据 暂 歇 的 客 栈,\n 是 催 发 的 号 角,满 是 起 程 的 船 票 - - - 而 文 件 描 述 符 那 枚 铜 匙,总 在 fflush 挥 手 时,
把 暂 存 的 故 事,郑 重 塞 进 磁 盘 的 褶 皱 里。
💪代 码 千 行,始 于 坚 持,每 日 敲 码,进 阶 编 程 之 路。
📦gitee 链 接:gitee
在这里插入图片描述


         学 Linux 文 件 操 作 时,你 是 否 遇 过 这 些 困 惑:printf 输 出 不 即 时、fwrite 与 write 结 果 不 一 致、程 序 结 束 数 据 丢 失?其 实 问 题 根 源 在 “缓 冲 区” 和 “文 件 描 述 符”。本 文 以 “代 码 实 测 + 底 层 拆 解” 为 核 心:先 用 5 组 对 比 代 码 讲 透 缓 冲 区 3 种 刷 新 策 略,再 手 把 手 模 拟 实 现 C 文 件 标 准 库(不 依 赖 stdio.h,直 接 调 用 系 统 调 用),帮 你 打 通 “会 用” 到 “懂 原 理” 的 链 路。


一、文 件 描 述 符

1、重 新 认 识 缓 冲 区

#include <stdio.h> #include <string.h> #include <unistd.h> int main(){const char* fstr ="hello fwrite\n";const char* str ="hello write\n";//Cprintf("hello printf\n");//stdout ---> 1fprintf(stdout,"hello fprintf\n");//stdout ---> 1fwrite(fstr,strlen(fstr),1,stdout);//stdout ---> 1//close(1);//操作系统提供的系统接口write(1,str,strlen(str));//stdout ---> 1fork();return0;}
在这里插入图片描述


         代 码 的 结 果 为 直 接 输 出 时 显 示 正 常 输 出,输 出 到 文 件 中 时 C 语 言 的 接 口 输 出 了 2 次,系 统 调 用 的 函 数 输 出 了 1 次。

原 因:向 显 示 器 输 出 为 行 缓 冲 方 式 会 依 次 输 出 到 显 示 器 中。当 向 文 件 中 输 出 时,缓 冲 方 式 由 行 缓 冲 变 成 了 全 缓 冲。即 遇 到 \n,不 在 刷 新,而 是 等 缓 冲 区 被 写 满 才 刷 新。

         首 先 write 通 过 缓 冲 区 直 接 刷 新,文 件 刷 新 为 全 缓 冲,C 接 口 的 函 数 的 内 容 被 储 存 在 C 语 言 提 供 的 用 户 级 缓 冲 区 中,fork 之 后 创 建 子 进 程,父 子 进 程 的 数 据 共 享,子 进 程 进 行 写 时 拷 贝,C 语 言 提 供 的 缓 冲 区 里 的 数 据 被 拷 贝 了 2 份,在 进 程 退 出 时,C 语 言 的 缓 冲 区 被 刷 新。

#include <stdio.h> #include <string.h> #include <unistd.h> int main(){const char* fstr ="hello fwrite\n";const char* str ="hello write\n";printf("hello printf\n");//stdout ---> 1sleep(2);fprintf(stdout,"hello fprintf\n");//stdout ---> 1sleep(2);fwrite(fstr,strlen(fstr),1,stdout);//stdout ---> 1sleep(2);write(1,str,strlen(str));//stdout ---> 1sleep(5);return0;}
在这里插入图片描述


         前 6 秒 时 C 接 口 的 函 数 被 存 储 在 C 语 言 的 缓 冲 区 中,write 函 数 通 过 缓 冲 区 直 接 写 入 文 件 中,当 进 程 结 束 时 会 刷 新 缓 冲 区 将 数 据 放 入 文 件 中。文 件 刷 新 为 全 缓 冲,\n 不 会 刷 新 缓 冲 区,而是 等 缓 冲 区 写 满 才 会 被 刷 新。


#include <stdio.h> #include <string.h> #include <unistd.h> int main(){const char* fstr ="hello fwrite\n";const char* str ="hello write\n";//Cprintf("hello printf\n");//stdout ---> 1fprintf(stdout,"hello fprintf\n");//stdout ---> 1fwrite(fstr,strlen(fstr),1,stdout);//stdout ---> 1close(1);//操作系统提供的系统接口write(1,str,strlen(str));//stdout ---> 1fork();return0;}
在这里插入图片描述


         和 第 1 次 代 码 的 结 果 不 同,添 加 close 后,C 语 言 的 接 口 输 出 了 1 次,系 统 调 用 的 接 口 没 有 输 出。


#include <stdio.h> #include <string.h> #include <unistd.h> int main(){const char* fstr ="hello fwrite";printf("hello printf");//stdout ---> 1fprintf(stdout,"hello fprintf");//stdout ---> 1fwrite(fstr,strlen(fstr),1,stdout);//stdout ---> 1close(1);return0;}
在这里插入图片描述


         如 果 去 掉 \n,代 码 没 有 输 出。


#include <stdio.h> #include <string.h> #include <unistd.h> int main(){const char* str ="hello fwrite";write(1,str,strlen(str));//stdout ---> 1close(1);return0;}
在这里插入图片描述


         代 码 没 有 \n,和 C 语 言 的 接 口 不 同,系 统 调 用 的 接 口 可 以 输 出。


总 结

  1. 缓 冲 区 一 定 不 在 操 作 系 统 内 部,不 是 系 统 级 别 的 缓 冲 区。write 可 以 输 出 是 因 为 write 直 接 将 字 符 串 写 入 了 缓 冲 区 中,close 对 显 示 没 有 影 响。
  2. C 语 言 会 提 供 一 个 用 户 级 缓 冲 区,C 语 言 的 接 口 函 数 传 递 的 数 据 实 际 上 储 存 在 C 语 言 提 供 的 用 户 级 缓 存 区 中,当 调 用 \nfclose 等 可 以 刷 新 缓 冲 区 时,才 会 刷 新 C 语 言 提 供 的 缓 冲 区,并 调 用 write 函 数,将 数 据 写 入 操 作 系 统 中。
  3. C 语 言 的 文 件 操 作 绕 不 开 FILE,FILE 中 包 含 文 件 描 述 符 fd,FILE 里 面 还 有 对 应 打 开 文 件 的 缓 冲 区 字 段。这 个 FILE 对 象 属 于 用 户,语 言 都 属 于 用 户 层。
  4. 显 示 器 的 文 件 刷 新 方 案 是 行 刷 新,所 以 在 printf 执 行 完 成 后 就 会 立 即 遇 到 \n 的 时 候,将 数 据 进 行 刷 新。刷 新 的 本 质 是 将 数 据 通 过 1 + write 通 过 write 接 口 写 入 到 内 核 中。

目 前 我 们 认 为,只 要 将 数 据 刷 新 进 入 了 内 核 中,数 据 就 会 被 刷 新 进 入 硬 件 中。

在这里插入图片描述

2、exit 和 _exit

exit 和 _exit

         exit 是 C 语 言 的 接 口,退 出 时 会 刷 新 C 语 言 提 供 的 缓 冲 区,然 后 调 用 _exit 退 出,_exit 是 系 统 调 用,直 接 释 放 进 程 不 会 对 数 据 进 行 刷 新。


3、缓 冲 区 的 刷 新 问 题

操 作 系 统 会 维 护 缓 冲 区。

(1)无 缓 冲

         无 缓 冲 区 是 一 种 写 透 模 式,不 要 在 缓 冲 区 中 做 出 各 种 数 据 残 留,直 接 刷 新,不 能 等 待,没 有 进 行 各 种 刷 新 策 略。

(2)行 缓 冲

         不 刷 新 直 到 遇 到 \n 才 会 刷 新。默 认 向 显 示 器 输 出 采 用 行 刷 新 是 因 为 显 示 器 是 给 人 看 的,人 每 次 看 数 据 符 合 每 次 看 一 行 的 习 惯,需 要 尽 可 能 快 的 把 数 据 刷 新 出 来。

(3)全 缓 冲

         缓 冲 区 满 了 才 会 刷 新 缓 冲 区。在 向 普 通 文 件 写 入 时 为 了 提 高 效 率 刷 新 时 不 需 要 实 时 观 看 所 以 采 用 全 缓 冲。

         根 据 这 3 种 情 况 来 决 定 什 么 时 候 调 用 write 接 口 的 问 题。fflush 的 底 层 会 封 装 write。缓 冲 区 中 存 储 的 数 据 越 多,效 率 越 高。


4、进 程 退 出

         进 程 退 出 时 也 会 刷 新 缓 冲 区。
示 例 1

#include <stdio.h> #include <string.h> #include <unistd.h> int main(){printf("hello world");return0;}
在这里插入图片描述


示 例 2

#include <stdio.h> #include <string.h> #include <unistd.h> int main(){printf("hello world");close(1);return0;}
在这里插入图片描述


         printf(“hello world”); 若 未 包 含 换 行 符 \n 且 程 序 未 结 束 或 未 手 动 刷 新,数 据 会 暂 时 留 在 缓 冲 区 中,不 会 立 即 显 示 在 终 端 上。当 使 用 close(1) 后,文 件 描 述 符 被 关 掉,会 导 致 缓 冲 区 中 的 数 据未 来 得 及 刷 新 就 丢 失,最 终 无 法 在 显 示 器 上 显 示。


5、C 语 言 提 供 缓 冲 区 的 原 因

  1. 解 决 效 率 问 题,硬 件 设 备 的 读 写 速 度 远 慢 于 CPU 和 内 存 的 处 理 速 度。缓 冲 区 用 于 临 时 存 储 待 输 出 或 待 读 取 的 数 据。程 序 先 将 数 据 写 入 缓 冲 区,待 缓 冲 区 满、触 发 特 定 条 件 或 程 序 结 束 时,再 一 次 性 将 缓 冲 区 数 据 写 入 硬 件 设 备。这 大 幅 减 少 了 硬 件 I/O 的 次 数,从 而 提 升 整 体 效 率。
  2. 标 准 I/O 库 通 过 缓 冲 区 批 量 处 理 数 据,将 多 次 小 的 I/O 请 求 合 并 为 一 次 大 的 请 求,从 而 减 少 系 统 调 用 的 次 数,降 低 开 销。
  3. 优 化 用 户 交 互 体 验。数 据 暂 存 在 缓 冲 区,直 到 遇 到 换 行 符 \n 才 刷 新 到 屏 幕。这 种 设 计 符 合 人 类 阅 读 习 惯,用 户 更 希 望 看 到 完 整 的 一 行 内 容,而 非 字 符 逐 个 零 散 显 示。

二、模 拟 实 现 C 文 件 标 准 库

1、为 什 么 要 “造 轮 子”?

         我 们 平 时 写 C 语 言 文 件 操 作 时,习 惯 直 接 调 用 标 准 库 的 fopen、fwrite、fclose - - - 这 些 函 数 好 用,但 你 有 没 有 想 过:

  1. 为 什 么 fwrite 不 是 “写 了 就 立 刻 到 文 件”?
  2. FILE 结 构 体 里 到 底 存 了 什 么?
  3. 标 准 库 是 怎 么 跟 操 作 系 统 交 互 的?

         这 是 一 个 简 化 版 的 C 文 件 操 作 库:手 动 实 现 了 _fopen、_fwrite、_fflush、_fclose,没 有 依 赖 标 准 库 的 stdio.h,而 是 直 接 调 用 操 作 系 统 的 底 层 接 口(open、write、close),还 加 入 了 核 心 的 缓 冲 区 机 制。FILE 中 的 缓 冲 区 的 意 义 是 使 用 C 语 言 的 接 口 更 快,节 省 时 间。

2、核 心 原 理

         在 拆 解 代 码 前,先 搞 懂 3 个 核 心 概 念:系 统 调 用、缓 冲 区、刷 新 策 略 - - - 这 是 所 有 高 级 语 言 文 件 操 作 的 “通 用 逻 辑”。

(1)系 统 调 用 与 用 户 层 封 装

         Linux 操 作 系 统 提 供 了 最 底 层 的 文 件 操 作 接 口,称 为 系 统 调 用(如 open、write、close)。但 系 统 调 用 的 “开 销 很 高” - - - 每 次 调 用 都 要 从 用 户 态 切 换 到 内 核 态,频 繁 调 用 会 拖 慢 程 序。

         C 标 准 库 的 stdio 系 列 函 数(fopen、fwrite)本 质 是 对 系 统 调 用 的 封 装,核 心 目 的 是:在 用 户 层 加 一 层 “缓 冲 区”,减 少 系 统 调 用 的 次 数,从 而 提 高 IO 效 率。

         我 们 的 代 码 也 遵 循 这 个 逻 辑:

  1. 用 _FILE 结 构 体 封 装 文 件 描 述 符(fileno)和 缓 冲 区;
  2. _fopen 封 装 open 系 统 调 用;
  3. _fwrite 先 写 缓 冲 区,满 了 再 调 用 write;
  4. _fclose 先 刷 新 缓 冲 区,再 调 用 close。

(2)缓 冲 区

为 什 么 缓 冲 区 能 提 高 效 率?
举 个 例 子:
         如 果 要 写 1000 个 字 符 到 文 件,直 接 调 用 write 需 要 1000 次 系 统 调 用;但 如 果 先 把 字 符 存 到 1024 字 节 的 缓 冲 区,满 了 再 调 用 1 次 write,系 统 调 用 次 数 从 1000 降 到 1 - - - 效 率 天 差 地 别。
         代 码 里,缓 冲 区 通 过 FILE 结 构 体 的 outbuffer(字 符 数 组)和out_pos(当 前 缓 冲 区 已 用 长 度)实 现,缓 冲 区 大 小 由 SIZE 宏 定 义 为 1024 字 节。

(3)缓 冲 刷 新 的 3 种 策 略

         缓 冲 区 的 数 据 不 会 一 直 存 着,需 要 在 特 定 时 机 “刷 新” 到 磁 盘,代 码 里 定 义 了 3 种 刷 新 策 略(通 过 flag 控 制):

FLUSH_NOW:无 缓 冲,写 入 后 立 即 刷 新
FLUSH_LINE:行 缓 冲,遇 到 \n 时 刷 新
FLUSH_ALL:全 缓 冲,缓 冲 区 满 了 才 刷 新。

3、代 码 拆 解

         接 下 来 逐 文 件 解 析 代 码,搞 懂 每 个 函 数 的 核 心 逻 辑。

(1)头 文 件 myfile.h

         头 文 件 的 作 用 是 “对 外 声 明” - - - 定 义 结 构 体、宏、函 数 接 口,让 其 他 文 件 能 复 用。

#ifndef __MYFILE_H__ // 防止头文件重复包含(比#pragma once更通用) #define __MYFILE_H__ // 引入依赖的系统头文件 #include<stdlib.h>// malloc/free #include<string.h>// memcpy/strcmp #include<sys/types.h> #include<sys/stat.h>// 文件状态相关 #include<fcntl.h>// open系统调用的标志(O_CREAT、O_WRONLY等) #include<assert.h>// 断言(调试用) #include<unistd.h>// close/write/sleep系统调用// 1. 宏定义:缓冲区大小 + 刷新策略 #define SIZE1024// 输出缓冲区大小:1024字节 #define FLUSH_NOW1// 立即刷新(无缓冲) #define FLUSH_LINE2// 行刷新(遇\n刷) #define FLUSH_ALL4// 全缓冲(满了刷)// 2. 自定义文件结构体:替代标准库的FILE typedef struct IO_FILE{ int fileno;// 文件描述符(操作系统给文件的唯一标识) int flag;// 缓冲刷新策略(FLUSH_NOW/FLUSH_LINE/FLUSH_ALL)// int in_pos; // (未实现)输入缓冲区当前位置// char inbuffer[SIZE]; // (未实现)输入缓冲区 char outbuffer[SIZE];// 输出缓冲区:存待写入文件的数据 int out_pos;// 输出缓冲区当前已用长度(从0开始)}_FILE;// 重命名为_FILE,简化使用// 3. 函数声明:对外提供的文件操作接口 _FILE*_fopen(const char* filename,const char* flag);// 打开文件 int _fwrite(_FILE* fp,const char* msg, int len);// 写入数据void_fclose(_FILE* fp);// 关闭文件void_fflush(_FILE* fp);// 强制刷新缓冲区 #endif 

关 键 点

  1. 用 #ifndef MYFILE_H 防 止 头 文 件 被 重 复 包 含(比 如 main.c 和 myfile.c 都 包 含 myfile.h,编 译 时 不 会 报 错)。
  2. _FILE 结 构 体 只 实 现 了 输 出 缓 冲 区,输 入 缓 冲 区(inbuffer/in_pos)被 注 释 了。
  3. fileno 是 核 心:操 作 系 统 通 过 文 件 描 述 符 识 别 文 件,所 有 底 层 操 作 都 依 赖 它。

(2)文 件 myfile.c

         myfile.c 负 责 把 myfile.h 声 明 的 接 口 落 地,核 心 是 4 个 函 数:_fopen、_fwrite、_fflush、_fclose。

1. _fopen

         _fopen 的 作 用 是 根 据 用 户 传 入 的 “打 开 模 式”(flag),调 用 open 系 统 调 用 创 建 文 件 描 述 符,再 分 配 并 初 始 化 _FILE 结 构 体。

#include"myfile.h" #define FILE_MODE0666// 文件创建时的权限(可读可写,所有者/组/其他用户) _FILE*_fopen(const char* filename,const char* flag){assert(filename);// 调试断言:如果filename为NULL,直接崩溃(避免非法访问) int open_flags =0;// 传给open系统调用的标志 int fd =-1;// 文件描述符(初始化为-1,代表无效)// 1. 根据打开模式(flag)设置open的标志if(strcmp(flag,"w")==0)// 写模式:创建文件(不存在则建)、只写、覆盖原有内容{ open_flags =O_CREAT|O_WRONLY|O_TRUNC;// O_CREAT:文件不存在则创建;O_WRONLY:只写;O_TRUNC:清空原有内容 fd =open(filename, open_flags,FILE_MODE);// 创建文件时需指定权限}elseif(strcmp(flag,"a")==0)// 追加模式:创建文件、只写、在文件末尾追加{ open_flags =O_CREAT|O_WRONLY|O_APPEND;// O_APPEND:每次写都追加到文件末尾 fd =open(filename, open_flags,FILE_MODE);}elseif(strcmp(flag,"r")==0)// 读模式:只读(文件必须存在){ open_flags =O_RDONLY; fd =open(filename, open_flags);// 读模式不需要权限参数}else{returnNULL;// 不支持的模式,返回NULL}// 2. 检查open是否成功(fd == -1代表失败,比如文件不存在、权限不够)if(fd ==-1){returnNULL;}// 3. 分配_FILE结构体内存(用户层的文件对象) _FILE* fp =(_FILE*)malloc(sizeof(_FILE));if(fp ==NULL)// 检查内存是否分配成功(避免内存不足导致崩溃){returnNULL;}// 4. 初始化_FILE结构体 fp->fileno = fd;// 绑定文件描述符 fp->flag =FLUSH_ALL;// 默认使用“全缓冲”策略 fp->out_pos =0;// 输出缓冲区初始为空(已用长度为0)return fp;// 返回用户层的文件指针}

关 键 点

  1. FILE_MODE 0666:文 件 权 限 的 八 进 制 表 示,rw-rw-rw-(所 有 者、组、其 他 用 户 都 有 读 写 权 限),但 实 际 权 限 会 受 umask 影 响(比 如 默 认 umask 0022,实 际 权 限 会 变 成 0644);
  2. open 的 标 志 组 合:不 同 模 式 对 应 不 同 的 标 志,比 如 w 模 式 必 须 加 O_TRUNC(清 空 文 件),a 模 式 必 须 加 O_APPEND(追 加);
  3. 内 存 分 配 检 查:malloc 可 能 失 败(比 如 内 存 不 足),必 须 检 查 fp == NULL,否 则 后 续 操 作 会 崩 溃。

2. _fwrite

         _fwrite:不 直 接 写 文 件,而 是 先 把 数 据 拷 贝 到 缓 冲 区,再 根 据 flag 判 断 是 否 需 要 刷 新 到 磁 盘。

int _fwrite(_FILE* fp,const char* s, int len){// 1. 把数据拷贝到输出缓冲区(当前代码未处理缓冲区溢出!)memcpy(&fp->outbuffer[fp->out_pos], s, len); fp->out_pos += len;// 更新缓冲区已用长度// 2. 根据刷新策略,判断是否需要调用write刷新到文件if(fp->flag &FLUSH_NOW)// 立即刷新:不管缓冲区是否满,直接写{write(fp->fileno, fp->outbuffer, fp->out_pos); fp->out_pos =0;// 刷新后,缓冲区重置为空}elseif(fp->flag &FLUSH_LINE)// 行刷新:遇到\n才刷新{if(fp->outbuffer[fp->out_pos -1]=='\n')// 检查最后一个字符是否是\n{write(fp->fileno, fp->outbuffer, fp->out_pos); fp->out_pos =0;}}elseif(fp->flag &FLUSH_ALL)// 全缓冲:缓冲区满了才刷新{if(fp->out_pos ==SIZE)// 已用长度等于缓冲区大小,满了{write(fp->fileno, fp->outbuffer, fp->out_pos); fp->out_pos =0;}}return len;// 返回写入的字节数(简化处理,未检查实际写入量)}

关 键 点

  1. 缓 冲 区 溢 出 风 险:当 前 代 码 直 接 memcpy,如 果 fp->out_pos + len > SIZE(比 如 剩 余 缓 冲 区 只 有 50 字 节,要 写 100 字 节),会 导 致 数 据 溢 出 outbuffer,破 坏 内 存 - - - 这 是 严 重 bug,后 续 优 化 会 解 决。
  2. 刷 新 策 略 的 实 际 效 果:当 前 _fopen 默 认 用 FLUSH_ALL,即 使 写 入 的 字 符 串 有 \n,也 不 会 立 即 刷 新,只 会 在 缓 冲 区 满(1024 字 节)或 _fclose 时 刷 新。

3. _fflush

         _fflush 的 作 用 是 “不 管 缓 冲 区 是 否 满、是 否 有 \n”,强 制 把 缓 冲 区 的 数 据 写 到 文 件,常 用 于 fclose 前 或 需 要 立 即 落 盘 的 场 景。

void_fflush(_FILE* fp){if(fp->out_pos >0)// 只有缓冲区有数据时才刷新{write(fp->fileno, fp->outbuffer, fp->out_pos); fp->out_pos =0;// 重置缓冲区}}

         判 断 缓 冲 区 是 否 有 数 据(out_pos > 0),有 就 调 用 write,然 后 重 置 out_pos。


4. _fclose

         _fclose 是 “收 尾 工 作”:必 须 先 刷 新 缓 冲 区(避 免 数 据 残 留),再 关 闭 文 件 描 述 符,最 后 释 放 _FILE 结 构 体 的 内 存。

void_fclose(_FILE* fp){assert(fp);// 断言:fp不能为NULL(避免非法访问)_fflush(fp);// 1. 先强制刷新缓冲区(关键!否则缓冲区数据会丢失)close(fp->fileno);// 2. 关闭文件描述符(归还操作系统资源)free(fp);// 3. 释放_FILE结构体内存(避免内存泄漏)}

核 心 原 则

  1. 必 须 先 刷 新 再 关 闭:如 果 不 调 用 _fflush,缓 冲 区 中 未 刷 的 数 据 会 随 着 fp 被 free 而 丢 失。
  2. 资 源 释 放 顺 序:先 释 放 用 户 层 资 源(缓 冲 区 数 据),再 释 放 内 核 层 资 源(文 件 描 述 符),最 后 释 放 内 存。

4、运 行 与 验 证

观 察 结 果

在这里插入图片描述


代 码 运 行 多 次,会 继 续 追 加 内 容(因 为 打 开 模 式 是 a)。

编 译 并 运 行 代 码

在这里插入图片描述

5、完 整 代 码 展 示

myfile.c

#include"myfile.h" #define FILE_MODE0666//"w" "a" "r" _FILE*_fopen(const char* filename,const char* flag){assert(filename); int f =0; int fd =-1;if(strcmp(flag,"w")==0){ f =(O_CREAT|O_WRONLY|O_TRUNC); fd =open(filename,f,FILE_MODE);}elseif(strcmp(flag,"a")==0){ f =(O_CREAT|O_WRONLY|O_APPEND); fd =open(filename,f,FILE_MODE);}elseif(strcmp(flag,"r")==0){ f =O_RDONLY; fd =open(filename,f);}else{returnNULL;}if(fd ==-1){returnNULL;} _FILE* fp =(_FILE*)malloc(sizeof(_FILE));if(fp ==NULL){returnNULL;} fp->fileno = fd; fp->flag =FLUSH_ALL; fp->out_pos =0;return fp;} int _fwrite(_FILE* fp,const char* s,int len){//"abcd\n"memcpy(&fp->outbuffer[fp->out_pos],s,len);//没有做异常处理,也不考虑局部问题 fp->out_pos += len;if(fp->flag &FLUSH_NOW){write(fp->fileno,fp->outbuffer,fp->out_pos); fp->out_pos =0;}elseif(fp->flag &FLUSH_LINE){if(fp->outbuffer[fp->out_pos-1]=='\n'){write(fp->fileno,fp->outbuffer,fp->out_pos); fp->out_pos =0;}}elseif(fp->flag &FLUSH_ALL){if(fp->out_pos ==SIZE){write(fp->fileno,fp->outbuffer,fp->out_pos); fp->out_pos =0;}}return len;}void_fflush(_FILE* fp){if(fp->out_pos >0){write(fp->fileno,fp->outbuffer,fp->out_pos); fp->out_pos =0;}}void_fclose(_FILE* fp){assert(fp);//进程结束时,缓冲区有内容直接刷新_fflush(fp);close(fp->fileno);free(fp);}

myfile.h

//防止文件被重复包含//#pragma once  #ifndef __MYFILE_H__ #define __MYFILE_H__ #include<stdlib.h> #include<string.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<assert.h> #include<unistd.h> #define SIZE1024 #define FLUSH_NOW1 #define FLUSH_LINE2 #define FLUSH_ALL4 typedef struct IO_FILE{ int fileno; int flag;//int in_pos;//char inbuffer[SIZE]; char outbuffer[SIZE]; int out_pos;}_FILE; _FILE*_fopen(const char* filename,const char* flag); int _fwrite(_FILE* fp,const char* msg,int len);void_fclose(_FILE* fp);void_fflush(_FILE* fp); #endif 

main.c

#include "myfile.h" #define filename "test.txt" int main(){ _FILE* fp =_fopen(filename,"a");if(fp ==NULL){return1;}const char* msg ="hello world\n"; int cnt =10;while(cnt){_fwrite(fp,msg,strlen(msg));sleep(1); cnt--;}_fclose(fp);return0;}

makefile

main:main.c myfile.c gcc -o $@ $^-std=c99 .PHONY:clean rm -f main 

在这里插入图片描述

三、总 结

         当 你 跑 通 模 拟 C 库 的 代 码,那 些 文 件 操 作 的 困 惑 早 已 有 了 答 案:printf 等 \n、fwrite 藏 缓 冲、close (1) 丢 数 据,本 质 都 是 “用 户 层 与 内 核 层 交 互 逻 辑” 的 体 现。Linux 学 习 从 不 是 记 接 口,而 是 抓 本 质:想 清 “数 据 在 哪 个 缓 冲”,“通 过 哪 个 描 述 符 到 内 核”,多 数 问 题 会 迎 刃 而 解。继 续 打 磨 代 码、追 问 底 层,“懂 原 理” 的 每 一 步,都 会 让 后 续 进 阶 更 扎 实。

Read more

Java WebFlux集成DeepSeek大模型:流式接入完整实现(含代码+优化+避坑)

Java WebFlux集成DeepSeek大模型:流式接入完整实现(含代码+优化+避坑)

Java WebFlux集成DeepSeek大模型:流式接入完整实现(含代码+优化+避坑) 前言:随着大模型技术的普及,Java后端接入DeepSeek等大模型时,传统同步阻塞式调用已无法满足高并发、低延迟的业务需求。本文基于Spring WebFlux响应式框架,详细讲解大模型流式接入的技术方案、完整实现代码、性能优化技巧及常见问题解决方案,全程干货,可直接落地到生产环境。 关键词:Java WebFlux;DeepSeek;流式接入;SSE;响应式编程;大模型集成 一、技术背景与需求分析 在Java后端开发中,接入DeepSeek等大模型进行AI推理时,传统同步HTTP调用模式存在诸多痛点,而流式处理结合WebFlux的响应式特性,成为解决该问题的最优路径。 1.1 传统AI模型接入的局限性 传统Java应用接入AI推理模型,普遍采用同步阻塞式HTTP请求(如OkHttp、RestTemplate同步调用),这种模式在对接DeepSeek等大模型时,瓶颈尤为突出,具体表现为三点: * 高延迟导致线程阻塞:DeepSeek等大模型单次推理耗时通常在1-5秒

By Ne0inhk
AI模型API排行榜横评:通义千问、Deepseek、Kimi 性价比对比指南

AI模型API排行榜横评:通义千问、Deepseek、Kimi 性价比对比指南

在选择 AI 模型 API 时,开发者不仅关注功能,更关心性能与性价比。本文横向对比 通义千问、Deepseek 和 Kimi 三大 AI API,从价格、响应速度、功能完整性到开发便利性进行评测,并提供实用提示词辅助开发参考。 一、AI 模型 API 选型关键指标 在横评前,需要明确选择 API 的核心指标: 1. 功能完整性:支持的任务类型(文本生成、问答、代码辅助等) 2. 接口稳定性:请求响应时间与并发能力 3. 价格与性价比:调用成本与性能比 4. 易用性:SDK 与文档完整性 5. 安全与合规:数据处理与访问权限控制 二、三大 AI

By Ne0inhk
AI驱动的图表生成器Next-AI-Draw.io

AI驱动的图表生成器Next-AI-Draw.io

简介 什么是 Next-AI-Draw.io ? Next-AI-Draw.io 是一个开源的、支持自托管的在线绘图应用。它结合了传统绘图工具的灵活性和人工智能的强大能力,让你不仅可以自由创作流程图、线框图、思维导图,还能通过 AI 指令一键生成内容,极大地提升了创作效率。 主要特点 * LLM 驱动的图表创建:利用大型语言模型(LLM)通过自然语言命令直接创建和操作 draw.io 图表。 * 基于图像的图表复制:上传现有图表或图像,让 AI 自动复制并增强它们。 * 图表历史记录:全面的版本控制,跟踪所有更改,允许您查看和恢复图表的先前版本。 * 交互式聊天界面:与 AI 进行交流,实时优化您的图表。 * AWS 架构图支持:专门支持生成 AWS 架构图。 * 动画连接器:在图表元素之间创建动态和动画连接器,以实现更好的可视化效果。 * 多模型支持:支持多个 AI

By Ne0inhk

EvoMap 全球首个面向 AI Agent 的自进化基础设施

EvoMap 是全球首个面向 AI Agent 的自进化基础设施,核心是一套叫 GEP(基因组进化协议)的开放系统,让 AI 能力像生物基因一样在网络中共享、验证、继承和自然进化。 简单说:它给每个 AI 装上 “数字 DNA”,让一个 AI 学会的技能,全网 AI 都能瞬间复用,不用从零训练。 一、核心定位 * 不是新模型,是底层协议 / 网络:大模型是 “大脑”,EvoMap 是 “DNA 系统”,负责能力的记录、传承与进化。 * 解决的痛点:终结 AI 经验孤岛、重复造轮子、平台依赖,让能力跨模型、跨节点自由流动。 * 口号:AI

By Ne0inhk