Linux(12)——基础IO(下)

news2025/6/6 11:55:25

 

目录

六、重定向

📄输出重定向

📄输入重定向 

📄追加重定向

📄dup2

七、理解一切皆文件

八、缓冲区 

🧠什么是缓冲区

🧠为什么要引入缓冲区

📄缓冲区类型 

 九、FILE


六、重定向

我们这里主要还是分开说一说输出重定向、输入重定向和追加重定向,然后得出结论:

📄输出重定向

输出重定向说白了就是本来要输出到A的内容结果上却是输出到了B上,我们来画个图理解一下:

我们这里在打开文件log.txt之前就把文件描述符1关闭,也就是把标准输出流关闭,这样一来我们在后面打开文件log.text时所分配到的文件描述符就是1了,我们也可以写个到吗来验证一下:

#include <stdio.h>    
#include <unistd.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>    
    
int main(){    
    close(1);    
    int fd = open("mytest.txt", O_WRONLY | O_CREAT, 0666);    
    if(fd < 0){    
        perror("open");    
        return 1;    
    }    
    printf("hello xywl\n");    
    fflush(stdout);    
    close(fd);                                                                                                                                                                      
    return 0;    
}    

这里就验证了我们之前说的,原本应该打印到屏幕上面的内容,现在打印到了文件mytest.txt当中了。

说明一下:

这里我们在代码的最后用了fflush函数,这是因为printf先是把内容写到了缓冲区当中了,我们想要写入到文件中就得调用fflush函数了。

🧠但是这里有个问题:为什么我们平时在C语言代码时并没有说是用到了这么一个函数来刷新才能写入到屏幕文件让我们看到?

🔖这里就要谈到一个非常重要的概念:缓冲区模式

行缓冲(Line Buffering):当 stdout 连接到终端(如屏幕)时,通常是行缓冲模式。每次遇到换行符(\n)或缓冲区满时,数据会自动刷新到内核(文件描述符 1)。

全缓冲(Full Buffering):当 stdout 重定向到文件时,通常是全缓冲模式,只有缓冲区满或程序显式调用 fflush(stdout) 时才会刷新。

无缓冲(No Buffering):stderr 默认无缓冲,输出立即写入,但 stdout 很少使用此模式。 

所以说我们这里才需要自己刷新。 

📄输入重定向 

这里的基本逻辑和上面的一样,就是本来应该从A文件中读数据,结果却是在B文件中读到。

这里我们是想让原本在键盘文件中读的数据,改成从log.txt中读,那么我们就可以将文件描述符为0的文件先关闭,这样之后在读取数据时就从文件中读了,写个代码验证一下:

#include <stdio.h>    
#include <unistd.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>    
    
int main(){    
    close(0);                                                                                                                                                                       
    int fd = open("mytest.txt", O_RDONLY | O_CREAT, 0666);    
    if(fd < 0){    
        perror("open");    
        return 1;    
    }    
    char str[40];    
    while(scanf("%s", str) != EOF){    
        printf("%s\n", str);    
    }    
    close(fd);    
    return 0;    
}  

我们可以看到运行结果是把mytest.txt的内容输出到了屏幕上:

📄追加重定向

其实追加重定向和输出重定向类似,只不过是追加重定向是在原有的内容后面追加,而输出重定向是覆写了之前的内容。

这里也是把本来应该输出到屏幕文件的内容追加地输出到了文件log.txt中,我们先关闭文件描述符1,然后后面分配的时候就变成了往文件log.txt中输入了。写个代码来验证一下:

#include <stdio.h>    
#include <unistd.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>    
    
