[Linux] 逐层深入理解文件系统 (2)—— 文件重定向

标题:[Linux] 逐层深入理解文件系统 (2)—— 文件重定向

个人主页@水墨不写bug

(图片来源于网络)

目录

一、文件的读取和写入

 二、文件重定向的本质

1.手动模拟重定向的过程——把标准输出重定向到redir.txt

2.重定向函数dup2

3.命令行的重定向指令操作 

四、缓冲区的刷新策略


正文开始:

一、文件的读取和写入

        在深入理解文件系统(1)中,我们深入了解了文件的打开与关闭:操作系统通过文件描述符fd来对文件进行管理操作。此外,我们知道了文件的系统调用级别的打开方式,但是我们还没有了解如何读写文件的内容。

文件的写入同样有对应的系统调用:

 参数列表:

        fd:想要写入的文件的对应的文件描述符(对打开文件后会得到对应的文件描述符)

        *buf:这是一个我们定义的数组,是一个缓冲区。

        count:写入的字节数

返回值:

        成功,返回写入的字节数;失败,返回-1,并设置错误码。

文件的读取的系统调用:

参数列表:

        fd:想要读取的文件的对应的文件描述符

        *buf:把文件的内容读取到这个数组中。

        count:读取的字节数。

返回值:

        成功,返回读取的字节数;失败,返回-1,设置错误码。

 二、文件重定向的本质

        文件重定向的本质是在内核中改变文件描述符表的特定下标,和上层的语言层面的无关。

1.手动模拟重定向的过程——把标准输出重定向到redir.txt

        1)使用系统调用close关闭  标准输出(fd==1的文件)

        2)使用系统调用open打开redir.txt文件

        这个时候向标准输出打印信息就会被重定向到redir.txt文件中。

底层原理:

        首先我们关闭了标准输出(文件fd==1的文件),文件描述符标的下标1的这位置就被空出来了,由于文件描述符的分配规则是从小到大来分配的,当我们打开一个新的文件(redir.txt)这个文件的fd就会被分配为1。

        我们向标准输出写入,本质是向文件描述符表下标1的位置对应的内核级缓冲区写入,由于redir.txt分配到了1这个位置,所以向原来的标准输出的缓冲区写入,就是向redir.txt的缓冲区写入。

        这样就实现了文件的重定向。

 关闭标准输出后打开redir.txt:


2.重定向函数dup2

函数原型 :

简单总结:

        函数作用:让newfd对应的内容成为oldfd对应的内容的拷贝,本质是文件描述符下标所对应的内容的拷贝,并在结束的时候首先关闭newfd。

注意:

        1)oldfd不是一个有效的fd,则调用失败, newfd不会被关闭。

        2)oldfd是有效的fd,但是newfd和oldfd对应同一个文件,dup2函数不做任何事。

         dup2系统调用函数的意义在于可以让我们的重定向操作更加方便比如如果我们还是想要把标准输出重定向到redir.txt文件,那么只需要调用一个系统调用函数即可:

int fd1 = open("redir.txt",O_CREAT | O_WDONLY);dup2(fd1,1);//这样就实现了把标准输出重定向到fd1

3.命令行的重定向指令操作 

       我们写出这样的一个代码:

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

        编译完运行:

         发现标准输出和标准错误都是显示器,这符合预期。


        对一个项目,我们可以把运行结果重定向到不同的日志文件中,方便后续维护:

        上面的操作是把运行结果(向显示器输出重定向到两个文件中):

        标准输出重定向到ok.txt;

        标准错误重定向到err.txt。 

       


如果不加fd直接重定向,会仅仅把标准输出重定向:

         标准错误仍被打印到显示器。

       


如果想把标准输出和标准错误重定向到一个文件,需要:


四、缓冲区的刷新策略

        在之前,我们知道在文件IO中 缓冲区存在两个,一个是C语言层面的缓冲区,一个是系统内核级缓冲区,但是我们并不了解缓冲区究竟是什么。

        是什么:缓冲区就是一段连续的内存空间。

        为什么:将系统调用和与硬件交互解耦,将C语言函数调用与系统调用解耦;提高刷新效率,从而在整体上提高IO效率,为用户提供高效的IO体验。

        怎么办:这就需要谈到不同的刷新策略问题了。

        对于不同的文件,缓冲区的刷新策略不同。常见刷新策略有如下几种:

                1)立刻刷新。比如调用fflush(stdout)(立刻刷新语言缓冲区)   ,   fsync(fd)(立刻刷新系统缓冲区)等。

                2)行刷新。显示器,因为显示器需要照顾用户查看习惯。

                3)  全缓冲,缓冲区写满才刷新。比如普通文件。

此外,对于特殊情况也会进行缓冲区刷新:

                a)进程退出,系统自动刷新缓冲区

