【linux】重定向+缓冲区

重定向+缓冲区

自我名言只有努力,才能追逐梦想,只有努力,才不会欺骗自己。

喜欢的点赞,收藏,关注一下把!

1.重定向

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

close(1),为什么没有打印新建文件fd呢?

printf("%d\n",fd); printf会把内容打印到stdout文件中。

但是close(1)关闭标准输出stdout--->显示器,int fd=open();新打开的文件fd是1。

stdout-->fd-->1,虽然我们手动关闭了stdout,但是系统并不知道,还以为fd为1的位置是stdout,但是这个位置现在已经被新打开的文件占用了,所以打印到了新打开的文件里。

c 复制代码
  1 #include<stdio.h>
  2 #include<string.h>
  3 #include<sys/types.h>
  4 #include<sys/stat.h>
  5 #include<fcntl.h>
  6 #include<unistd.h>
  7 #include<stdlib.h>
  8 
  9 int main()
 10 {
 11    // close(0);
 12    // close(2);
 13     close(1);
 14     umask(0);
 15     int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
 16     if(fd == -1)
 17     {
 18         perror("open");
 19         exit(1);
 20     }
 21     printf("fd:%d\n",fd);
 22     
 23     //这里必须刷新一下,不然log.txt里面没有内容,这里和缓冲区有关,下面讲                                                                                                                  
 24     fflush(stdout);      
 25     close(fd);           
 26                          
 27     return 0;            
 28 } 

本来应该打印到显示器文件中,但是却写到文件里了。------>重定向

1.1重定向本质

1.2重定向接口

这个主要介绍dup2函数。

c 复制代码
int dup2(int oldfd, int newfd);

那怎么使用dup2来实现刚才的效果,把打印到显示器内容,写入到"log.txt"

是上面那样写的,还是下面那样写的,我们分析分析。

画图分析

因此正确写法如下

c 复制代码
  4 #include<sys/stat.h>
  5 #include<fcntl.h>
  6 #include<unistd.h>
  7 #include<stdlib.h>
  8 
  9 int main()
 10 {
 11    // close(0);
 12    // close(2);
 13    // close(1);
 14     umask(0);
 15     int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
 16     if(fd == -1)
 17     {
 18         perror("open");
 19         exit(1);
 20     }
 21     //重定向                                                                                                                                                     
 22     dup2(fd,1);                                                                                                               
 23     printf("fd:%d\n",fd);                                                                                                     
 24                                                                                                                               
 25     //这里必须刷新一下,不然log.txt里面没有内容                                                                               
 26     fflush(stdout);                                                                                                           
 27     close(fd);                                                                                                                
 28                                                                                                                               
 29     return 0;                                                                                                                 
 30 }  

1.3重定向分类

1.3.1>输出重定向

上面内容就是输出重定向,把新打开文件的fd重定向到fd为1(默认为显示器)的位置。

1.3.2>>追加重定向

c 复制代码
    1 #include<stdio.h>
    2 #include<string.h>
    3 #include<sys/types.h>
    4 #include<sys/stat.h>
    5 #include<fcntl.h>
    6 #include<unistd.h>
    7 #include<stdlib.h>
    8 
    9 int main()
   10 {
   11    // close(0);
   12    // close(2);
   13    // close(1);
   14     umask(0);
   15    // int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
   16     int fd=open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
   17     if(fd == -1)
   18     {
   19         perror("open");
   20         exit(1);
   21     }
   22     //重定向
   23     dup2(fd,1);
   24     printf("你好\n");
   25     printf("吃了吗\n");                                                                                                                                           
   26                                                                                                                           
   27     //这里必须刷新一下,不然log.txt里面没有内容                                                                           
   28     fflush(stdout);                                                                                                       
   29     close(fd);                                                                                                            
   30                                                                                                                           
   31     return 0;                                                                                                             
   32 } 

1.3.3<输入重定向

c 复制代码
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
     
int main()    
{    
   // close(0);    
   // close(2);    
   // close(1);    
    umask(0);    
   // int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);    
   // int fd=open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0666);    
    int fd=open("log.txt",O_RDONLY);    
    if(fd == -1)    
    {      
        perror("open");      
        exit(1);      
    }      
    //输入重定向    
    dup2(fd,0);                                                                                                                                                      
    char outbuffer[64];        
    while(1)                   
    {                          
        printf("<");                                                     
        if(fgets(outbuffer,sizeof(outbuffer),stdin) == NULL) break;      
        printf("%s",outbuffer);                                                                                                                        
    }
    return 0;
 }   

