【Linux-基础IO】文件描述符重定向原理缓冲区

news2024/11/16 21:45:23

文件描述符

文件描述符的概念和原理

通过上述内容,我们知道使用 open 系统调用打开文件时,系统会返回一个文件描述符。这个描述符用于后续的文件操作。

在C语言中默认会打开三个输入输出流,分别是stdin,stdout,stderr

仔细观察会发现,这三个流的类型都是FILE*,fopen返回值类型是文件指针,C语言库函数式对系统接口的封装。故FILE中必然存在一个保存描述符的变量间,即_fileno

#include <stdio.h>

int main()
{
	printf("stdin->%d\n", stdin->_fileno);
	printf("stdout->%d\n", stdout->_fileno);
	printf("stderr->%d\n", stderr->_fileno);
	return 0;
}

文件描述符描述对应设备
0标准输入键盘
1标准输出显示器
2错误输出显示器

文件描述符fd的本质是:内核的进程:文件映射关系的数组的下标

下面,我们使用系统接口从0号描述符读入内容,并将读入内容写入1号及2号描述符

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>

int main(){
  char buffer[1024];

  read(0, buffer, sizeof(buffer));
  
  printf("write to 1st fd: \n");
  write(1,buffer,strlen(buffer));
  
  printf("write to 2st fd: \n");
  write(2,buffer,strlen(buffer));


  return 0;
}

 

  • 向1号和2号描述符中打印都是输出到显示器,那它们有什么区别呢?

用途上的区别

  • 标准输出(stdout):通常用于程序的正常输出,比如程序的运行结果。

  • 标准错误(stderr):用于程序的错误输出,包括运行时产生的警告和错误信息。

◉ 重定向行为上的区别

  • 标准输出(stdout)和标准错误(stderr)都可以被重定向到文件或其他目的地,但是它们的行为有所不同。

  • 如果标准输出被重定向,而标准错误没有,那么标准错误仍然会默认输出到显示器(如果之前没有重定向过)。

  • 如果标准错误被重定向,而标准输出没有,那么标准输出仍然会默认输出到显示器(如果之前没有重定向过)。

错误信息显示

  • 标准错误(stderr)默认情况下总是显示错误信息,即使它们被重定向。这有助于用户快速识别和解决问题。

  • 标准输出(stdout)则不总是显示错误信息,除非它也被重定向。

重定向的文件描述符

  • 标准输出(stdout)默认情况下被重定向到显示器,文件描述符为 1。

  • 标准错误(stderr)默认情况下也被重定向到显示器,文件描述符为 2。

信号处理上的区别

  • 标准输出(stdout)和标准错误(stderr)在某些情况下(如接收到特定信号时)可能会被特殊处理。例如,当接收到 SIGPIPE 信号时,标准输出(stdout)可能会被关闭,而标准错误(stderr)则不受影响。

标准输出被重定向,而标准错误没有,那么标准错误仍然会默认输出到显示器(如果之前没有重定向过),但是标准输出的内容被打印到指定文件当中

 

  • 那么如何标准错误被重定向呢?

在 UNIX 和类 UNIX 系统中,标准错误(stderr)可以通过不同的方法重定向。这里有几种常见的方法:

  1. 使用 2> 符号

    ./mytest 2> log1.txt

    这会将 mytest 程序的标准错误输出重定向到 log1.txt 文件,而标准输出仍然会发送到屏幕。

  2. 使用 2>> 符号

    ./mytest 2>> log1.txt

    这会将 mytest 程序的标准错误输出追加到 log1.txt 文件,而不是覆盖已有的内容。如果文件不存在,它将创建该文件。

  3. 使用 &> 符号(双竖线)

    ./mytest &> log1.txt

    这会将 mytest 程序的标准输出和标准错误输出都重定向到 log1.txt 文件。如果文件不存在,它将创建该文件。

  4. 使用 2>| 符号

    ./mytest 2>| command

    这会将 mytest 程序的标准错误输出通过管道传递给 command 命令。

  5. 使用 2>& 符号

    ./mytest 2>&1

    这会将 mytest 程序的标准错误输出重定向到标准输出(stdout),这意味着错误信息将与正常输出一起显示在屏幕上。

