我与Linux的爱恋:基础IO 文件描述符重定向缓冲区


外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

🔥个人主页guoguoqiang. 🔥专栏Linux的学习

Alt

文章目录

  • 文件描述符
    • 文件描述符分配规则
    • 访问文件的本质
  • 重定向原理
  • 缓冲区的理解

文件描述符

通过上述内容,我们知道使用 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;
}

在这里插入图片描述
在这里插入图片描述
文件描述符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):专门用于输出程序运行过程中的错误信息,如语法错误、运行时错误(例如除以零错误、内存访问违规)、无法打开文件等异常情况相关的提示。它能帮助开发者和用户快速定位程序出现问题的地方。
◉输出特性
标准输出:输出内容的格式通常较为灵活,可以根据程序设计来呈现出美观、易读的形式。它往往有一定的缓冲机制,在某些情况下,数据不会立即显示在终端,而是先存储在缓冲区,当缓冲区满或者遇到特定条件(如换行符)时才输出。不过,这种缓冲机制可以通过编程手段修改,例如在 C 语言中使用setbuf等函数。
标准错误:输出格式更注重清晰地表达错误内容,一般没有像标准输出那样丰富的格式控制(但也可以在程序中自行设定)。标准错误通常是无缓冲的,这样能保证错误信息可以即时显示在终端,让用户第一时间发现问题,便于调试。
◉重定向行为
标准输出:可以很方便地重定向到文件或其他输出设备。在命令行环境中,使用>操作符就能将标准输出重定向到指定文件。例如python my_program.py > output.txt会把程序my_program.py的标准输出内容保存到output.txt文件中,而不是在屏幕上显示。
标准错误:有其独立的重定向方式。在命令行中,使用2>操作符可将标准错误重定向。例如python my_program.py 2> error.txt会把程序运行中的错误信息保存到error.txt文件中。并且,还可以将标准输出和标准错误分别重定向到不同的文件,以实现对正常输出和错误信息的分别处理。
◉ 错误信息显示:
标准错误(stderr)默认情况下总是显示错误信息,即使它们被重定向。这有助于用户快速识别和解决问题。
标准输出(stdout)则不总是显示错误信息,除非它也被重定向。
◉ 重定向的文件描述符:
标准输出(stdout)默认情况下被重定向到显示器,文件描述符为 1。
标准错误(stderr)默认情况下也被重定向到显示器,文件描述符为 2。
◉ 信号处理上的区别:
标准输出(stdout)和标准错误(stderr)在某些情况下(如接收到特定信号时)可能会被特殊处理。例如,当接收到 SIGPIPE 信号时,标准输出(stdout)可能会被关闭,而标准错误(stderr)则不受影响。

标准输出被重定向,而标准错误没有,那么标准错误仍然会默认输出到显示器(如果之前没有重定向过),但是标准输出的内容被打印到指定文件当中
在这里插入图片描述
在这里插入图片描述
那么如何将标准错误重定向呢?

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

  1. 使用 2> 符号:
    ./test 2> log1.txt
    这会将 test 程序的标准错误输出重定向到 log1.txt 文件,而标准输出仍然会发送到屏幕。
    2.使用 2>> 符号:

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

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

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

4.使用 2>| 符号:

./test 2>| command 这会将 test 程序的标准错误输出通过管道传递给 command 命令。

5.使用 2>& 符号:

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

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

将打印到2号描述符的内容打印到err.txt,打印到1号描述符的内容打印到显示器
在这里插入图片描述
将写入到1号和2号文件描述符的内容输入到 all.txt,则需要执行 ./test > all.txt 2>&1 或./test > all.txt 1>&2或./test &>all.txt输出结果相同

在 UNIX 系统中,文件描述符 2 通常用于标准错误输出。当使用 2>&1 时,实际上是将标准错误输出重定向到文件描述符 1,即先将标准错误输出重定向到标准输出,然后再将标准输出重定向到指定的文件。
在这里插入图片描述
在 Linux 内核中,文件系统(Filesystem)是操作系统的一部分,用于管理和存储文件和目录。每个进程在内存中维护一个打开的文件列表,这个列表通过 struct files_struct 结构体来管理。每个打开的文件在 struct files_struct 中都有一个对应的 struct file 结构体,用于保存该文件的信息。