2.理解 >, >>, <

在前面实现过一个自己的shell,现在我们给这个shell增加重定向功能来理解重定向。

c 复制代码
//增加一个分割指令和文件名的函数                                                                                                                               
commandstrtok(lineCommand);

分割的时候,我们需要知道重定向是什么类型,文件是什么名字,因此增加两个全局变量来记录。

c 复制代码
  //重定向类型    
  //第一个为初始重定向     
  #define DEFAULT_REDIR 0    
  #define INPUT_REDIR 1  
  #define OUTPUT_REDIR 2    
  #define ERROR_EDIRR 3    
      
  //重定向类型+文件名    
  int redirType=DEFAULT_REDIR;    
  char* redirFile=NULL;    

分割函数

c 复制代码
  //这里找文件名没有写成函数,而写个宏
 #define trimSpace(start) do{\
      while(isspace(*start)) ++start;\
  }while(0)
  
  void commandstrtok(char* cmd)
  {
      assert(cmd);
      char* start=cmd;
      char* end=cmd+strlen(cmd);

      while(start < end)
      {
          if(*start == '<')
          {
              *start=0;
              ++start;
              //这里可能 ls -a -l >      log.txt,
              trimSpace(start);
              redirType=INPUT_REDIR;
              redirFile=start;
              break;
          }
          else if(*start == '>')                                                                                                                                 
          {                                                                                                                                                      
              *start=0;                                                                                                                                          
              ++start;                                                                                                                                           
              if(*start == '>')                                                                                                                                  
              {                                                                                                                                                  
                  redirType=APPEND_REDIR;                                                                                                                        
                  ++start;                                                                                                                                       
              }                                                                                                                                                  
              else                                                                                                                                               
              {                                                                                                                                                  
                  redirType=OUTPUT_REDIR;                                                                                                                        
              }                                                                                                                                                  
              trimSpace(start);                                                                                                                                  
              redirFile=start;                                                                                                                                       
              break;                                                                                                                                             
          }                                                                                                                                                      
          else
          {                                                                                                                                                      
              ++start;
          }
      }

因为命令是子进程执行的,真正重定向的功能一定是由子进程来完成的,

但是如何重定向是父进程要告知给子进程的。

c 复制代码
//创建子进程
pid_t id = fork();
assert(id != -1);
if(id == 0)
{             
   switch(redirType)
   {
    	case INPUT_REDIR:
         {
             int fd=open("log.txt",O_RDONLY);
             if(fd <0)
              {
                  perror("open");
                  return 1;
               }
               //重定向文件已经打开了
                dup2(fd,0);
           }
           	 break;
           case OUTPUT_REDIR:
           case APPEND_REDIR:
           {
                umask(0);
                int flags=O_WRONLY|O_CREAT;
                if(redirType == OUTPUT_REDIR) flags|=O_CREAT;
                else flags|=O_APPEND;                                                                                                                     
                int fd=open("log.txt",flags,0666);
                if(fd < 0)
                {
                 	perror("open");
                    return 1;
                 }
                  dup2(fd,1);     
             }
               break;
             default:
             printf("bug?\n");
               break;
     }
      //程序替换
      execvp(myargv[0],myargv);
      exit(1);
}

shell完整代码

问:子进程重定向会影响父进程吗?

不会

问:指向程序替换的时候,会不会影响曾经进程打开的重定向文件呢?

不会

3.如何理解linux下一切皆文件

Linux下一切皆文件,那键盘,显示器,磁盘,网卡等硬件在linux下都是文件吗?

可以这样说的。

站在struct file上层看来,所有的设备和文件,统一都是struct file--------->Linux下一切皆文件。

这里可能有这样一个问题,如果同一个文件被多个指针指向,但是某个进程把这个文件关了,会不会影响其他进程对这个文件的操作呢?

其实并不会,一个被打开的文件有引用计数

表明有几个指针指向这个文件,这样做是因为一个文件可能被多个指针指向。如果某个进程关闭文件,影响到其他进程,就不能保证进程的独立性。close关闭文件,其实并不是真正关闭文件,只是把引用计数减1,当只有一个指针指向这个文件,close关闭文件才是真正关闭这个文件。

4.缓冲区

先看一种现象。

c 复制代码
  1 #include<stdio.h>  
  2 #include<string.h>                                                        
  3 #include<sys/types.h>  
  4 #include<sys/stat.h>  
  5 #include<fcntl.h>  
  6 #include<unistd.h>  
  7 #include<stdlib.h>  
  8   
  9 int main()  
 10 {  
 11     printf("hello printf\n");  
 12     fprintf(stdout,"%s\n","hello fprintf");  
 13   
 14   
 15     const char* output="hello write\n";  
 16     write(1,output,strlen(output));  
 17   
 18                                                                                                                                                                  
 19     return 0;  
 20 }     
c 复制代码
  1 #include<stdio.h>
  2 #include<string.h>
  3 #include<sys/types.h>
  4 #include<sys/stat.h>
  5 #include<fcntl.h>
  6 #include<unistd.h>
  7 #include<stdlib.h>
  8 
  9 int main()
 10 {
 11     printf("hello printf\n");
 12     fprintf(stdout,"%s\n","hello fprintf");
 13 
 14 
 15     const char* output="hello write\n";
 16     write(1,output,strlen(output));
 17 
 18     fork();                                                                                                                                                      
 19     return 0;                                                                                                                           
 20 }    

对比两张图片,第二段代码补充fork(创建子进程),重定向之后,C接口的函数,被打印了两次,系统调用的接口前面都只是打印了一次。

这是什么原因?现在只是知道肯定是fork函数有关。

其实这里也和缓冲区有关。

4.1理解缓冲区问题

缓冲区本质就是一段内存!!!

4.1.1为什么要有缓冲区

这里讲一个小故事,帮助理解。

在四川的张三要给远在北京的李四送一个键盘。

张三为了节省时间,选择2;

现实中快递行业的意义?

节省发送者的时间。

进程把数据打包给磁盘,是一个很慢的过程,如果让进程一直等着显然不太好。因此我们也需要一个快速的方式----->缓冲区


缓冲区的意义是什么呢?

节省进程进行数据IO的时间。

但是我们在用文件写的接口时,并没有专门拷贝数据啊?(以fwrite为例)

其实与其理解fwrite是写入到文件的函数,倒不如理解fwrie是拷贝函数,将数据从进程拷贝到"缓冲区"或者"外设"中。

4.1.2缓冲区刷新策略的问题

把东西给顺丰之后,那顺丰什么时候发货呢?

张三第二天在想寄一个鼠标给李四,当走到快递站点发现自己昨天的快递还没有发走,就问快递人员,快递人员说,我们的快递都是用大货车和飞机发送的,就这一个快递不可能马上就发走,等到满足发送件货才发。

缓冲区的刷新策略:

不同外设IO访问速度是不同的。
缓冲区结合具体的设备,定制自己的刷新策略。(3策略,2特殊)

3策略
a.立即刷新 ----> 无缓冲
b.行刷新 ----> 行缓存 ----> 显示器 (显示器是给人看的,如果一次给很多信息,人看着不舒服)
c.缓冲区满 ----> 全缓存 ----> 磁盘文件(显示器除外)

2特例
1:用户强制刷新 (fflush)
2:进程退出 (进程退出要刷新缓冲区)

4.1.3缓冲区在哪里,指的是什么缓冲区

C接口打印了两次,系统调用接口打印了一次,这种现象一定和缓冲区有关。

虽然现在不知道缓冲区在哪里,但是我们知道缓冲区一定不在内核中

因为C接口底层调用的是系统调用接口,如果缓冲区在内核中,write也应该打印两次。

那缓冲区到底在哪?

我们之前谈论的所有缓冲区,都指的是用户级语言层面给我们提供的缓冲区

还记得C程序默认给我们打开stdin,stdout,stderr都是------->FILE*------>指向FILE结构体,结构体包含-------> fd,其实还包含一个缓冲区

因此,缓冲区在FILE结构体中。

所以当我们自己要强制刷新:fflush(文件指针),关闭:fclose(文件指针)传的都是FILE*。因为缓存区在FILE结构体中。

4.1.4如何解释fork问题

代码结束之前,创建子进程。

1.我们没有进行>输出重定向,看到了三条信息。

stdout默认使用的是行刷新,在fork之前,两条C函数已经将数据打印输出到到显示器上,你的FILE内部,进程内部不存在对应的数据了。这时创建子进程,等到子进程,父进程退出的时,都要刷新缓冲区,但是这时缓冲区已经没有内容可以刷新了。因此C函数打印两条信息。

2.>输入重定向之后,C接口打印两次

当我们进行>,写入文件就不再是显示器,而是普通文件,采用的刷新策略是全缓存,之前2条C打印函数,虽然带了\n,但是不足与将stdout缓冲区写满,数据并没有被刷新。

fork的时候,stdout属于父进程,创建子进程紧跟着就是进程退出,谁先退出,一定要进行缓冲区刷新(缓存区刷新---->就是修改)

这时就有了写时拷贝!! 因此C接口,数据最终会显示两份。

3.write为什么前后只打印一次

上面过程都是write无关,write没有FILE,而用的是fd,就没有C提供的缓冲区。

5.缓冲区该如何理解

自己写一个简易的缓冲区,来帮助理解"数据刷新策略"+"数据如何缓存"

5.1myStdio.h

c 复制代码
  1 #pragma once  
  2 #include<errno.h> 
  3 #include<stdio.h>      
  4 #include<string.h>      
  5 #include<sys/types.h>      
  6 #include<sys/stat.h>      
  7 #include<fcntl.h>      
  8 #include<unistd.h>      
  9 #include<stdlib.h> 
 10   
 11 #define SIZE 1024    
 12 #define SYNC_NOW 1    
 13 #define SYNC_LINE 2      
 14 #define SYNC_FULL 3     
 15                      
 16 typedef struct _FILE{  
 17     int flags;//刷新方式  
 18     int fileno;  //文件描述符
 19     int capacity;//buffer容量  
 20     int size;//buffer当前使用量  
 21     char buffer[SIZE]; //缓冲区
 22 }_FILE;              
 23   
 24                      
 25 _FILE* _fopen(const char* path_name,const char* mode);  
 26 void _fwrite(const void* ptr,int num,_FILE* fp);                                                                                                                 
 27 void _fclose(_FILE* fp);  
 28 void _fflush(_FILE* fp);  

5.2myStdio.c

5.2.1_fopen

c 复制代码
 _FILE* _fopen(const char* path_name,const char* mode)
{
    assert(path_name);
    int flags=0;
    int defaultmode=0666;

    if(strcmp(mode,"r") == 0)
    {
        flags|=O_RDONLY;
    }
    else if(strcmp(mode,"w") == 0)
    {
        flags|=(O_WRONLY|O_CREAT|O_TRUNC);
    }
    else if(strcmp(mode,"a") == 0)
    {
        flags|=(O_WRONLY|O_CREAT|O_APPEND);
    }

    
    int fd=0;

    if(flags & O_RDONLY)
    {
        fd=open(path_name,flags);
    }
    else
    {
        fd=open(path_name,flags,defaultmode);                                                                                                                        
    }
    if(fd < 0)
    {
        const char*err=strerror(errno);
        write(2,err,strlen(err));
		return NULL;//这就是为什么创建文件失败,返回NULL
    }

    _FILE* fp=(_FILE*)malloc(sizeof(_FILE));
    assert(fp);

    fp->flags=SYNC_LINE;
    fp->fileno=fd;
    fp->capacity=SIZE;
    fp->size=0;
    memset(fp->buffer,0,SIZE);

    return fp;
}

5.2.2_fwrite

c 复制代码
void _fwrite(const void* ptr,int num,_FILE* fp)                                                                                       
{                                                                                   
                                                                                                                                                                     
    //写到缓冲区里                                                                                                                  
    memcpy(fp->buffer+fp->size,ptr,num); //这里没有考虑缓冲区溢出问题                                                                                           
    fp->size+=num;                                                                                                                  
                                                                                                                                    
    //判断是否要刷新                                                                                                                
    if(fp->flags & SYNC_NOW)                                                                                                        
    {                                                                                                                               
        write(fp->fileno,fp->buffer,fp->size);                                                                                       
        fp->size=0;//清空缓冲区                                                                                                     
    }                                                                                                                               
    else if(fp->flags & SYNC_LINE)                                                                                                  
    {                                                                                                                               
        if(fp->buffer[fp->size-1] == '\n')  //这里也没有考虑abc\ndef这种形式,如果是这样的可以用for循环                                                                                        
        {                                                                                                                            
            write(fp->fileno,fp->buffer,fp->size);                                                                                       
            fp->size=0;                                                                                                             
        }                                                                                                                           
    }                                                                                                                               
    else if(fp->flags & SYNC_FULL)                                                                                                  
    {                                                                                                                               
        if(fp->size == fp->capacity)                                                                                                
        {                                                                                                                            
            write(fp->fileno,fp->buffer,fp->size);                                                                                       
            fp->size=0;                                                                                   
        }                                                                                                                           
    }                                                                                                                               
}     

5.2.3_fflush

c 复制代码
void _fflush(_FILE* fp)                                                                                       
{                                                                                       
    if(fp->size > 0) write(fp->fileno,fp->buffer,fp->size);                                                                                       
    fsync(fp->fileno); //把数据强制从内核缓冲区刷新到磁盘                                                                                     
    fp->size=0;                                                                                                                                                      
} 

这里引入了内核缓冲区,下面解释。

5.2.4_fclose

c 复制代码
void _fclose(_FILE* fp)                                                                                       
{                                                                                       
    _fflush(fp);  
    close(fp->fileno);                                                                                                                                               
}    

5.3main.c

行刷新

c 复制代码
  1 #include"myStdio.h"
  2 
  3 
  4 int main()
  5 {
  6     _FILE* fp=_fopen("log.txt","w");
  7     if(fp == NULL)
  8     {
  9         perror("_fopen");
 10         return 1;
 11     }
 12 
 13     int cnt=10;
 14     const char* msg="hello linux\n";
 15     while(1)
 16     {
 17         _fwrite(msg,strlen(msg),fp);
 18         sleep(1);
 19         printf("count:%d\n",cnt--);
 20         if(cnt == 0) break;
 21     }                                                                       
 22     _fclose(fp);
 23 
 24     return 0;
 25 }

退出刷新

c 复制代码
    1 #include"myStdio.h"
    2 
    3 
    4 int main()
    5 {
    6     _FILE* fp=_fopen("log.txt","w");
    7     if(fp == NULL)
    8     {
    9         perror("_fopen");
   10         return 1;
   11     }
   12 
   13     int cnt=10;
   14     const char* msg="hello linux";
   15     while(1)
   16     {
   17         _fwrite(msg,strlen(msg),fp);
   18         sleep(1);
   19         printf("count:%d\n",cnt--);                                       
   20         if(cnt == 0) break;
   21     }                    
   22     _fclose(fp);         
   23                          
   24     return 0;            
   25 }  


立即刷新

c 复制代码
  1 #include"myStdio.h"
  2 
  3 
  4 int main()
  5 {
  6     _FILE* fp=_fopen("log.txt","w");
  7     if(fp == NULL)
  8     {
  9         perror("_fopen");
 10         return 1;
 11     }
 12 
 13     int cnt=10;
 14     const char* msg="hello linux";
 15     while(1)
 16     {
 17         _fwrite(msg,strlen(msg),fp);
 18         _fflush(fp);                                                        
 19         sleep(1);
 20         printf("count:%d\n",cnt--);
 21         if(cnt == 0) break;
 22     }          
 23     _fclose(fp);
 24                
 25     return 0;  
 26 }   

6.缓冲区和OS的关系

这里是由write函数,直接把数据写到磁盘上的文件中吗?

其实并不是这样的。

write并不是直接把缓冲区里的内容刷新到文件中,在打开文件对应的struct file{}结构体中其他有一个方法指针,还有一个指向内核缓冲区的指针,系统会把FILE结构体里面缓冲区内容通过struct file{}找到内核缓冲区,再由write()拷贝 到内核缓冲区,然后再由这个内核缓冲区把内容刷新到文件中区,至于如何刷新和用户毫无关系,我们所知道的行刷新等,这是由语言层面所分类的,而内核缓冲区刷新由OS自主决定。

那么我们该如何证明有这个内核缓冲区呢?

超级大佬可以证明,这里证明不了,但是我们可以看到接口。

还有一个问题,如果OS突然宕机了会发生什么情况?

数据肯定会丢失。如果还是按照OS规定的内核缓冲区刷新策略肯定是不行的。

我们希望可以在用户层就告知OS,内核缓冲区不要再给我缓存了,赶紧把数据刷新到磁盘中。

这里介绍fsync函数

强制性把该文件对应的内核缓冲区数据持久到磁盘。就是OS不要给我缓存了,赶紧把数据给我更新到磁盘上。

相关推荐
用户97183563346612 小时前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪13 小时前
linux 拷贝文件或目录到指定的位置
linux
大树881 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠1 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质1 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush41 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5201 天前
Linux 11 动态监控指令top
linux
小小工匠1 天前
Redis - 事务机制:能实现 ACID 属性吗
数据结构·redis·性能优化·并发·持久化
Inhand陈工1 天前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智1 天前
ARP代理--工作原理
运维·网络·arp·arp代理