【Linux】开始了解重定向

在这里插入图片描述
送给大家一句话:

人真正的名字是:欲望。所以你得知道,消灭恐惧最有效的办法,就是消灭欲望。 – 史铁生 《我与地坛》

开始了解重定向

  • 1 前言
  • 2 重定向与缓冲区
    • 2.1 文件描述符分配规则
    • 2.2 重定向的现象
    • 2.3 重定向的理解
    • 2.4 缓冲区的理解
  • 3 进程与重定向
  • Thanks♪(・ω・)ノ谢谢阅读!!!
  • 下一篇文章见!!!

1 前言

上一篇文章我们复习了C文件IO相关操作,了解了linux下的文件系统调用(open write read ),认识了文件描述符fd值,今天我们来学习重定向和缓冲区,这个缓冲区之前遇到过很多次,比如进度条项目的刷新缓冲区操作。然后我们可以来尝试封装一下系统调用,模拟C语言的文件库。

2 重定向与缓冲区

2.1 文件描述符分配规则

接下来我们来了解重定向!
首先我们来看fd文件描述符的分配规则,我们写一段代码来看:

    1 #include<stdio.h>2 #include<sys/types.h>3 #include<sys/stat.h>4 #include<fcntl.h>5 #include<unistd.h>6 #include<string.h>7 #include<stdlib.h>8 9 const char* filename = "log.txt";10 11 12 int main()13 {14 	15   int fd = open("myfile", O_RDONLY);16   if(fd < 0){17   perror("open");18     return 1;19   }20   printf("fd: %d\n", fd);21   close(fd);22   return 0;    23   }

我们运行来看:
在这里插入图片描述
这和我们的预期是一样的,我们文件操作那篇文章讲解了fd 的 0 1 2 分别代表了标准输入,标准输出,标准错误。那么在创建的文件描述符很自然的就使用了3! 那么加入我们关闭012中的文件呢,那么新打开的文件描述符会是3吗???

    1 #include<stdio.h>2 #include<sys/types.h>3 #include<sys/stat.h>4 #include<fcntl.h>5 #include<unistd.h>6 #include<string.h>7 #include<stdlib.h>8 9 const char* filename = "log.txt";10 11 12 int main()13 {14 	close(0); 15   int fd = open("myfile", O_RDONLY);16   if(fd < 0){17   perror("open");18     return 1;19   }20   printf("fd: %d\n", fd);21   close(fd);22   return 0;    23 }

来看:
在这里插入图片描述
我们新创建的文件的文件描述符就成了 0 !
再来试试:

  • 关闭 2 close(2) -->新创建的文件的文件描述符就成了 2
  • 关闭 1 close(1) -->就什么也打印不出来(标准输出被关闭自然打印不出来)
  • 关闭 0 2 close(2)close(0) --> 新创建的文件的文件描述符就成了 0

这样我们大致可以总结出来一个结论:
文件描述符的分配规则:进程会查自己的文件描述符表,分配最小的并且没有被使用过的 fd

2.2 重定向的现象

刚才我们看到了文件描述符的分配规则,也发现关闭1 (标准输出)就我们打印出来,我们再来探究一下:如果我们关闭了 标准输出,并打开了一个文件,那么该文件就成为了1 ,来看看会发生什么现象:

  1 #include<stdio.h>2 #include<sys/types.h>3 #include<sys/stat.h>4 #include<fcntl.h>5 #include<unistd.h>6 #include<string.h>7 #include<stdlib.h>8 9 const char* filename = "log.txt";10 11 12 int main()13 {14 15   close(1);16 17   int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC , 0666);18   if(fd < 0){19   perror("open");20     return 1;21   }22   printf("fd: %d\n", fd);23   fprintf(stdout,"fprintf fd :%d\n",fd);24 25   fflush(stdout);    26   close(fd);27   return 0 ;