请注意,重定向操作符 >>> 必须位于命令的最后一个参数之前,并且它们不能与 & 操作符同时使用。如果您需要同时重定向标准输出和标准错误,请使用 &> 操作符。

将打印到2号描述符的内容打印到err.txt,打印到1号描述符的内容打印到显示器

将写入到1号和2号文件描述符的内容输入到 all.txt,则需要执行 ./mytest > all.txt 2>&1./mytest > all.txt 1>&2二者输出结果相同

代码解释:

这个命令行 ./mytest > all.txt 2>&1 执行了以下操作:

  1. 标准输出重定向:首先,mytest 程序的标准输出(stdout)被重定向到文件 all.txt。这意味着 mytest 程序的输出(除了错误信息)不会显示在屏幕上,而是会被写入到 all.txt 文件中。

  2. 标准错误重定向:然后,mytest 程序的标准错误输出(stderr)被重定向到标准输出(stdout)。由于标准输出已经被重定向到 all.txt 文件,因此 mytest 程序的错误信息也会被写入到同一个文件中。

  3. 文件描述符 2:在 UNIX 系统中,文件描述符 2 通常用于标准错误输出。当使用 2>&1 时,实际上是将标准错误输出重定向到文件描述符 1,即先将标准错误输出重定向到标准输出,然后再将标准输出重定向到指定的文件。

综上所述,这个命令行会将 mytest 程序的所有输出(包括正常输出和错误信息)写入到 all.txt 文件中,而不会在屏幕上显示任何输出。


 

在 Linux 内核中,文件系统(Filesystem)是操作系统的一部分,用于管理和存储文件和目录。每个进程在内存中维护一个打开的文件列表,这个列表通过 struct files_struct 结构体来管理。每个打开的文件在 struct files_struct 中都有一个对应的 struct file 结构体,用于保存该文件的信息。

以下是 struct filestruct files_struct 结构体的简要概述:

struct file 结构体

struct file 结构体包含了关于打开的文件的信息,包括文件描述符、文件操作指针、文件状态、文件大小等。每个打开的文件都对应一个 struct file 实例。

struct files_struct 结构体

struct files_struct 结构体用于保存当前进程打开的文件列表。每个进程都有一个自己的 struct files_struct 实例。它包含了指向 struct file 实例的指针数组,以及一些管理打开文件的信息,如文件描述符表等。

当一个进程打开一个文件时,内核会创建一个 struct file 实例来保存该文件的信息,并将其添加到该进程的 struct files_struct 中的文件描述符表中。当进程关闭文件时,内核会从文件描述符表中移除对应的 struct file 实例,并释放与之相关的资源。

通过这种方式,Linux 内核能够有效地管理每个进程打开的文件,确保资源的正确分配和释放,从而避免资源泄露和文件描述符冲突等问题。

所以,本质上,文件描述符就是数组下标(即当前进程的文件描述符表下标)。只要取得对应的文件描述符,就能找到对应的文件。

 

文件描述符分配规则

系统会默认打开三个标准输入输出流stdin(0号)、stdout(1号)、stderr(2号),此时我们再打开一个文件时会默认分配3号描述符,再打开一个文件,那就会被自动分配4号描述符;但是如果我们关闭2号,再打开一个新的文件时,该文件的描述符为2。由此可知,文件描述符分配规则是:分配当前最小的未使用的文件描述符

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>