以下是 struct file 和 struct 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)
它的主要目的是创建一个新的文件描述符newfd,使其成为oldfd的副本。这意味着对newfd执行的 I/O 操作与对oldfd执行的 I/O 操作完全相同。
在dup2(fd, 1)中,fd是一个已存在的文件描述符,它可以指向各种类型的 I/O 资源,比如文件、管道、套接字等。而1是标准输出的文件描述符。
当执行dup2(fd, 1)时,如果fd是有效的,那么它会将标准输出(通常是终端屏幕)重定向到fd所指向的资源。例如,如果fd指向一个文件,那么后续所有原本输出到屏幕的内容(比如使用printf等函数输出的信息)都将被写入到fd所指向的文件中。
如果fd本身就是1,则dup2函数只是简单地返回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(文件描述符)
在这里插入图片描述
如果重定向到log.txt中呢?
在这里插入图片描述
可以看出,面向屏幕输出,则每个语句打印一次;如果将结果重定向到指定文件,除了操作系统提供的write打印一次,其他c语言打印两次,这是为什么?并且为什么重定向后是write先打印出来呢?
这个现象是因为fork()系统调用后进程行为以及标准输出的缓冲机制
缓冲区是什么?本质就是一块连续的空间!

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

缓冲区的刷新策略:

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

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

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

缓冲区分为用户级缓冲区和内核级缓冲区

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

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

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

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

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

缓冲区的作用
提高效率:当数据在不同速度的设备或进程间传输时,缓冲区可作为中间存储。例如,从磁盘读取文件时,磁盘的读取速度相对较慢。将数据先读入缓冲区,然后程序再从缓冲区获取数据进行处理,这样可以减少对磁盘的频繁访问,提高整体效率。
数据暂存:方便对数据进行批量处理。比如,在网络编程中接收网络数据时,可能不会每次收到少量数据就立刻处理,而是先将数据存放在缓冲区,等缓冲区满或者满足一定条件后再统一处理。

都在这个struct _IO _FILE中,所以每一个文件都有自己的缓冲区(printf,scanf)
在这里插入图片描述

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

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

相关文章

Java每日刷题之二分算法

34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣&#xff08;LeetCode&#xff09; 转化 通过题目时间复杂度为O(logN),我们就可以联想到二分算法&#xff0c;但是我们前面学到的算法&#xff0c;是查找出&#xff0c;有序数组里的值&#xff0c;并不是求其中的范围&a…

qt QBrush详解

1、概述 QBrush是Qt框架中的一个基本图形对象类&#xff0c;它主要用于定义图形的填充模式。QBrush可以用于填充如矩形、椭圆形、多边形等形状&#xff0c;也可以用于绘制背景等。通过QBrush&#xff0c;可以设置填充的颜色、样式&#xff08;如实心、渐变、纹理等&#xff09…

postman如何安装旧版本不升级(以9.31和11.10版本为例)

postman版本超过10.x&#xff08;包含10.x)&#xff0c;有个大的麻烦&#xff0c;就是需要登录账号&#xff0c;如果网络不佳&#xff08;其实是外网受限&#xff09;,那就很难受了 功能页面都进不去了&#xff01;而8.x /9.x等以下版本就不需要登录了。 比如9.31.30这个版本就…

线程函数和线程启动的几种不同形式

线程函数和线程启动的几种不同形式 在C中&#xff0c;线程函数和线程启动可以通过多种形式实现。以下是几种常见的形式&#xff0c;并附有相应的示例代码。 1. 使用函数指针启动线程 最基本的方式是使用函数指针来启动线程。 示例代码&#xff1a; #include <iostream&g…

Arm和高通闹翻在即,或影响骁龙 8 Elite

原文转载修改自&#xff08;更多互联网新闻/搞机小知识&#xff09;&#xff1a; Arm向高通下达最后通牒&#xff0c;骁龙 8 Elite或受影响 其实对于小江这个不咋关注安卓圈的用户来说&#xff0c;高通和Arm大概就是秤不离砣&#xff0c;砣不离秤的关系。不过在看了今天的最新…

【学术精选】SCI期刊《Electronics》特刊“New Challenges in Remote Sensing Image Processing“

英文名称&#xff1a;New Challenges in Remote Sensing Image Processing 中文名称&#xff1a;"遥感图像处理的新挑战"特刊 期刊介绍 “New Challenges in Remote Sensing Image Processing”特刊隶属于《Electronics》期刊&#xff0c;聚焦遥感图像处理领域快速…

人机环境系统智能是东方天地人思想与西方科技思维的融合

西方科技思维常常受到还原主义的影响&#xff0c;这种思维方式通常强调将复杂系统拆解为更简单的部分进行分析&#xff08;比如物理的分子、原子、电子、夸克……&#xff0c;数理中的分解因式&#xff09;。以下是还原主义的一些特点&#xff1a; 分析方法&#xff1a;强调通过…

深度学习:梯度下降算法简介