来看效果:
在这里插入图片描述
我们发现并没有在显示器打印出来,而是在新文件log.txt中打印出来了!!!

  1. 因为我们关闭了1号文件 (标准输出
  2. 然后又打开了一个文件,那么1号下标就成了该新文件的文件描述符。
  3. 又因为stdout是对系统的封装,里面封装了 1 号文件
  4. 那么stdout 的指向没有发生改变(还是1 号文件),所以自然就打印到了log.txt中去了!.

这种技术就叫做 重定向,也就是把本应该打印到显示器的内容打印到了一个其他文件中。
其本质就是在内核中改变文件描述符表特定下标的内容,和上层无关!
在这里插入图片描述

可是如果不加入fflush 呢???结果是log.txt文件里也什么都没有?!这就涉及缓冲区的内容了。
首先 一个文件都有一个方法表和内核文件缓冲区。同样在C语言中 (stdin stdout stderr都是struct FILE* 的指针,)文件结构体里面一定封装了fd描述符,而且也封装了语言级的缓冲区。以往的 printf fprintf都是先讲内容写到语言级的缓冲区里在写到文件内核缓冲区了,所以fflush作为一个系统调用,就是刷新文件内核缓冲区,使其输出到文件中!!!
而为什么不加入fflush 呢结果是log.txt文件里也什么都没有呢??? 就是因为内容写入到文件内核缓冲区里还没有刷新就被close关闭了,所以还没刷新就文件被关闭了,还怎么打印到文件中。而且我们不写fflush 不写close 就可以成功打印到文件中!!!

2.3 重定向的理解

完成重定向的操作肯定不是像我们上面做的那样简单粗暴(又要删除,又要创建新文件),我们有一个系统调用dup2

NAMEdup, dup2, dup3 - duplicate a file descriptorSYNOPSIS#include <unistd.h>int dup(int oldfd);int dup2(int oldfd, int newfd);#define _GNU_SOURCE             /* See feature_test_macros(7) */#include <fcntl.h>              /* Obtain O_* constant definitions */#include <unistd.h>int dup3(int oldfd, int newfd, int flags);

每次我们使用dup2 就可以实现重定向 ,来看其功能描述。
dup2() makes newfd be the copy of oldfd, closing newfd first if necessary
通过描述可以知道:

  1. 首先文件描述符的拷贝不是对数字的拷贝,而是下标所对应内容(文件结构体指针)的拷贝
  2. 然后是实现了将oldfd的内容拷贝到newfd(多个下标指向一个文件),dup2( fd , 1 )就是将fd指向的文件拷贝到1 (标准输出)里。

这样通过dup2既可以完成重定向:

    1 #include<stdio.h>2 #include<sys/types.h>3 #include<sys/stat.h>4 #include<fcntl.h>5 #include<unistd.h>6 #include<string.h>7 #include<stdlib.h>8 9 const char* filename = "log.txt";10 11 12 int main()13 {14 15   int fd = open(filename , O_CREAT | O_WRONLY | O_TRUNC);16 17   dup2(fd,1);18 19   printf("Hello world!\n");20   fprintf(stdout,"Hello world!\n");                                              21   close(fd);22   return 0;23 }                                          

来看效果:
在这里插入图片描述
这样也实现了重定向的功能!!!比简单粗暴的关闭stdout 再打开新文件好多了!!!
我们也可以将O_TRUNC 换成O_APPEND,这样每次都是追加内容,所以我们的命令也有了对应:

  • > 相当于 O_TRUNC 覆盖
  • >> 相当于 O_APPEND 追加

就这么简单!!!

2.4 缓冲区的理解

缓冲区分为:用户级缓冲区 和 内核缓冲区。缓冲区的作用是:解耦和提高使用者效率

类比生活中,缓冲区就是类似一个超市,我们不需要去工厂进行采购,这样十分麻烦,而直接去超市就解决了问题。也可以比作顺丰快递,我们想要寄东西,只需要交给快递站就可以,我们不需要考虑快递怎么到达目的地!
所以我们操作系统与语言层中,我们的printf 和 fprintf就不需要考虑我们如何将内容写入到文件中,这不是他们需要关心的事情!!!

那为什么会拷贝两次呢???为什么会有两个缓冲区, **因为系统调用是有成本的!**操作系统可能正在执行其他任务,所以为了注重用户体验,就需要缓冲区(也就提高printf fprintf 的效率,因为我们实际上还没有将内容打印到文件,只是打印到了缓冲区,可能调用10次pringtf ,但是只需要刷新一次,是不是刷新IO的效率就高了)

  1. 缓冲区可以理解为一段内存空间
  2. 缓冲区是为了给上层通过良好的IO体验(语言 --> 操作系统 --> 磁盘)
  3. 缓冲区的刷新策略是什么呢?
    • 立即刷新 语言层:fflush() , 系统调用:fysnc(int fd) 相当于无缓冲
    • 行刷新 :显示器(配合人的阅读习惯)
    • 全缓冲,缓冲区写满才刷新:普通文件
    • 特殊情况 :进程退出会自动刷新缓冲区

截图内核的刷新策略我们不关心,就针对用户层面来研究。

3 进程与重定向

我们再来与先前的进程控制结合一下,来看:

  1 #include<stdio.h>2 #include<sys/types.h>3 #include<sys/stat.h>4 #include<fcntl.h>5 #include<unistd.h>6 #include<string.h>7 #include<stdlib.h>8 9 const char* filename = "log.txt";10 11 12 int main()13 {14 15   int fd = open(filename , O_CREAT | O_WRONLY | O_TRUNC);16 17   18 19   printf("Hello printf!\n");20   fprintf(stdout,"Hello fprintf!\n");21 22   const char *msg = "hello write!\n";23   write(1,msg,strlen(msg));                                                                                                                                                   24                                                                                                                                            25   fork();      26   close(fd);27	  return 0;28 }

我们运行一下来看效果:
在这里插入图片描述

啊???这是什么现象???

  1. 显示器与文件的打印顺序不一样
  2. 打印次数不一样?!
  • 现象 1: 是因为显示器采用行刷新所以每次换行就会打印出来,普通文件采用全缓冲,最后才会打印出来,打印顺序类似入栈出栈。
  • 现象 2 : 按理说我们fork()之后,创建了子进程,子进程会继承父进程的代码与数据。那么为什么显示器只打印了一遍呢???因为显示器采用行刷新,fork的时候,语言缓冲区和内核缓冲区里没有内容,所以子进程不会打印出来。而向文件打印时,fork之前语言缓冲区有内容(printf fprintf ),内核缓冲区有内容(write)。fork后 ,子进程会拷贝一份数据也就语言层的缓冲区被打印了两次,内核缓冲区不会拷贝给子进程(不是用户级,所有用户共享)

缓冲区就在struct file 内部!每个文件都有自己的缓冲区

Thanks♪(・ω・)ノ谢谢阅读!!!

下一篇文章见!!!

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

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

相关文章

突破传统RAG限制!Adaptive-RAG实现高效复杂查询处理

参考文章&#xff1a;突破传统RAG限制&#xff01;Adaptive-RAG实现高效复杂查询处理 在人工智能领域&#xff0c;大型语言模型&#xff08;LLMs&#xff09;的发展日新月异&#xff0c;它们在多种任务中展现出了卓越的性能。然而&#xff0c;尽管LLMs在处理问题时表现出色&…

MongoDB数据库转换为表格文件的Python实现

目录 一、引言 二、转换工具与库的选择 三、转换过程详解 安装必要的库 连接MongoDB数据库 查询并处理数据 将数据写入CSV文件 四、进阶技巧与注意事项 五、总结 一、引言 在当今大数据时代&#xff0c;数据的存储、处理与共享显得尤为重要。MongoDB作为一个面向文档…

如何更换网络IP地址,简单几步轻松搞定

在数字化日益普及的今天&#xff0c;网络IP地址作为设备在网络中的标识&#xff0c;扮演着极其重要的角色。有时&#xff0c;出于安全考虑、网络布局调整或解决特定问题的需要&#xff0c;我们可能需要更换网络IP地址。虎观代理将详细介绍如何更换网络IP地址&#xff0c;帮助用…

Android 输入法框架

输入法属于输入系统的一部分&#xff0c;区别于输入系统只能向系统产生时间&#xff0c;输入法能向系统输入具体的内容&#xff0c;下面来认识输入法的大体框架&#xff0c;以下内容参考清华大学出版社出版的《Android图形显示系统》。 输入法框架包含3个组件&#xff0c;各组件…

k8s集群node节点状态为Not Ready

目录 一、Node节点Not Ready状态的可能原因 二、排查node节点状态为Not Ready的原因 一、Node节点Not Ready状态的可能原因 node节点状态为Not Ready可能的原因有&#xff1a; 1.网络插件出问题 有过安装经验的小伙伴应该很熟悉未安装网络插件的情况下node节点在集群中的状…

基于java+springboot+vue实现的西安旅游系统(文末源码+Lw)23-265

摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统西安旅游系统信息管理难度大&#xff0c;容错率低&#…

3-1 AUTOSAR RTE概述

返回总目录->返回总目录<- 一、概念 1.1 虚拟总线VFB 若从整车级别去看待整车上所有的功能模块,即软件组件的架构,它们之间的通信形式主要涉及以下两种: 在单个ECU内部的通信(Intra-ECU Communication);在多个ECU之间的通信(Inter-ECU Communication)。 …

java实现TCP交互

服务器端 import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.PriorityQueue; import java.util.Scanner;public class TCP_Serv…

【嵌入式学习】ARM day04.11

一、思维导图 二、练习 实现三个灯闪烁 汇编代码 .text .global _start _start: 使能GPIOE和F时钟LDR r0,0x50000A28LDR r1,[R0]ORR R1,R1,#(0X3<<4)STR R1,[R0]配置GPIOE和F的MODER寄存器LDR r0,0x50006000 GPIOELDR R1,0X50007000 G…

golangci-lint 报错

File is not gci-ed with --skip-generated -s standard,default (gci) golangci-lint 报错上面的错解决办法&#xff1a; 1. 文件换行需要换成"LF" 而不是"CRLF" ---->>> 我用的goland IDE&#xff0c;随便在这个文件删除一个空行&#xff…

Docker部署SpringBoot+Vue前后端分离项目

文章目录 1. 安装Docker1. 1 卸载旧版Docker1.2 配置yum仓库1.3 安装Docker1.4 添加自启动配置1.5 配置阿里云镜像加速1.6 测试 2. 安装Nginx2.1 拉取镜像2.2 安装Nginx2.3 测试 3. 安装MySQL3.1 拉取镜像3.2 安装MySQL3.3 连接MySQL 4. 部署SpringBoot项目4.1 Maven打包4.2 编…

qt 打印日志

在 Qt Creator 中&#xff0c;将 QDebug、QInfo、QWarning、QCritical 和 QFatal 打印的日志输出到指定文件&#xff0c;需要设置 Qt 的消息处理机制。这通常涉及到安装一个自定义的消息处理器&#xff0c;该处理器将日志消息重定向到文件。以下是一个基本的步骤指南&#xff1…

R-Tree原理及朴素实现代码

R树是用于空间访问方法的树数据结构&#xff0c;即用于索引多维信息&#xff0c;例如地理坐标、矩形或多边形。 NSDT工具推荐&#xff1a; Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 - REVIT导出3D模型插件 -…

使用ROCm的HIP API向量加法程序

一、向量加法程序 Radeon Open Compute (ROCm) 是一个开源平台&#xff0c;用于加速高性能计算 (HPC) 和机器学习应用程序。它支持包括GPUs在内的多种硬件&#xff0c;并提供HIP (Heterogeneous-compute Interface for Portability) 作为CUDA代码的便捷转换工具。为了提供一个…

蓝桥杯算法题:栈(Stack)

这道题考的是递推动态规划&#xff0c;可能不是很难&#xff0c;不过这是自己第一次靠自己想出状态转移方程&#xff0c;所以纪念一下&#xff1a; 要做这些题目&#xff0c;首先要把题目中会出现什么状态给找出来&#xff0c;然后想想他们的状态可以通过什么操作转移&#xf…

关闭笔记本自带的键盘

目录 一、问题 二、方法 【方法一】 【方法二】 一、问题 笔记本自带的键盘上的个别按键又坏了&#xff0c;可能是因为使用电脑时&#xff0c;最先坏的几个按键那里温度比较高&#xff0c;久而久之就烧坏了吧。距离上次更换新键盘才差不多一年&#xff0c;所以不打算再买新…

基于arcgis /envi PCA(主成分分析)实现过程

基于arcgis /envi PCA(主成分分析)实现过程 1 提取研究范围 2对研究范围进行重采样 &#xff08;根据数据情况进行选做&#xff0c;如数据较大建议进行该步骤操作&#xff09; 3 对研究范围内数据进行归一化处理 4 将空值替换为0 5 对同期不同要素数据进行波段合成 对波段…

python pivot_table功能详解与应用 -- 实现Excel的透视表功能

1. 背景描述 透视表是一种能对多维数据进行分析统计的工具&#xff0c;具有筛选处理、分类汇总&#xff0c;优化显示等强大的功能&#xff0c;是Excel中最好用的数据分析工具之一。 在自动化办公中&#xff0c;使用python的pivot_table()&#xff0c;搭配合适的聚合函数&#x…

【linux篇】ubuntu安装教程

有道是工欲善其事必先利其器&#xff0c;在学习linux前&#xff0c;先得搭建好环境才能事半功倍。 1.VMware虚拟机安装 打开浏览器&#xff0c;可直接在搜索栏中输入VMware。

【C#】 删除首/尾部字符

代码 static void Main(string[] args){string str "123abc";string strdelete "abc";string str1 str.Trim(1);string strc str1.Trim(c);string str11 str1.TrimStart(1);string strcc str1.TrimEnd(c);string strabc str.Trim(strdelete.ToCharA…