int main()
{
	printf("stdin->%d\n", stdin->_fileno);
	printf("stdout->%d\n", stdout->_fileno);
	printf("stderr->%d\n", stderr->_fileno);
	
	int fd1 = open("./log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
	printf("fd1->%d\n", fd1);
	
	//关闭标准输入
	close(0);
	int fd2 = open("./log2.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
	printf("fd2->%d\n", fd2);

	//关闭标准错误
	close(2);
	int fd3 = open("./log3.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
	printf("fd3->%d\n", fd3);

	close(fd1);
	close(fd2);
	close(fd3);
	return 0;
}

访问文件的本质

当进程打开一个文件的时候,操作系统会创造对应的数据结构;在类Unix操作系统中,struct file是内核中的一个数据结构,用于表示打开的文件。它通常包含文件位置(当前读写操作的文件偏移量)、文件操作(指向一组函数指针,这些指针定义了对文件执行各种操作的方法,如读写、打开、关闭等。)、文件状态标志(如读写模式、同步等。)、引用计数(表示有多少进程或文件描述符引用了这个struct file)、文件信息(如文件大小、最后修改时间等)等

文件访问的流程:

打开文件

  • 当进程调用open系统调用时,操作系统会创建一个struct file结构体,并将其地址放入进程的文件描述符表中。

  • 如果文件已经被其他进程打开,则操作系统会增加现有struct file的引用计数,而不是创建一个新的。

文件操作

  • 进程通过文件描述符执行读写操作时,操作系统会根据文件描述符找到对应的struct file,并执行相应的文件操作。

关闭文件

  • 当进程调用close系统调用时,操作系统会减少struct file的引用计数。

  • 如果引用计数降到0,操作系统会释放struct file结构体,并可能执行清理操作,如关闭文件描述符、释放内存等。

当多个进程打开同一个文件时,它们共享同一个struct file结构体,但每个进程都有自己的文件描述符,指向这个共享的struct file。这意味着即使一个进程关闭了文件,只要其他进程还在使用该文件,struct file就不会被释放

重定向原理

我们前面已经使用了重定向,这里我们系统的学习一下重定向

在计算机科学中,特别是操作系统的上下文中,重定向指的是改变输入输出流的方向,使其从一个默认的源或目的地转移到另一个源或目的地。我们之前就使用过输出重定向>>>输入重定向<错误输出重定向 (2>) 合并重定向 (&>) 管道(|当然还有其他的就不介绍了

dup2系统调用

在类Unix操作系统中,dup2 是一个系统调用,用于复制一个现有的文件描述符到另一个指定的文件描述符。

 

dup2的本质:dup2() makes newfd be the copy of oldfd, closing newfd first if necessary)(文件描述符下标所对应的内容拷贝

如果我们希望将 fd 描述符被1号替换则可以使用 dup2(fd , 1)

【示例1】

#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>

int main()
{
   // close(0);
   // close(2);
   // close(1);
   umask(0);
   int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
   if(fd == -1)
   {
       perror("open");
       exit(1);
   }
   //重定向
   dup2(fd,1);
   printf("fd:%d\n",fd);
 
   //这里必须刷新一下,不然log.txt里面没有内容
   fflush(stdout);
   close(fd);
 
   return 0;
}

可以发现我们执行可执行程序的时候,并未在命令行打印,反而打印到文件当中;

 

如果我们把dup2(fd,1)注释了,那么重新执行的结果;即:printf默认输出到stdout上(显示器)

而文件内部不会打印

所以说重定向的本质:是在内核中改变文件描述符表特定下表的内容,与上层无关

缓冲区的理解

在C语言里相关IO函数与系统调用是对应的,并且相关库函数是对系统调用的封装;所以本质上,C语言访问文件就是通过fd(文件描述符访问的)。所以在C语言内FILE结构体,必定有fd(文件描述符)

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
	const char* msg1 = "printf\n";
	const char* msg2 = "fprintf\n";
	const char* msg3 = "fwrite\n";
	const char* msg4 = "write\n";
	
	printf("%s", msg1);
	fprintf(stdout, "%s", msg2);
	fwrite(msg3, strlen(msg3), sizeof(char), stdout);
	write(1, msg4, strlen(msg4));

	fork();
}

执行结果:

如果将输出重定向到log.txt呢?

 

可以看出,如果向屏幕输出,则每个语句打印一次;如果将结果重定向到指定文件中,除了操作系统提供的write打印一次,其余的C语言接口均打印两次,这是为什么?并且重定向后为什么write先打印出来呢?

这个现象的原因在于 fork() 系统调用后的进程行为以及标准输出的缓冲机制。

1️⃣ 缓冲区是什么?本质就是一块连续的空间!

2️⃣ 为什么要有缓冲区?给上层提供高效的IO体验,间接提高整体的效率

3️⃣ 缓冲区的刷新策略:

◉ 立即刷新(无缓冲):fflush(stdout);ffsync(int fd)

◉ 行缓冲(显示器):照顾用户的查看习惯

◉ 全缓冲(缓冲区写满才刷新):常用于普通文件的写入,该缓冲区在进程退出时也会刷新

4️⃣缓冲区分为用户级缓冲区和内核级缓冲区

【解答上面问题】

首先我们已经了解到./test默认是向显示器文件进行写入,显示器文件默认是行刷新

./test > log.txt这个是向普通文件进行写入,刷新策略也会发生变化,变为全缓冲

在fork()前 write是系统调用,直接写到操作系统内核,甚至已经写到硬件中

但printf等是stdout对应的缓冲区是用户级别的缓冲区,用户级别是全缓冲,此时缓冲区没有写满,不会加载到操作系统内核,而调用fork()后,进程结束时,父子进程会都把缓冲区刷新一遍,刷新就是读取,就是写时拷贝,所以出现了父进程打印一遍内容,子进程打印一遍内容。

C语言中的缓冲区 语言中的缓冲区

都在这个struct _IO _FILE中,所以每一个文件都有自己的缓冲区(printf,scanf)

 

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

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

相关文章

JSP(Java Server Pages)基础使用二

简单练习在jsp页面上输出出乘法口诀表 既然大家都是来看这种代码的人了&#xff0c;那么这种输出乘法口诀表的这种简单算法肯定是难不住大家了&#xff0c;所以这次主要是来说jsp的使用格式问题。 <%--Created by IntelliJ IDEA.User: ***Date: 2024/7/18Time: 11:26To ch…

Web端云剪辑解决方案,提供多轨视频、音频、特效、字幕轨道可视化编辑

传统视频剪辑软件的繁琐安装、高昂硬件要求以及跨平台协作的局限性&#xff0c;让无数创意者望而却步。美摄科技作为云端视频编辑技术的领航者&#xff0c;携其革命性的Web端云剪辑解决方案&#xff0c;正重新定义视频创作的边界&#xff0c;让专业级视频剪辑触手可及&#xff…

LeetCode 909. 蛇梯棋

LeetCode 909. 蛇梯棋 给你一个大小为 n x n 的整数矩阵 board &#xff0c;方格按从 1 到 n2 编号&#xff0c;编号遵循 转行交替方式 &#xff0c;从左下角开始 &#xff08;即&#xff0c;从 board[n - 1][0] 开始&#xff09;的每一行改变方向。 你一开始位于棋盘上的方格 …

微服务(一)

目录 一、概念 1、单体架构 2、微服务 3、springcloud 二、微服务的拆分 1、微服务的拆分原则 1.1 什么时候拆 1.2 怎么拆 2、服务调用 2.1 resttemplate 2.2 远程调用 一、概念 1、单体架构 单体架构&#xff08;monolithic structure&#xff09;&#xff1a;顾名…

项目启动卡住不动Property ‘mapperLocations‘ was not specified.

问题如上图所示&#xff1b; 原因&#xff1a;在mapper打了个断点&#xff01;

js实现多行文本控件textarea,根据文本内容自适应窗口全部显示

概述 本人在使用html控件textarea&#xff0c;多行显示的时候&#xff0c;希望根据后台实际的文本&#xff0c;来全部显示文本内容&#xff0c;而不用再去操作滚动条查看全部文本。 本功能实现的难点在于&#xff0c;计算当前文本显示有多少行。 软件环境 编辑器&#xff1a…

8.11Zero Crossing Detection (零交叉检测)

基本概念 零交叉检测是一种基于二阶导数的边缘检测方法&#xff0c;它通过查找二阶导数过零点来定位边缘。 注意: OpenCV没有直接提供这种检测方法&#xff0c;但可以通过结合其他函数来实现。 在OpenCV中&#xff0c;基于C的Zero Crossing Detection&#xff08;零交叉检测&…

关于PHP方面需要掌握的一些基础语法

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///C爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于【PHP的基础语法】相关内容&#xff01;…

Unity开发绘画板——03.简单的实现绘制功能

从本篇文章开始&#xff0c;将带着大家一起写代码&#xff0c;我不会直接贴出成品代码&#xff0c;而是会把写代码的历程以及遇到的问题、如何解决这些问题都记录在文章里面&#xff0c;当然&#xff0c;同一个问题的解决方案可能会有很多&#xff0c;甚至有更好更高效的方式是…

零售业的数字化转型与消费者体验升级

在数字化浪潮的推动下&#xff0c;零售业正经历着前所未有的变革。数字化转型不仅为零售商带来了新的商业模式和运营效率的提升&#xff0c;更重要的是&#xff0c;它极大地提升了消费者的购物体验。金智维将探讨零售业如何通过数字化转型&#xff0c;实现线上线下融合、智能推…

【架构】NewSQL

文章目录 NewSQLTiDBTiDB 主要组件特点使用场景安装与部署 推荐阅读 NewSQL NewSQL是一种数据库管理系统(DBMS)的类别&#xff0c;它结合了NoSQL数据库的可扩展性和传统SQL数据库的事务一致性。具体来说&#xff0c;NewSQL数据库旨在解决传统关系型数据库在处理大规模并发事务…

通过pyenv local 3.6.1 这里设置了当前目录的python版本,通过pycharm基于这个版本创建一个虚拟环境

要在 PyCharm 中基于你通过 pyenv local 设置的 Python 版本创建虚拟环境&#xff0c;可以按照以下步骤进行操作&#xff1a; 步骤 1: 获取当前使用的 Python 路径 通过 pyenv 查找当前项目下的 Python 解释器路径&#xff0c;使用以下命令&#xff1a; pyenv which python …

Thread , ThreadLocal , ThreadLocalMap , Entry 之间的关系?

Thread , ThreadLocal , ThreadLocalMap , Entry 之间的关系&#xff1f; 首先ThradLocal是线程的本地副本&#xff0c;怎么理解这句话呢&#xff1f;一个Thread都有一个它自己的ThreadLocalMap。ThreadLocalMap不是HashMap的结构&#xff0c;而是一个Entry数组&#xff0c;里面…

报错解决方案

大模型-报错解决方案 百度千帆大模型 仅个人笔记使用&#xff0c;感谢点赞关注 百度千帆大模型 未开通付费模型 qianfan.errors.APIError: api return error, req_id: code: 17, msg: Open api daily request limit reached 可能的原因: 未开通所调用服务的付费权限&#xff0…

【设计模式-观察者模式】

定义 观察者模式&#xff08;Observer Pattern&#xff09;是一种行为型设计模式&#xff0c;用于定义一对多的依赖关系&#xff0c;让多个观察者对象同时监听某一个主题对象&#xff08;被观察者&#xff09;的状态变化。当主题状态发生变化时&#xff0c;所有依赖于它的观察…

00DSP学习-F28379D学习准备(了解一个工程的构成)

叠甲 我也算初学F28379D&#xff0c;不对之处请大家斧正。不同型号的DSP在外设配置的函数上有一些区别&#xff0c;但是掌握一种对其他型号的来说则难度不大。对于我们而言学习DSP最终还是要用于算法验证&#xff0c;而DSP资源的最大化利用、代码效率提升等则是后话。 软件准…

大数据-146 Apache Kudu 安装运行 Dockerfile 模拟集群 启动测试

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

进阶SpringBoot之分布式系统与 RPC 原理

分布式系统是若干独立计算机的集合&#xff0c;这些计算机对于用户来说就像单个相关系统 分布式系统是由一组通过网络进行通信&#xff0c;为了完成共同的任务而协调工作的计算机节点组成的系统 其目的是利用更多的机器&#xff0c;处理更多的数据 RPC&#xff08;Remote Pr…

【Day20240924】05git 两人协作 冲突

git 两人协作 冲突 命令行解决 两个人修改同一文件时 的冲突可视化解决 两个人修改同一文件时 的冲突参考 命令行解决 两个人修改同一文件时 的冲突 假设kerwin.js是项目的路由文件。tiechui文件夹是组员铁锤的工作目录&#xff1b;test2008文件夹是组长的工作目录。此时&…

JAVA基本简介(期末)

1、JDK JRE JVM &#xff08;1&#xff09;JDK JAVA标准开发包&#xff0c;提供了编译、运行JAVA程序所需的各种工具和资源&#xff0c;包括JAVA编译器、JAVA运行时的环境&#xff0c;及常用的JAVA类库等 &#xff08;2&#xff09;JRE JAVA运行环境&#xff0c;用于解释执行JA…