在了解了缓冲区刷新策略之后,我们看看下面这样的场景:

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

         编译成功运行结果:

当我们重定向到log.txt,并打印出来:

         发现顺序不一样了,原因在于缓冲区向文件写入时刷新策略发生了变化:

        对一次是向显示器写入,刷新策略是按行刷新,由于三次打印有带有换行符,所以是按照代码的顺序打印输出。

        第二次是向文件写入,策略是全缓冲,所以尽管带有换行符,前两次写入都是暂时写入到了C语言级别的缓冲区内了,没有立刻被刷新到内核级缓冲区内。但是write是系统调用,直接刷新到了内核级缓冲区,所以最先被写入文件,后来在进程结束前,语言级缓冲区刷新到内核级,再被刷新到磁盘的文件中。

 这个场景还有一个变式:

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>int main()
{printf("hello printf\n");fprintf(stdout,"hello fprintf\n");const char* msg = "hello write\n";write(1,msg,strlen(msg));fork();return 0;
}

运行后直接输出到显示器:

        在意料之内。

但是重定向到log.txt文件并cat打印出来:

        发现了不对劲,其实造成这样的现象本质也是缓冲区刷新策略的变化导致的。

        write是系统调用,会直接刷新到内核级缓冲区。但是这个时候前两个语句已经被执行了,但是数据还存在于语言缓冲区内。这个时候fork创建了一个子进程,子进程的数据和父进程相同,在进程结束之前,两个进程都刷新了自己的语言级缓冲区,导致前两个打印语句被执行了两次! 


完~

未经作者同意禁止转载

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

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

相关文章

分享两种安装windows系统教程,学会后再也不需要花钱装系统了。

前期准备工作&#xff1a; 需要一个8G或16G的空U盘需要你安装的系统的镜像文件 一般是一个以 .iso 后缀结尾的文件 2.1 镜像文件获取方式 1&#xff09; 去windows 官网获取 2&#xff09;去 我告诉你 网址下载所需要的镜像文件 这个网址 分享了很多 我们常用的系统 大家可以按…

C++面向对象--------继承篇

目录 一.继承&#xff08;重点&#xff09; 1.1 概念 1.2 构造函数 1.2.1 派生类与基类的构造函数关系 1.2.2 解决方案 1.2.2.1 补充基类的无参构造函数 1.2.2.2 手动在派生类中调用基类构造函数 1.2.2.2.1 透传构造 1.2.2.2.2 委托构造 1.2.2.2.3 继承构造 1.3 对象…

中标麒麟v5安装qt512.12开发软件

注意 需要联网操作 遇到问题1&#xff1a;yum提示没有可用软件包问题 终端执行如下命令 CentOS7将yum源更换为国内源保姆级教程 中标麒麟V7-yum源的更换&#xff08;阿里云源&#xff09; wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Cento…

mysql 慢查询日志slowlog

慢查询参数 slow log 输出示例 # Time: 2024-08-08T22:39:12.80425308:00 #查询结束时间戳 # UserHost: root[root] localhost [] Id: 83 # Query_time: 2.331306 Lock_time: 0.000003 Rows_sent: 9762500 Rows_examined: 6250 SET timestamp1723127950; select *…

PS证件照换底色

ps工具&#xff1a;Adobe Photoshop 2021 文章目录 1. 扣取人物2. 更换底色 1. 扣取人物 2. 更换底色

SwiftUI 6.0(iOS 18)自定义容器值(Container Values)让容器布局渐入佳境(上)

概述 我们在之前多篇博文中已经介绍过 SwiftUI 6.0&#xff08;iOS 18&#xff09;新增的自定义容器布局机制。现在&#xff0c;如何利用它们对容器内容进行“探囊取物”和“聚沙成塔”&#xff0c;我们已然胸有成竹了。 然而&#xff0c;除了上述鬼工雷斧般的新技巧之外&…

10月15日 -- 11月15日 ,参与《人工智能导论》学习打卡赢B站大会员

一、活动参与地址 点击链接进行活动报名>>>https://momodel.cn/classroom/course/detail?id6173911eab37f12b14daf4a8&activeKeyinfo&srcbef3adb478 二、活动详情 进入链接点击报名&#xff0c;仅需每天参与吴超老师的《人工智能导论》打卡活动&#xff0…

NPCAP和WPCAP

NPCAP是专为Windows开发的一款网络抓包SDK,该SDK提供了被应用程序调用的库文件和系统驱动程序。通过Npcap,我们可以得到原始网络数据,即未经过TCP/IP协议栈的数据,也就是网卡收到的数据,同时呢,我们也可以通过NPCAP设置接收过滤器,这样收到的数据就是我们感兴趣的数据,…

