【Linux】开始了解重定向

送给大家一句话:

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

开始了解重定向

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

1 前言

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

2 重定向与缓冲区

2.1 文件描述符分配规则

接下来我们来了解重定向!

首先我们来看fd文件描述符的分配规则,我们写一段代码来看:

cpp 复制代码
    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吗???

c 复制代码
    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 ,来看看会发生什么现象:

c 复制代码
  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

c 复制代码
NAME
       dup, dup2, dup3 - duplicate a file descriptor

SYNOPSIS
       #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既可以完成重定向:

c 复制代码
    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 进程与重定向

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

c 复制代码
  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♪(・ω・)ノ谢谢阅读!!!

下一篇文章见!!!

相关推荐
路上阡陌几秒前
Java学习笔记(二十四)
java·笔记·学习
带多刺的玫瑰2 分钟前
Leecode刷题C语言之收集所有金币可获得的最大积分
算法·深度优先
爱敲代码的边芙3 分钟前
Linux:信号的保存[2]
linux·运维·服务器
葛小白15 分钟前
第五天 Labview数据记录(5.1 INI配置文件读写)
服务器·labview
LabVIEW开发8 分钟前
PID控制的优势与LabVIEW应用
算法·labview
阿俊仔(摸鱼版)14 分钟前
Python 常用运维模块之OS模块篇
运维·开发语言·python·云服务器
工程师焱记16 分钟前
Linux 常用命令——系统设置篇(保姆级说明)
linux·运维·服务器
涅槃寂雨32 分钟前
C语言小任务——寻找水仙花数
c语言·数据结构·算法
某风吾起37 分钟前
linux系统中的 scp的使用方法
linux·服务器·网络
『往事』&白驹过隙;39 分钟前
操作系统(Linux Kernel 0.11&Linux Kernel 0.12)解读整理——内核初始化(main & init)之缓冲区的管理
linux·c语言·数据结构·物联网·操作系统