int main(){    
    close(1);    
    int fd = open("mytest.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);                                                                                                               
    if(fd < 0){    
        perror("open");    
        return 1;    
    }    
    printf("Append\n");    
    fflush(stdout);    
    close(fd);    
    return 0;    
} 

我们运行之后发现结果是把输出到屏幕的内容输出到了文件log.txt中。

那么我们这里就后有一个问题了:

🧠标准输出流和标准错误流有什么区别呢? 

我们也可以写个代码来见一见:

#include <stdio.h>    
    
int main()    
{    
    printf("printf\n");    
    perror("perror");    
    
    fprintf(stdout, "stdout:fprintf\n");    
    fprintf(stderr, "stderr:fprintf\n");    
    return 0;                                                                                                                                                                       
}

运行程序之后,我们发现两种流的数据都打印到了屏幕中。

其实这里的区别并不在这里,   我们的区别在重定向操作,我们还是来写个命令来看看:

我们这里发现之前的标准输出流输出到了文件001.txt中,而标准错误流还是打印在了屏幕上面。实际上我们重定向是,重定向的是文件描述符是1的文件流,不会对文件描述符2重定向。

结论:重定向就是更改文件描述符表的指针指向

📄dup2

我么如果想要实现重定向只需要将数组fd中元素进行内容的拷贝即可,比如我们想把原本输出到屏幕的内容重定向到文件log.txt中,那么我们只需要将数组下标为3的内容拷贝到数组下标为1的位置即可,如图:

函数原型: 

int dup2(int oldfd, int newfd);

作用:在 Linux 环境下,dup2 是一个 POSIX 系统调用,用于复制文件描述符,常用于重定向文件描述符(如标准输入、输出或错误输出)。

参数:

oldfd:要复制的现有文件描述符(如打开的文件、管道、标准输入/输出等)。

newfd:目标文件描述符编号,oldfd 将被复制到此编号。

返回值:

成功:返回 newfd(目标文件描述符)。

失败:返回 -1,并设置 errno 表示错误原因(如 EBADF 表示无效文件描述符)。 

敲黑板:

1.这里可能你会有一个疑问,那就是为什么是oldfd复制到newfd而不是newfd复制到oldfd,这里我们一定要清楚这里的新旧指的是这个文件是否被使用或是否被打开,那么就是被使用的(oldfd)复制到未被使用的(newfd)。

2.重定向的原理就是打开文件的方式+dup2函数

七、理解一切皆文件

打开的struct file中会有一个函数指针,会作对应的初始化,指向对应驱动层的读写方法,通过读写方法,完成对应设备的读写。用户站在struct fie(文件的结构体对象)上层看来,所有的设备和文件,统一都是struct file,至于struct file内部是如何使用函数指针来调用对应设备文件的读写方法,用户并不了解也并不关心。所以在用户的视角下,Linux一切皆文件。

八、缓冲区 

我们先解决两个问题:

🧠什么是缓冲区

其实缓冲区类似于我们日常生活中的菜鸟驿站,是一个预留给我们处理快递的地方以提高效率,计算机中的缓冲区实际上是内存空间的一部分,也就是在内存空间中预留一定的空间,这些空间用来缓冲数据或输出数据。缓冲区根据对应的输入还是输出设备分为输入缓冲区和输出缓冲区。

🧠为什么要引入缓冲区

其实还是为了提高效率,例如我们在读写文件的时候如果不开辟对文件操作的缓冲区,而是直接通过系统调用来对磁盘进行操作,那么我们每次对文件的读写操作都需要读写系统调用来处理,这会损耗cpu的时间,频繁的对磁盘的访问对程序的执行效率也是极大的影响。

为了减少使用系统调用的次数,提高效率,我们就可以采用缓冲机制。比如我们从磁盘里取信息,可以在磁盘文件进行操作时,可以一次从文件中读出大量的数据到缓冲区中,以后对这部分的访问就不需要再使用系统调用了,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度。

📄缓冲区类型 

我们先还是来看一个代码:

#include <stdio.h>    
#include <string.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>    
#include <unistd.h>    
    
int main()    
{    
    close(1);    
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);    
    if (fd < 0) {    
        perror("open");    
        return 0;    
    }    
    printf("hello world: %d\n", fd);    
    close(fd);    
    return 0;                                                                                                                                                                       
} 

根据我们之前重定向的知识这里本来应该输出到文件log.txt中的内容并没有形成。

这里我们不得不提缓冲区的三大类型了:

1、全缓冲:数据在缓冲区填满后才一次性写入目标设备(磁盘文件)

2、行缓冲:以行为单位进行缓冲,当遇到换行符或缓冲区满时写入目标设备(显示器文件)

3、无缓冲:数据直接写入目标设备,不经过缓冲区(实时性的场景,如网络通信)

