当前位置: 首页 > news >正文

Linux - 基础IO【下】

一、重谈重定向

  • stderr 和 stdout 是打印到显示器文件里面的 , 均访问同一个文件
  • 进行重定向的时候,是把3重定向到1 , 但是2依旧是在显示屏

问 : 为什么有了stdout , 还需要有stderr ?

问:如何把stderr 和 stdout 打印到同一个文件 ?

二、理解"一切皆文件"

Linux 把所有资源都抽象成文件,包括:

  • 普通磁盘文件(log.txt
  • 设备(键盘、显示器、网卡、磁盘)
  • 进程间通信(管道、socket)
  • 甚至内核对象

统一接口的意义

不管操作的是什么资源,都用同一套系统调用接口open/read/write/close)来操作,这就是「一切皆文件」的本质:

  • 对开发者:只需要学一套 API,就能操作所有资源
  • 对内核:通过struct file_operations函数指针集,为不同资源实现不同的底层操作(比如键盘的read和磁盘的read实现完全不同)
  • 对系统:架构简洁,可扩展性极强

骗过进程,相当于骗过用户 , 让进程以为 "一切皆文件" ,屏蔽底层的差异。

file_operation 就是把系统调用和驱动程序关联起来的关键数据结构, 这个结构的每个成员都对应着一个系统调用。读取file _operation 中相应的函数指针,接着把控制权转交给函数。从而完成了Linux设备驱动程序的工作

上图的外设,每个设备都可以有自己的read , write , 但一定是对应着不同的操作方法!!!但通过struct file下file _operation中的各种函数回调,让我们开发者只用file便可以调取Linux系统中绝大部分的资源!!!这就是"Linux下一切皆文件"的核心概念!

三、缓冲区

3.1 什么是缓冲区

缓冲区是内存中预留的一块空间,用于缓存 IO 数据,分为:

  • 用户级缓冲区由 C 标准库(libc)维护,在用户空间
  • 内核级缓冲区由 Linux 内核维护,在内核空间

3.2 为什么要引入缓冲区机制

核心原因:CPU 速度 ≫ 内存速度 ≫ 磁盘 / 设备 IO 速度,存在严重的速度不匹配。

缓冲区的价值:

  1. 减少系统调用次数:避免频繁的用户态 ↔ 内核态切换,降低开销
  2. 批量 IO 提升效率:底层设备(如磁盘)更擅长批量读写,缓冲区可以攒够数据再一次性写入
  3. 解耦 CPU 与 IO:CPU 只需要操作内存缓冲区,无需等待低速设备完成 IO

想象一下,如果没有菜鸟驿站,快递员要送快递给取件人,就必须直接联系对方:

  • 快递员每送到一件快递,都要打电话给取件人,等对方下楼来取。

  • 如果取件人不在家,快递员就得改天再跑一趟,或者一直等着。

  • 取件人也可能因为不能及时拿到快递而感到不便。

这就像没有缓冲区的文件读写:每次应用程序要读一个字节,CPU就得发起一次系统调用,切换到内核态,让磁盘控制器去读一个字节,然后切换回用户态。频繁的系统调用和状态切换会消耗大量时间,就像快递员反复跑腿、等待一样,效率极低。

有了菜鸟驿站,流程就变成了:

  • 快递员(数据源/输入设备)不再需要挨个打电话等取件人,而是直接把一批快递(数据块)放到驿站的货架上(缓冲区),然后就可以去处理下一批快递了。

  • 取件人(CPU/应用程序)可以在自己方便的时候(比如下班后)去驿站,一次取走所有自己的快递,或者分次取,完全不用和快递员同步。

  • 驿站(缓冲区)起到了中间暂存的作用,既让快递员能快速卸货,也让取件人能灵活取货。