[C++ 核心编程]笔记 4.1.4 类和对象 - 案例1

类和对象: 案例1: 设计立方体类(Cube) 求出立方体的面积和体积分别用全局函数和成员函数判断两个立方体是否相等。 设计方法: 创建立方体类设计属性设计行为 求立方体面积和体积分别用全局和成员函数 判断立方体是否相等 #include<iostream> using namespace std;clas…

正则表达式-“三剑客”(grep、sed、awk)

1.3正则表达式 正则表达式描述了一种字符串匹配的模式&#xff0c;可以用来检查一个串是否含有某种子串&#xff0c;将匹配的子串替换或者从某个串中取出符号某个条件的子串等&#xff0c;在linux中代表自定义的模式模版&#xff0c;linux工具可以用正则表达式过滤文本。Linux…

★ C++进阶篇 ★ AVL树实现

Ciallo&#xff5e;(∠・ω< )⌒☆ ~ 今天&#xff0c;我将继续和大家一起学习C进阶篇第五章----AVL树实现 ~ ❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️澄岚主页&#xff1a;椎名澄嵐-CSDN博客C专栏&#xff1a;★ C进阶篇 ★_椎名澄嵐的博客-CSDN博客 ❄️…

Java基础:面向对象编程3

1 Java可变长参数 1.1 概述 Java 的可变长参数&#xff08;Varargs&#xff09;是在 Java 1.5 中引入的功能&#xff0c;允许方法接受任意数量的相同类型的参数。可变参数的语法是在参数类型后面加上三个点&#xff08;...&#xff09;&#xff0c;例如 int... numbers。 1.…

IPV6学习汇总

一、ICMPV6 ICMPv6&#xff08;Internet Control Message Protocol version 6&#xff09;&#xff0c;即互联网控制信息协议版本六&#xff0c;是为了与IPv6配套使用而开发的互联网控制信息协议。以下是关于ICMPv6的详细介绍&#xff1a; 一、基本功能 ICMPv6向源节点报告关…

半小时速通RHCSA

1-7章: #01创建以上目录和文件结构&#xff0c;并将/yasuo目录拷贝4份到/目录下 #02查看系统合法shell #03查看系统发行版版本 #04查看系统内核版本 #05临时修改主机名 #06查看系统指令的查找路径 #07查看passwd指令的执行路径 #08为/yasuo/ssh_config文件在/mulu目录下创建软链…

Vulnhub:DarkHole_2

一.信息收集/站点收集 &#xff08;1&#xff09;根据物理地址用nmap的主机发现功能得出IP地址 nmap -P 192.168.138.0/24 //同网段下主机发现得到IP为192.168.138.185&#xff08;2&#xff09;做nmap的目录扫描和端口扫描来发现其他站带以及信息 nmap -p- 192.168.138.185 …

什么是DApp?DApp开发指南

一、什么是DApp&#xff1f; DApp&#xff08;Decentralized Application&#xff09;&#xff0c;即去中心化应用&#xff0c;是一种基于区块链技术开发的应用程序&#xff0c;与传统的中心化应用不同&#xff0c;DApp不依赖单一服务器或管理主体&#xff0c;而是利用去中心化…

Oracle数据库安装Windows版本

1.下载压缩包 首先下载oracle19c的数据库&#xff0c;可以在官网下载&#xff0c;也可以从我的百度网盘下载。文件比较大&#xff0c;从oracle官网&#xff08;外网&#xff09;下载速度比较慢&#xff0c;还需要注册oracle用户。 通过网盘分享的文件&#xff1a;oracle数据库…

路由器概述

一、路由器的工作原理 根据路由表转发数据 二、路由表与其形成 2.1路由表 &#xff08;1&#xff09;概念 路由&#xff1a;从源主机到目的主机的转发过程路由表&#xff1a;路由器中维护的路由条目的集合&#xff1b;路由器根据路由表做路径选择 &#xff08;2&#xff…

k8s、prometheus、grafana数据采集和展示的链路流程

k8s集群中&#xff0c;容器级别的数据采集是由cAdvisor程序实现 cAdvisor # Container Advisor 容器顾问 cAdvisor程序是kubelet组件的一部分。 每个节点&#xff0c;包括master节点&#xff0c;都有一个kubelet系统服务&#xff0c; kukelet负责管理pod和容…

多台服务器分布式定时调度的几种方案

背景&#xff1a;现在有多个后端服务器&#xff0c;并且在代码中定义了一个定时任务&#xff0c;希望这个定时任务在一个时间只在一个服务器上执行&#xff0c;涉及到分布式调度&#xff0c;调研了一下总结出几种方案&#xff1a; 1.mysql的内置GET_LOCK GET_LOCK方法的介绍 …