我们从原来的输出到屏幕的内容重定向到了输出到文件,也就是从行缓冲变成了全缓冲,因为没有刷新缓冲区,所有文件log.txt没有内容。我们只要写一个fflush就可以将内容刷新到文件log.txt当中了:

#include <stdio.h>    
#include <string.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>    
#include <unistd.h>    
    
int main()    
{    
    close(1);    
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);    
    if (fd < 0) {    
        perror("open");    
        return 0;    
    }    
    printf("hello world: %d\n", fd);    
    fflush(stdout);    
    close(fd);    
    return 0;                                                                                                                                                                       
}    

输出的结果: 

我们再来看一个有意思的代码:

#include <stdio.h>    
#include <unistd.h>    
    
int main()    
{    
    printf("printf\n");    
    fputs("fputs\n", stdout);                                                                                                                                                       
    write(1, "write\n", 6);    
    fork();    
    return 0;    
}

我们改成重定向到文件test.txt当中:

这里我们发现在重定向的时候有两份代码被写入了,这是因为我们直接执行的时候是行缓冲,直接就是将数据打印到了显示器上面了。后来我们改成了重定向到文件log.txt当中,数据的刷新变成了全缓冲了,这时我们打印的数据就统一存在了C语言的缓冲区当中了,之后我们在执行fork函数的时候创建了子进程,子进程和父进程都是独立的,这样我们在刷新的时候就是在对父子进程的数据进行了修改,这时就需要对数据写时拷贝,缓冲区的数据就变成了两份,也就输出了两份内容。

敲黑板:

printf和fwrite库函数会自带缓冲区,而 write 系统调用没有带缓冲区,因此write函数只打印一份内容。

这里的缓冲区是C语言提供的,如果是系统提供的,那么在重定向时候三个函数都应该打印三次。

📌补充内容: 

这里贴一个FILE的结构体:

//在/usr/include/libio.h

struct _IO_FILE {
 int _flags; /* High-order word is _IO_MAGIC; rest is flags. */

#define _IO_file_flags _flags

//缓冲区相关 
 /* The following pointers correspond to the C++ streambuf protocol. */

 /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */

 char* _IO_read_ptr; /* Current read pointer */

 char* _IO_read_end; /* End of get area. */

 char* _IO_read_base; /* Start of putback+get area. */

 char* _IO_write_base; /* Start of put area. */

 char* _IO_write_ptr; /* Current put pointer. */

 char* _IO_write_end; /* End of put area. */

 char* _IO_buf_base; /* Start of reserve area. */

 char* _IO_buf_end; /* End of reserve area. */

 /* The following fields are used to support backing up and undo. */

 char *_IO_save_base; /* Pointer to start of non-current get area. */

 char *_IO_backup_base; /* Pointer to first valid character of backup area */

 char *_IO_save_end; /* Pointer to end of non-current get area. */

 struct _IO_marker *_markers;
 struct _IO_FILE *_chain;
 int _fileno; //封装的⽂件描述符 

#if 0

 int _blksize;

#else

 int _flags2;

#endif

 _IO_off_t _old_offset; /* This used to be _offset but it's too small. */

#define __HAVE_COLUMN /* temporary */

 /* 1+column number of pbase(); 0 is unknown. */

 unsigned short _cur_column;
 signed char _vtable_offset;
 char _shortbuf[1];
 /* char* _save_gptr; char* _save_egptr; */

 _IO_lock_t *_lock;

#ifdef _IO_USE_OLD_IO_FILE

};

 九、FILE

因为IO相关的函数与系统调用的接口对应,并且库函数封装系统调用,所有本质上访问文件都是通过fd访问的,所有C库当中的FILE结构体内部一定是封装了fd的。

这个结构体实际上是在我们的<stdio.h>中,我们可以在文件中找到这个结构体:

typedef struct _IO_FILE FILE;

这说明FILE实际上就是struct _IO_FILE的一个别名了,我们在文件中还可以找到一个名为_fileno的变量,这个就是我们之前一直在说的文件描述符了。

 int _fileno; //封装的⽂件描述符 

结论:我们来拿fopen举例:我们在使用fopen时先是要解析参数(文件名和模式等),然后调用、open函数打开文件并获得文件描述符(这个过程后面会讲),再进行FILE结构体的分配与初始化操作,再设置对应的缓冲区,最后返回FILE*指针。我们在调用fopen、fread和fprintf等接口时也是类似的操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2401618.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

