【Linux】基础IO-----重定向与缓冲区

目录

一、文件描述符分配规则:

二、重定向:

1、本质(原理):

2、dup2的使用:

3、添加重定向功能到shell:

4、stdout与stderr:

三、Linux下一切皆文件:

四、缓冲区:

1、引入

2、缓冲区的刷新方案:

3、缓冲区存在的意义:

4、fork和缓冲区

5、拓展:

6、模拟实现C库:


一、文件描述符分配规则:

从0下标开始,寻找 最小的 没有被使用的 数组的位置,它的下标就是新文件的文件描述符

证明:
从上一篇我们了解到了默认打开的新文件的文件描述符的下标是3,因为系统会默认为我们打开三个文件流:stdin,stdout,stderr,分别对应着0,1,2,所以接下来打开文件的文件描述符就是三
那么为什么不是从下标为3开始寻找呢?接下来我们关闭前三个文件之一来进行实验看看

如上,整体思路就是
1、首先关闭1,也就是关闭标准输出流stdout
2、然后再打开一个文件
3、再向文件描述符为1的文件里面写入五行字符串(如果没有关闭stdout,那么应该会在显示器上打印该五行字符串)
4、最后再向标准错误流(也就是文件描述符为2的文件中打印fd,由于这个也是指向显示器的,所以fd这一行会在显示器中打印

如上就是我们看到的现象,

1、首先当前目录中是没有log.txt文件的,
2、执行mytest程序后和预期的一样,会在显示器打印log.txt的文件描述符,这个文件描述符为1,3、因为我们在代码中首先就把stdout给关闭了,并且此时我们查看log.txt文件就发现,本来应该写入到显示器中的字符串写入到了log.txt中
所以就可以证明:打开文件时,给这个文件的文件描述符分配原则就是从0开始的
此时可以发现:本应该输出到一个文件的数据重定向输出到另一个文件中,这个就叫做输出重定向

输出重定向是覆盖式输出数据,而追加重定向是追加式输出数据
输入重定向就是,将我们本应该从一个文件读取(stdin)数据,现在重定向为从另一个文件读取数据

想要实现上述功能就只需在open打开方式中第二个参数修改宏即可

二、重定向:

1、本质(原理):

在明白文件描述符的概念以及其是怎么分配的,就可以理解重定向的本质了,其实其本质就是修改文件描述符表的下标对应的指针内容

如上,这是一个进程的文件描述符表,里面前3个文件描述符被使用了,然后我们把文件描述符为1的文件close掉(这个操作实际上就是把1中的指针置空,标记为未使用的)然后我们创建了一个新的文件,这样就会从0开始往后找,这个时候发现1位置的地方没有被使用,就把log.txt文件的struct file*这个指针放到1中,

这样,write函数再向文件描述符为1的位置写入字符串实际上就不在是往显示器文件中写了,就是往log.txt文件中写入字符串了,

本来向显示器文件里面写东西转化为向log.txt文件里面写东西,而这个过程就是重定向

2、dup2的使用:

首先,在man函数手册中看看这个函数的参数,返回值,所在的头文件,这是一个系统接口

如上虽然有两个,但是一般是使用dup2的

解析:

返回值:dup2如果调用成功,返回newfd,否则返回-1

dup2函数的形参就是两个文件标识符,这个函数的作用就是将文件描述符表中里面的一个指针覆盖另一个指针,

如上,当新打开一个文件,就会把新开的文件的struct file*指针放在文件描述符下标为3的地方,然后在进行系统调用接口:dup(fd,1)这样就会把fd(3)中的指针覆盖到1中,这样的话明明文件描述符为1应该是指向显示器文件的,经过这次操作后就会指向log.txt文件

此时我们就将输出重定向到了文件log.txt

如下是语言层面的实现,打开一个文件,在输出重定向,也就是新打开文件的文件描述符里面的指针覆盖文件描述符1里面的指针,这样的话,文件描述符1就指向新打开的文件了,那么write函数继续往文件描述符为1的文件写,就不在是往显示器文件写了,就是往新指向的log.txt文件写了

由于open第二个参数里面的宏是追加的,所以写入文件中就是追加写了

int main()
{int fd = open("log.txt",O_CREAT|O_WRONLY|O_APPEND,0666);if(fd < 0){perror("open fail");return 1;}dup2(fd,1);int cnt = 5;const char* message = "hello world\n";while(cnt--){write(1,message,strlen(message));}return 0;
}

如果想变成清空后在写入就将宏O_APPEND修改为O_TRUNC

如果是输入重定向也就是修改一些参数即可

如上,这就是open,以读的形式打开,然后把fd位置的指针覆盖0位置的指针,使0位置的指针不在指向stdin,而是新打开文件的log.txt,这样的话就不在是从键盘文件里面读取了,就是从log.txt文件里面进行读取了

3、添加重定向功能到shell:

思路:
在原先代码上加些功能即可,

如上,增加了这些宏和全局变量,

作用:

flag是作为判断重定向为哪一个,和上面的宏一起组成判断,filename能够拿到重定向后面的文件

然后在初始化中检查当前指令是否有重定向标志(</>/>>)

最后在普通命令的子程序中进行判断,进行对应的程序替换

如下:

整体意思就是

通过上述代码,会不会有个问题,为什么做了重定向的工作,在后面再进行程序替换的时候难道不影响吗?

为了解决上述问题,就要知道一个进程的task_struct,进程替换,和文件打开的关系了,

如下:

在上面的图里面,task_struct和文件管理,是内核数据结构,

而虚拟地址空间,页表,物理内存,是进程数据结构,

这两个是解耦合关系,在Linux系统中,进程数据结构内核数据结构是分开管理的,这种设计使得系统更加灵活和高效

而对于程序替换中,物理内存,程序和代码加载替换掉物理内存,页表重新映射

在内核数据结构里 并不关心 进程数据结构中是怎么操作的

所以文件的重定向和进程替换之间没有啥影响

4、stdout与stderr:

这两个都是指向显示器的文件的,那么这两个存在的意义是啥呢?

在程序猿的眼中当程序出现问题的时候,只需要关心报错的信息,那么如果无论是正确的信息还是错误的信息都往显示器中打印,那么程序猿对程序调试的效率就会大大下降,所以就有两个流,一个存储错误信息,一个存储正常的信息,那么就可以提高锁定问题的效率了

如下,就可以进行重定向,把正常的信息打印到一个装正常内容的文件中,把错误的信息写入到一个装错误信息的文件中,这样只需看装错误的信息的文件即可

当然,也可以把所有信息都输入到同一个文件中,如下,使用指令:

这就是运行一个程序运行的结果是本来打标准输出到显示器中的,但是后面1>both.txt将 标准输出 重定向到 both.txt中,然后2>&1在将标准错误输出 重定向到 标准输出,但是此时标准输出已经重定向到both.txt了,所以程序运行后无论是在1(标准输出)还是在2(标准错误输出)的结果就写到both.txt了

三、Linux下一切皆文件:

在操作系统看来,无论是是标准输入(键盘),还是标准输出(显示器),或者是其他底层外设,比如说网卡,声卡等等,当对其操作的时候对操作系统来说都会对其创建一个file的结构体对象,

对于操作系统来说,无论是硬件(外设),还是软件(文件),只需要提供相应的读方法和写方法就可以对其进行驱动

如下是例图:

解析:

首先,底层的外设给操作系统提供了读写方法,操作系统在管理这些外设的时候创建了file的结构体,这个结构体里面有一个指针指向另一个结构体,这个结构体通常包含多个函数指针,例如读、写、打开、关闭等操作的函数指针。通过这些函数指针,系统可以调用相应的操作函数来管理各种设备,包括磁盘、网络接口、字符设备等

然后操作系统给我们定义了系统调用,如下

这样当使用系统调用的时候,根据下层文件的不同,找到不同的外设提供的write,read方法,至于这些设备是怎么实现的就不是我们关心的了

在Linux中struct file这一层也叫做VFS——virtual file system虚拟文件系统,往上就是展现给用户的,用户看到的就是文件,所以就是Linux下一切皆文件

四、缓冲区:

1、引入

首先通过几个示例来进行理解:

#include<stdio.h>
#include<unistd.h>
#include<string.h>int main()
{const char* fstr = "hello fwrite\n";const char* str = "hello write\n";//Cprintf("hello printf\n");	fprintf(stdout,"hello fprintf\n");fwrite(fstr,strlen(fstr),1,stdout);//system callwrite(1,str,strlen(str));return 0;
}

如上,当进行运行之后,应该会打印四行hello,如下,确实也没问题

并且,如果去掉\n的话,就会打印在一行,也没啥问题

如果在代码最后将stdout这个文件关闭了

那么在屏幕上也会打印四行,但是如果把后面的\n去掉,

可以看到C接口就不会打印在屏幕上了,但是system call依然会打印在屏幕上这是为什么呢,并且如下,尽管当有\n,但是当重定向到文件中的时候依然只有system call能够打印出

上面的这些现象是为什么呢?

首先我们了解,C语言的对文件操作的接口,其底层封装一定是调用了write这个system call,
我们又知道数据的写入并不是直接写到显示器中的,而是先写到缓冲区中,再刷新到显示器或者文件磁盘中的

所以再通过上述的情景引入,我们可以得出一个结论:当上述close(1)的时候,C语言的文件操作接口中写入的一个缓冲区和system call中文件操作写入的缓冲区不是同一个缓冲区,C语言中的缓冲区一定不在操作系统内部,不是系统级别的。

所以应该是如下的关系:

如上,我们平时所说的缓冲区是C语言级别的缓冲区,这是一个用户级别的缓冲区,所以当使用文件调用接口将数据写入的时候是写到了这个C语言级别的缓冲区,然后等到某个时机就将C语言级别的缓冲区里的内容写到系统级别的缓冲区,再刷新到磁盘,显示器中

所以当我们通过printf,fprintf的时候就将数据写到了C语言级别的缓冲区中,并且没有\n,的话,往显示器中写入就是行刷新,如果在后面将close(1)的时候1号文件struct file,系统文件缓冲区就被关掉了,就无法向磁盘写入数据了,也就看不到显示结果了

相对应,如果有\n的话,那么就会进行刷新,这样就可以在显示器中看到了,如果是重定向到文件,那么就会是全刷新,这样的话就需要将缓冲区写满才会刷新,所以重定向到文件中,文件就只看得到system call了

2、缓冲区的刷新方案:

这里的刷新方案是指的是语言级别的缓冲区的刷新方案

1、无缓冲:这就是直接刷新,无论往哪里打印,打印什么,直接刷新到系统级别的缓冲区中

2、行缓冲:直到遇到'\n'在进行刷新

3、全缓冲:直到语言级别的缓冲区被写满的时候就会进行刷新

注意:

向显示器中写入的时候,C缓冲区是采用的是行缓冲,
向普通文件中写入的时候,C缓冲区是采用的是全缓冲

3、缓冲区存在的意义:

缓冲区实现其实就是一个buffer数组,在实现的时候配合不同的刷新策略,提高IO效率

缓冲区就像菜鸟驿站,如果没有菜鸟驿站的话,我们要想好朋友寄东西,那么快递公司就一个一个地送,这样效率就很低,但是如果有菜鸟驿站,那么当一个学校的快递都在菜鸟驿站的时候,这个时候一起打包送出去效率就很高了,

CPU的计算速率很快,而磁盘的读取速度相对于 CPU 来说是很慢的,因此需要先将数据写入缓冲区中,依据不同的刷新策略,将数据刷新至内核缓冲区中,供CPU进行使用,这样做的是目的是尽可能的提高效率,节省调用者的时间

当数据进行打印的时候,先将数据打印到用户缓冲区,当某个时机的时候,就将用户缓冲区的数据打印到磁盘里面,由于这种数据进入,数据流出,很像一条河流,所以用户缓冲区也被叫做流 

4、fork和缓冲区

如上,当向文件写入的时候可以看到C语言接口写了两遍,但是system call只写了一遍,并且 是采用的是全缓冲,如右边可以看到,当最后经过5秒后,再程序结束后就全部刷新出来了

                                         

那么为什么C语言接口写了两遍,但是system call只写了一遍呢?

首先我们知道,当程序结束的时候,语言级别的缓冲区就都会刷新,并且fork创建子进程的时候会拷贝父进程的代码和数据,当子进程刷新缓冲区的时候就相当于对数据进行修改,那么就会发生写实拷贝,所以就会有两份缓冲区的数据了

5、拓展:

exit和_exit

前面我们了解到,_exit是系统接口,exit是封装了_exit,其主要区别是exit在退出的时候会帮我们把用户级别的缓冲区刷新,然后_exit不会,这是因为_exit是系统级别的,他不知道用户级别的缓冲区在哪,所以也就刷新不好,而exit在用户层,就是先刷新语言级别的缓冲区在调用_exit退出

FILE:

​​​​​​FILE是在用户级别的,并不是在系统级别的,因为语言就是属于用户的

文件操作绕不开FILE,用户级别的缓冲区是在FILE这个结构体中进行维护的,这个FILE里面还有对应的维护信息,还有文件描述符等等

所以fopen事实上就是打开一个文件就会获取这个文件的文件描述符,然后在malloc一块空间,将文件描述符,缓冲区等等都保存在FILE这个结构体里面进行维护

6、模拟实现C库:

这里采用三个文件,Mystdio.h文件Mystdio.c文件和main.c文件,一个是声明函数,一个是实现对应函数一个是使用对应函数

Mystdio.h

如上是.h文件的声明,准备实现文件的打开,读写,刷新缓冲区的操作,

其中fileno是文件描述符,flag是刷新策略,outbuffer是维护的缓冲区,out_pos是缓冲区中写到哪儿了的指向

如上是.c文件实现的fopen函数,在底层是进行系统接口调用,然后根据第二个参数选项进行判断,是读写还是追加,打开失败就返回NULL,在进行初始化

如下是fwrite函数的实现,思路就是将所写的字符串拷贝到所维护的缓冲区中,然后在下面在进行缓冲区的刷新策略,这样的话就可以知道是什么时候进行调用write系统接口函数进行刷新

如下是fflush立即刷新缓冲区的函数和关闭缓冲区的函数,

因为关闭缓冲区的时候,如果缓冲区中还有数据就需要进行刷新,那么就可以进行实现fflush在这个函数里面进行判断

最后在main.c文件中进行函数的调用:

如上,这就是往log.txt文件中按行刷新的策略进行写入的,可以看到,每经过一秒后就会进行追加

C语言的跨平台性:

当我们这次实现C语言,其底层是通过Linux的系统调用接口进行实现的,那么我们是不是还可以通过Windows中系统调用接口实现,那么再将这两份代码进行条件编译进行裁剪实现,在Windows下就跑Windows的代码,在Linux下就跑Linux下的代码,这样的话就可以实现语言的跨平台性

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

音频客观测评方法PESQ

一、简介 语音质量感知评估&#xff08;Perceptual Evaluation of Speech Quality&#xff09;是一系列的标准&#xff0c;包括一种用于自动评估电话系统用户所体验到的语音质量的测试方法。该标准于2001年被确定为ITU-T P.862建议书[1]。PESQ被电话制造商、网络设备供应商和电…

ubuntu下anconda装pytorch

1、禁用nouveau sudo vim /etc/modprobe.d/blacklist.conf 在文件最后部分插入以下两行内容 blacklist nouveau options nouveau modeset0 更新系统 sudo update-initramfs -u 重启系统 2、装nvidia驱动 卸载原来驱动 sudo apt-get remove nvidia-* &#xff08;若安装…

Hyperledger Fabric 2.x 环境搭建

Hyperledger Fabric 是一个开源的企业级许可分布式账本技术&#xff08;Distributed Ledger Technology&#xff0c;DLT&#xff09;平台&#xff0c;专为在企业环境中使用而设计&#xff0c;与其他流行的分布式账本或区块链平台相比&#xff0c;它有一些主要的区别。 环境准备…

c++中类的应用综合练习

整理思维导图 课上类实现> 、<、!、||、&#xff01;和后自增、前自减、后自减运算符的重载 代码部分&#xff1a; #include <iostream> using namespace std; class complex {int rel;int vir; public:complex(int rel,int vir):rel(rel),vir(vir){}complex(){}…

视频智能分析平台LiteAIServer未戴安全帽检测算法助力矿山安全:精准监督矿工佩戴安全帽情况

矿山作业环境复杂多变&#xff0c;安全隐患层出不穷。其中&#xff0c;矿工未佩戴安全帽这一行为&#xff0c;看似微不足道&#xff0c;实则潜藏着巨大的安全风险。一旦发生事故&#xff0c;未佩戴安全帽的矿工将极易受到重创&#xff0c;甚至危及生命。因此&#xff0c;确保每…

k8s服务搭建与实战案例

Kubernetes&#xff08;K8s&#xff09;作为一个开源的容器编排平台&#xff0c;广泛应用于现代的云原生应用架构中。以下是一些常见的 **Kubernetes 实战案例**&#xff0c;包括从基础部署到高级应用场景的使用。通过这些案例&#xff0c;可以更好地理解 K8s 的运作原理和最佳…

JAVA学习日记(二十六)网络编程

一、网络编程的概念 常见的软件架构&#xff1a; 二、网络编程三要素 IP&#xff1a;设备在网络中的地址&#xff0c;是唯一的标识 端口号&#xff1a;应用程序在设备中的唯一标识 协议&#xff1a;数据在网络中传输的规则&#xff0c;常见的协议有UDP、TCP、http、https、f…

vue 设置 VUE_APP_TITLE 打包部署后不生效

VUE_APP_TITLE 名门望族云科技有限公司网站 这里的 名门望族云科技有限公司网站 两边不能加 (单引号) 部署后,浏览器刷新网站根目录

黑马头条day01 微服务搭建

1.请求调用流程 如http://localhost:8803/static/js/2.0195d7180dc783c3fe99.js这种静态资源&#xff0c;采用http的发送到本地8803端口的静态资源请求&#xff0c;而nginx配置的监听8801、8802、8803&#xff0c;所以请求走到nginx&#xff0c;nginx的admin配置文件 upstream…

LabVIEW热电偶传感器虚拟仿真实验系统

在教学和科研领域&#xff0c;实验设备的更新和维护成本较高&#xff0c;尤其是在经济欠发达地区&#xff0c;设备的短缺和陈旧化严重影响了教学质量。基于LabVIEW的热电偶传感器虚拟仿真实验系统能够通过模拟实验环境&#xff0c;提供一个成本低廉且效果良好的教学和研究平台。…

Win11 如何真正获取 Trustedinstaller 权限(非修改所有者及权限)

在新的系统中又该如何获得 Trustedinstaller 权限呢&#xff1f; 准备工作 1、首先&#xff0c;我们需要下载并安装 Set-NtTokenPrivilege 命令所需模块&#xff0c;我们先在系统 C 盘根目录新建名为“token”的文件夹。 2、以管理员身份运行 Powershell&#xff0c;然后输入…

ensp 静态路由配置

A公司有广州总部、重庆分部和深圳分部3个办公地点&#xff0c;各分部与总部之间使用路由器互联。广州、重庆、深圳的路由器分别为R1、R2、R3&#xff0c;为路由器配置静态路由&#xff0c;使所有计算机能够互相访问&#xff0c;实训拓扑图如图所示 绘制拓扑图 给pc机配置ip地址…

C语言学习day18:字符串操作/ANSI编码/宽字节/消息框/软件/游戏编码/逆向分析中的编码

今天我们将学习字符串操作&#xff0c;为什么要着重来说这个呢&#xff1f;因为这是为我们之后window开发和api做准备。好的&#xff0c;我们现在正式开始&#xff1a; 字符串 字符串就是一串文字。 比如&#xff1a;"好好学习&#xff0c;天天向上"就是一个字符串…

【论文阅读笔记】One Diffusion to Generate Them All

One Diffusion to Generate Them All 介绍理解 引言二、相关工作三、方法预备知识训练推理实现细节训练细节 数据集构建实验分结论附录 介绍 Paper&#xff1a;https://arxiv.org/abs/2411.16318 Code&#xff1a;https://github.com/lehduong/onediffusion Authors&#xff1…

深入理解偏向锁、轻量级锁、重量级锁

一、对象结构和锁状态 synchronized关键字是java中的内置锁实现&#xff0c;内置锁实际上就是个任意对象&#xff0c;其内存结构如下图所示 其中&#xff0c;Mark Word字段在64位虚拟机下占64bit长度&#xff0c;其结构如下所示 可以看到Mark Word字段有个很重要的作用就是记录…

暂停一下,给Next.js项目配置一下ESLint(Next+tailwind项目)

前提 之前开自己的GitHub项目&#xff0c;想着不是团队项目&#xff0c;偷懒没有配置eslint&#xff0c;后面发现还是不行。eslint的存在可以帮助我们规范代码格式&#xff0c;同时 ctrl s保存立即调整代码格式是真的很爽。 除此之外&#xff0c;团队使用eslint也是好处颇多…

Elasticsearch 架构及 Lucene 索引结构原理入门

文章目录 Elasticsearch 整体架构Lucene 索引结构Lucene 倒排索引核心原理倒排索引倒排表&#xff08;Posting List&#xff09; Elasticsearch 整体架构 一个 ES Index 在集群模式下&#xff0c;有多个Node&#xff08;节点&#xff09;组成&#xff0c;每个节点就是ES的 inst…

【SQL Server报错】在与 SQL Server 建立连接时出现与网络相关的或特定于实例的错误。未找到或无法访问服务器。请验证实例名称是否正确并且 SQL Server 已配置为允许远程连接。

目录 1、报错截图 2、解决步骤 1&#xff09;打开SQL Server配置管理器 2&#xff09;检查SQL Server服务 3&#xff09;检查SQL Server网络配置 4&#xff09;重启SQL Server数据库 1、报错截图 报错界面忘记截图了&#xff0c;我用的SQL Server2019&#xff0c;这里借…

每天40分玩转Django:Django视图和URL

Django视图和URL 一、课程概述 学习项目具体内容预计用时视图基础函数视图、类视图、视图装饰器90分钟URL配置URL模式、路由系统、命名URL60分钟请求处理请求对象、响应对象、中间件90分钟 二、视图基础 2.1 函数视图 # blog/views.py from django.shortcuts import render…

6、AI测试辅助-测试报告编写(生成Bug分析柱状图)

AI测试辅助-测试报告编写&#xff08;生成Bug分析柱状图&#xff09; 一、测试报告1. 创建测试报告2. 报告补充优化2.1 Bug图表分析 3. 风险评估 总结 一、测试报告 测试报告内容应该包含&#xff1a; 1、测试结论 2、测试执行情况 3、测试bug结果分析 4、风险评估 5、改进措施…