梯度下降算法简介 梯度下降算法 我们思考这样一个问题&#xff0c;现在需要用一条直线来回归拟合这三个点&#xff0c;直线的方程是 y w ^ x b y \hat{w}x b yw^xb&#xff0c;我们假设斜率 w ^ \hat{w} w^是已知的&#xff0c;现在想要找到一个最好的截距 b b b。 一条…

瑞芯微RK3566/RK3568 Android11下该如何默认屏蔽导航栏/状态栏?看这篇文章就懂了

本文介绍瑞芯微RK3566/RK3568在Android11系统下&#xff0c;默认屏蔽导航栏/状态栏方法&#xff0c;使用触觉智能Purple Pi OH鸿蒙开发板演示&#xff0c;搭载了瑞芯微RK3566芯片&#xff0c;类树莓派设计&#xff0c;Laval官方社区主荐&#xff0c;已适配全新OpenHarmony5.0 R…

Python | Leetcode Python题解之第519题随机翻转矩阵

题目&#xff1a; 题解&#xff1a; class Solution:def __init__(self, m: int, n: int):self.m mself.n nself.total m * nself.map {}def flip(self) -> List[int]:x random.randint(0, self.total - 1)self.total - 1# 查找位置 x 对应的映射idx self.map.get(x,…

从0学习React(6)

这两天在写IT资产管理的时候&#xff0c;对于前端&#xff0c;我一直没有任何头绪&#xff0c;不知道怎么写。因为我之前并没有学习过前端方面的知识&#xff0c;我学的都是后端。我在写后端接口的时候&#xff0c;我是自己有一些基础的&#xff0c;然后我又参照着模仿着一些很…

单个相机矫正畸变

1、通过标定助手获取到内参外参&#xff0c;外参在此无效&#xff0c;只用到了内参 2、然后通过halcon算子进行矫正 参考&#xff1a;超人视觉

乘云而上,OceanBase再越山峰

一座山峰都是一个挑战&#xff0c;每一次攀登都是一次超越。 商业数据库时代&#xff0c;面对国外数据库巨头这座大山&#xff0c;实现市场突破一直都是中国数据库产业多年夙愿&#xff0c;而OceanBase在金融核心系统等领域的攻坚克难&#xff0c;为产业突破交出一副令人信服的…

laravel: Breeze 和 Blade, 登录 注册等

composer require laravel/breeze --dev php artisan breeze:install php artisan migrate npm install npm run build php artisan route:clear http://laravel-dev.cn/ http://laravel-dev.cn/register http://laravel-dev.cn/login

VS+Qt解决提升控件后,包含头文件格式不对问题处理

一、前言 VSQt 提升控件后&#xff0c;在uic目录下会生成ui相关的初始化文件&#xff0c;对于提升的控件头文件包含的格式为#include<> 而非 #include “ ” 导致无法找到头文件。如果手动修改为 #include “ ”相当麻烦&#xff0c;甚至每次编译都要修改一遍&#xff0c…

一些硬件知识【2024/11/3】

MLCC电容的ESR比较小&#xff0c;但是他的缺点是容量通常比较低&#xff1b;电解电容的容量比较大&#xff0c;但是他的ESR比较大&#xff1b;而钽电容介于两者之间&#xff0c;是MLCC找不到合适的容值比较大的时候&#xff0c;找钽电容&#xff1a; LC谐振电路&#xff1a; 五…

简单的ELK部署学习

简单的ELK部署学习 1. 需求 我们公司现在使用的是ELK日志跟踪&#xff0c;在出现问题的时候&#xff0c;我们可以快速定为到问题&#xff0c;并且可以对日志进行分类检索&#xff0c;比如对服务名称&#xff0c;ip , 级别等信息进行分类检索。此文章为本人学习了解我们公司的…

SpringBoot篇(自动装配原理)

目录 一、自动装配机制 1. 简介 2. 自动装配主要依靠三个核心的关键技术 3. run()方法加载启动类 4. 注解SpringBootApplication包含了多个注解 4.1 SpringBootConfiguration 4.2 ComponentScan 4.3 EnableAutoConfiguration 5. SpringBootApplication一共做了三件事 …

科学减肥:基础代谢减肥法

基础代谢减肥法是什么&#xff1f; 相信有很多人在减肥的路上都有做过努力&#xff0c;但是效果往往和努力不匹配。那是因为很多时候在细节上面没有做对&#xff0c;大家都是根据自己的理解去努力&#xff0c;而没有深度去了解过身体营养成分构成与吸收。 没科学了解过减肥的原…

华为云安装docker

docker_docker-compose_install: 代替官网的一键安装脚本&#xff0c;使用docker包进行离线安装 bash <(curl -sL https://raw.githubusercontent.com/1scripts/docker_docker-compose_install/main/quick_install.sh) 华为镜像&#xff1a; https://console.huaweicloud.…