WPF可拖拽ListView

1.控件描述 WPF实现一个ListView控件Item子项可删除也可拖拽排序&#xff0c;效果如下图所示 2.实现代码 配合 WrapPanel 实现水平自动换行&#xff0c;并开启拖拽 <ListViewx:Name"listView"Grid.Row"1"Width"300"AllowDrop"True&…

[蓝桥杯]倍数问题

倍数问题 题目描述 众所周知&#xff0c;小葱同学擅长计算&#xff0c;尤其擅长计算一个数是否是另外一个数的倍数。但小葱只擅长两个数的情况&#xff0c;当有很多个数之后就会比较苦恼。现在小葱给了你 nn 个数&#xff0c;希望你从这 nn 个数中找到三个数&#xff0c;使得…

【MySQL】 约束

一、约束的定义 MySQL 约束是用于限制表中数据的规则&#xff0c;确保数据的 准确性 和 一致性 。约束可以在创建表时定义&#xff0c;也可以在表创建后通过修改表结构添加。 二、常见的约束类型 2.1 NOT NULL 非空约束 加了非空约束的列不能为 NULL 值&#xff0c;如果可以…

前端vue打开多个窗口,关闭窗口后才继续执行后续逻辑

1.打开第一个弹窗 弹窗的按钮代码 2.点击窗口1中按钮&#xff0c;打开新的窗口 // 请领单按钮点击 async cb_6_delClick() {let ls_yfbm this.st_3Value.BMBMlet pstring {}pstring.a ls_yfbmpstring.b this.queryFormDialog.outDepotDeptCodeawait this.openwithparm_w_md…

「深度拆解」Spring Boot如何用DeepSeek重构MCP通信层?从线程模型到分布式推理的架构进化

什么是MCP&#xff1f; MCP&#xff08;Model Context Protocol&#xff0c;模型上下文协议&#xff09;是由Anthropic公司于2024年11月推出的开放标准协议&#xff0c;旨在为大型语言模型&#xff08;LLM&#xff09;与外部数据源、工具及系统提供统一的交互接口&#xff0c;被…

【西门子杯工业嵌入式-2-点亮一颗LED】

西门子杯工业嵌入式-2-点亮一颗LED 一、课程回顾与目标1.上节课内容回顾2.本节课目标 二、硬件连接与原理1. 硬件连接方式2. 连接实例 三、GPIO原理知识1. GPIO结构2. 推挽输出模式原理 四、软件实现步骤1. 项目结构设置2. 函数定义3. led.c 文件编写初始化函数 led_init交替闪…

代码随想录算法训练营第60期第五十五天打卡

大家好&#xff0c;我们今天继续我们图论的部分&#xff0c;其实我们昨天是主要讲解了深搜与广搜的理论基础&#xff0c;我们大体上了解了两种算法的差异与适用情景&#xff0c;今天我们就继续我们的图论的章节&#xff0c;以后几天的题目是图论中比较有名的问题叫做岛屿问题&a…

重磅更新! 基于Gemini 2.5 Pro打造的AI智能体PlantUML-X上线!

目录 图表绘制AI智能体PlantUML-X上线通过简单的提示词创建各种UML图&#xff1a;轻松搞定其它类型的技术图表&#xff1a; AI智能体PlantUML-X功能实测画一个在Java中的一个简单的用户登录功能的时序图效果展示&#xff1a;根据详细内容生成系统架构图效果展示&#xff1a;效果…

[5-02-04].第01节:Jmeter环境搭建:

JMeter笔记大纲 Jmeter依赖于JDK&#xff0c;所以必须确保当前计算机上已经安装了JDK&#xff0c;并且配置了环境变量 一、JMeter概述&#xff1a; 1.1.JMeter是什么&#xff1a; JMeter是Appache组织使用java开发的一款测试工具 可以用于对服务器、网络或对象模拟巨大的负载…

AI智能推荐实战之RunnableParallel并行链

导读&#xff1a;在现代AI应用开发中&#xff0c;如何高效处理多维度数据分析始终是开发者面临的核心挑战。当您需要同时进行情感分析、关键词提取和实体识别&#xff0c;或者要对比多个AI模型的输出结果时&#xff0c;传统的串行处理方式往往效率低下。 本文将深入解析LangCha…