3.3 缓冲类型

  • 用户级缓冲区(语言层缓冲区)
    由C标准库提供(如printffwrite等函数内部维护的缓冲区)。当我们调用这些函数时,数据并不会立即通过系统调用写入内核,而是先暂存在用户空间的一块内存中(即用户级缓冲区)。
    例如,多次调用printf("a"),字符'a'可能先被收集到用户缓冲区,直到满足一定条件才一次性通过write系统调用发送给内核。

  • 内核级缓冲区(文件内核缓冲区)
    当用户程序通过系统调用(如write将数据交给操作系统后,操作系统并不一定会立即将数据写入硬件(如磁盘),而是先将数据拷贝到内核空间的缓冲区(即内核缓冲区)。操作系统根据自身的策略(如缓存、合并写入)决定何时真正将数据刷到硬件设备。

缓冲类型刷新策略典型场景
全缓冲缓冲区满才刷新普通磁盘文件(默认)
行缓冲遇到\n或缓冲区满才刷新标准输出stdout(终端)
无缓冲直接写入,不缓冲标准错误stderr

解释:

这是由于我们将1号描述符重定向到磁盘文件后,缓冲区的刷新方式成为了全缓冲。而我们写入的内容并没有填满整个缓冲区,导致并不会将缓冲区的内容刷新到磁盘文件中。
怎么办呢?
可以使用fflush强制刷新下缓冲区。

还有一种解决方法 , 刚好可以验证 一下stderr是不带缓冲区的 :

这种方式便可以将2号文件描述符重定向至文件由于stderr没有缓冲区 ,"hello world"不用fflush就可以写入文件

3.4 FLIE(库封装系统调用)

很正常,四条消息,但是由于write是系统调用, 直接到文件内核缓冲区 ; 其他的库函数是先放在C标准库的的缓冲区,后面才被刷到os。

我们再接着看下面的例子:

3.5 简单设计一下libc库

mystdio.c

#include "mystdio.h" #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <unistd.h> #include <string.h> static MYFILE *BuyFile(int fd, int flag) { MYFILE *f = (MYFILE*)malloc(sizeof(MYFILE)); if(f == NULL) return NULL; f->bufferlen = 0; f->fileno = fd; f->flag = 0; f->flush_method = LINE_FLUSH; memset(f->outbuffer, 0, sizeof(f->outbuffer)); return f; } MYFILE *MyFopen(const char* path, const char *mode) { int fd = -1; int flag = 0; if(strcmp(mode, "w") == 0) { flag = O_WRONLY | O_CREAT | O_TRUNC; fd = open(path, flag, 0666); } else if(strcmp(mode, "a") == 0) { flag = O_WRONLY | O_CREAT | O_APPEND; fd = open(path, flag, 0666); } else if(strcmp(mode, "r") == 0) { flag = O_RDONLY; fd = open(path, flag); } else { //todo } if(fd < 0) return NULL; return BuyFile(fd, flag); } void *MyClose(MYFILE *file) { if(file->fileno < 0) return NULL; MyFflush(file); close(file->fileno); free(file); } int MyFwrite(MYFILE *file, void *str, int len) { //1.写入就是拷贝 memcpy(file->outbuffer + file->bufferlen, str, len); file->bufferlen += len; //2.尝试判断是否满足刷新条件? if((file->flush_method & LINE_FLUSH) && file->outbuffer[file->bufferlen-1] == '\n') { MyFflush(file); } return 0; } void MyFflush(MYFILE *file) { if(file->bufferlen <= 0) return; int n = write(file->fileno, file->outbuffer, file->bufferlen); (void)n; fsync(file->fileno); file->bufferlen = 0; }

mystdio.h

#pragma once #include <stdio.h> #define MAX 1024 #define NONE_FLUSH (1<<0) #define LINE_FLUSH (1<<1) #define FULL_FLUSH (1<<2) typedef struct IO_FILE { int fileno; int flag; char outbuffer[MAX]; int bufferlen; int flush_method; }MYFILE; MYFILE *MyFopen(const char* path, const char *flag); void *MyClose(MYFILE *file); int MyFwrite(MYFILE *file, void *str, int len); void MyFflush(MYFILE *file);

usercode.c

#include "mystdio.h" #include <string.h> int main() { MYFILE * filep = MyFopen("./log.txt", "a"); if(!filep) { printf("fopen error!\n"); return 1; } char* msg = "hello myfile!\n"; MyFwrite(filep, msg, strlen(msg)); MyClose(filep); //FILE *fp return 0; }
http://www.jsqmd.com/news/474872/

相关文章:

  • UR机器人通信端口全解析:从Modbus TCP到Dashboard的实战避坑指南
  • 云容笔谈解决403 Forbidden错误:API访问权限与配置详解
  • JavaScript 设计模式分类与应用实践
  • Markdown中同时使用了TOC与HTML锚点后,锚点无效解决方法
  • 通义千问1.5-1.8B-Chat-GPTQ-Int4实战:自动化作业批改与个性化反馈生成
  • 2026年洗车槽生产厂家盘点!钢制洗车槽厂家/工地洗车池厂家推荐/洗车槽租赁推荐/工地洗车槽厂家推荐:宁波玖鼎领衔 - 栗子测评
  • 5分钟搞定Arduino IDE+ESP32开发环境(最新2.0.9版)
  • 当水泥浆遇上随机裂隙:COMSOL里的流动艺术
  • 2026年知名的增强剂公司推荐:防水增强剂直销厂家推荐 - 品牌宣传支持者
  • 2026年长沙天心区足疗养生品牌评测与选型指南 - 2026年企业推荐榜
  • Prius 2004永磁同步电机设计报告:包含磁路法、Maxwell有限元法建模与仿真、Mot...
  • Allegro PCB设计必备:3分钟搞定中文字体导入(附BMP2Allegro工具包)
  • 从零到一:实战加固Hadoop集群,封堵未授权访问风险
  • Google Images API 调用实战:从零开始获取图片数据的完整指南
  • 智慧铁路AI巡检数据集 铁路紧固件识别 铁路紧固件缺失识别 扣件图像识别 yolo数据集第10547期
  • MCP SDK供应链安全加固实战:SBOM自动生成+OpenSSF Scorecard评分提升至9.8分的7项CI/CD嵌入式检查点
  • 二维钻孔封孔效果模拟案例
  • ChatGLM3-6B-128K真实案例分享:万字论文摘要生成效果
  • PowerDesigner报错Cannot load the DBMS ORACLE Version 9i!Choose another one
  • 新型装载机装配图(毕业设计)
  • 基于改进A*算法的AGV路径规划算法仿真代码
  • 文墨共鸣政务场景落地:政策文件语义一致性校验工具开发实践
  • 用友U8接口开发全攻略:从EAI到OpenAPI的5种方式详解(附避坑指南)
  • 三相两电平整流器Simulink仿真探究
  • 生成24小时风速数据(每5分钟一个点)
  • 探索 S7 - 200 PLC 与组态王构建热交换站监控系统
  • 基于单例模式的基础日志库
  • GTA5初始化Social Club失败?网络诊断与加速方案全解析
  • 基于三菱PLC与组态王的兰花灌溉控制技术在农业农田的实践应用系统
  • 探索 S7 - 200 PLC 与组态王构建六层电梯控制系统