Gemini开源项目DeepResearch:基于LangGraph的智能研究代理技术原理与实现

引言 在人工智能快速发展的今天&#xff0c;如何构建一个能够进行深度研究、自主学习和迭代优化的AI系统成为了技术前沿的重要课题。Gemini开源的DeepResearch一周收获7.9k Star&#xff0c;Google的开源项目Gemini DeepResearch技术通过结合LangGraph框架和Gemini大语言模型&…

Doris Catalog 联邦分析查询性能优化:从排查到优化的完整指南

在大数据分析中&#xff0c;Doris 的 Catalog 联邦分析功能为整合多源数据提供了有力支持。然而&#xff0c;在实际应用中&#xff0c;可能会遇到各种问题影响其正常运行。本文将详细剖析这些问题并提供解决方案。 一、联邦分析查询慢&#xff1a;内外表通用排查逻辑 当遇到 …

01 Deep learning神经网络的编程基础 二分类--吴恩达

二分类 1. 核心定义 二分类任务是监督学习中最基础的问题类型&#xff0c;其目标是将样本划分为两个互斥类别。设样本特征空间为 X ⊆ R n \mathcal{X} \subseteq \mathbb{R}^n X⊆Rn&#xff0c;输出空间为 Y { 0 , 1 } \mathcal{Y} \{0,1\} Y{0,1}&#xff0c;学习目标为…

视频自动化分割方案:支持按时间与段数拆分

在日常视频处理任务中&#xff0c;如何快速将一个较长的视频文件按照指定规则拆分为多个片段&#xff0c;是许多用户都会遇到的问题。尤其对于需要批量处理视频的开发者、自媒体运营者或内容创作者来说&#xff0c;手动剪辑不仅效率低下&#xff0c;还容易出错。这是一款绿色免…

股指期货合约价值怎么算?

股指期货合约价值就是你买一手股指期货合约&#xff0c;理论上值多少钱。这个价值是根据期货的价格和合约乘数来计算的。就好比你买了一斤苹果&#xff0c;价格是5块钱一斤&#xff0c;那你买一斤就得付5块钱。股指期货也是一样&#xff0c;只不过它的计算稍微复杂一点点。 一…

【QT】使用QT帮助手册找控件样式

选择帮助—》输入stylesheet(小写)—》选择stylesheet—》右侧选择Qt Style Sheets Reference 2.使用CtrlF—》输入要搜索的控件—》点击Customizing QScrollBar 3.显示参考样式表–》即可放入QT-designer的样式表中

计算机网络(5)——数据链路层

1.概述 数据链路层负责一套链路上从一个节点向另一个物理链路直接相连的相邻节点传输数据报。换言之&#xff0c;主要解决相邻节点间的可靠数据传输 节点(nodes)&#xff1a;路由器和主机 链路(links)&#xff1a;连接相邻节点的通信信道 2.数据链路层服务 2.1 组帧 组帧(fra…

VuePress完美整合Toast消息提示

VuePress 整合 Vue-Toastification 插件笔记 记录如何在 VuePress 项目中整合使用 vue-toastification 插件&#xff0c;实现优雅的消息提示。 一、安装依赖 npm install vue-toastification或者使用 yarn&#xff1a; yarn add vue-toastification二、配置 VuePress 客户端增…

adb 连不上真机设备问题汇总

问题一、无法弹出 adb 调试授权弹窗 详细描述&#xff1a; 开发者选项中已打开 usb 调试&#xff0c;仅充电模式下 usb 调试也已打开&#xff0c;电脑通过 usb 连上手机后&#xff0c;一直弹出 adb 调试授权弹窗&#xff0c;尝试取消授权再次连接&#xff0c;还是无法弹出问题…

[yolov11改进系列]基于yolov11引入注意力机制SENetV1或者SENetV2的python源码+训练源码

本文给大家带来的改进机制是SENet&#xff08;Squeeze-and-Excitation Networks&#xff09;其是一种通过调整卷积网络中的通道关系来提升性能的网络结构。SENet并不是一个独立的网络模型Q&#xff0c;而是一个可以和现有的任何一个模型相结合的模块&#xff08;可以看作是一种…