基础IO[二]

文件磁盘文件——没有被打开

内存文件——被进程中在内存中打开,文件只有被加载到内存中才可以被访问。

进程控制模块PCB内部有一个指针:styruct files_struct* files。这个指针指向一张表,file_struct

他是一个结构体,这个指针指向这个结构体变量。

基础IO[二]_第1张图片

重点需要关注的是fd_array。里面的那个变量是一个宏定义,根据系统地差别,大小也有差别,是一个指针数组,代表可以打开文件数目地多少。结构体最后一个变量file也是一个结构体,包含了文件地全部信息,大小路径,版本号等。       

基础IO[二]_第2张图片

他们的关系如下。所以所谓地文件描述符地本质是数组下标。每个文件都需要,一个打开的文件方法,内存区域供其读写。这些都在操作系统内核中进行。

fopen->open->fd,然后封装成FILE,用户得到FILE*。

文件描述符 

为了进行文件的管理组织,就必定需要文件描述符。

Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2.

0,1,2对应的物理设备一般是:键盘,显示器,显示器 所以输入输出还可以采用如下方式:

#include  
  2 #include  
  3 #include  
  4 #include  
  5 #include  
  6 #include  
  7 int main()  
  8 {  
  9   close(0);                                                                                                                                                     
 10   int fd =open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);            
 11   if(fd<0)                                                          
 12   {                                                                 
 13     perror("open fail\n");                                          
 14     exit(1);                                                        
 15   }                                                                 
 16   printf("%d\n",fd);                                                
 17   close(fd);                                                        
 18   return 0;                                                         
 19 } 

 这段代码默认的是打开一个log.txt的文件,然后输出一个fd的值,但是结果默认是3,0 .1 . 2.被占用,那么如果我们默认关闭文件描述符0呢?我们会 发现文件门描述符输出的都是0,关闭文件描述符2,回答先打印出的文件描述符是2。那么我么可以得到一个结论。

fd的分配规则:最小的,没有被占用的文件描述符分配给新打开的文件描述符。

如果我们close(1)我们执行程序的时候会发现不会有任何东西打印出来。因为012被占用,所以新打开的文件,文件描述符一定是1.

printf()默认往stdout里面打印,printf的文件描述符是1,但是1被关闭,这里1指向了log.txt。如果我们在代码结尾不关闭close(fd),我们打开文件log.txt会发现文件log.txt里面出现了内容。

或者可以加入fllush代码如下

#include  
  2 #include  
  3 #include  
  4 #include  
  5 #include  
  6 #include  
  7 int main()  
  8 {  
  9   close(1);  
 10   int fd =open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);  
 11   if(fd<0)  
 12   {  
 13     perror("open fail\n");  
 14     return 1;  
 15   }  
 16   printf("文件按描述符%d\n",fd);  
 17   fprintf(stdout,"hello world\n");                                                                                                                              
 18   fflush(stdout);                                                                                                                                      
 19   close(fd);                                                                                                                                           
 20   return 0;                                                                                                                                            
 21 }   

我们可以看到log.txt出现了内容,本来应该打印到屏幕上的内容打印到了文件里面,产生了输出重定向的现象。那么他的原理是什么呢? 

linux上一切皆文件,linux上文件进程默认会打开三个文件,标准输入,标准输出,标准错误,对应的键盘,显示器,显示器。所以每个进程打开默认填充这三个标准文件。而关闭1导致,标准输出的文件描述符,之间指向了新开的log.txt这就是文件重定向的原理。

 #include
  2 #include
  3 #include
  4 #include
  5 #include
  6 #include
  7 int main()
  8 {
  9   close(0);                                                                                                                                                     
 10   int fd =open("log.txt",O_RDONLY);
 11   if(fd<0)
 12   {
 13     perror("open fail");
 14   }
 15   printf("fd:%d\n",fd);
 16 
 17   char buffer[64];
 18   fgets(buffer,sizeof buffer,stdin);
 19   printf("%s\n",buffer);
 20
 35 }

我们关闭文件描述符0,然后标准输出。然后输出结果我们看一下是什么?

我们看到运行程序的时候没有等待而是直接读取了log.txt。这是因为在关闭标准输入之后新打开的Log.txt默认占据了标准输入。文件由log.txt提供。

接下来我们看一下

追加重定向
 #include  
  2 #include  
  3 #include  
  4 #include  
  5 #include  
  6 #include  
  7 int main()  
  8 {  
  9   close(0);  
 10   int fd =open("log.txt",O_WRONLY|O_APPEND|O_CREAT);//输出重定向,读取方式打开,然后情况,没有的话创建文件:appe追加重定向                                       
 11   if(fd<0)                                                                                                                  
 12   {                                                                                                                         
 13     perror("open fail");                                                                                                    
 14   }                                                                                                                         
 15   fprintf(stdout,"you can see success");                                                     
 16    
    }

这个就是实现了追加重定向,但是这样子有点不方便。每次都要关闭特定的文件描述符。有系统提供给我们的接口来让我们使用dup2

dup2

#include

int dup2(int oldfd, int newfd);`

 创建一个新的fd,把进程的文件描述符中fd指针进行拷贝,将oldfd拷贝给newfd。让old和新的一样。那么新的newfd就没有意义,那么就可以关闭newfd。

基础IO[二]_第3张图片

将3拷贝到1,关闭新打开的3,留下1,这是因为1和3一致,完成了输出重定向。使用也是dup2(3,1)。

 #include
  2 #include
  3 #include
  4 #include
  5 #include
  6 #include
  7 int main(int argc,char*argv[])
  8 {
  9     if(argc!=2)
 10       return 2;
 11     int fd=open("log.txt",O_WRONLY|O_TRUNC|O_CREAT);
 12     if(fd<0)
 13     {
 14       perror("open fail");
 15       return 1;
 16     }
 17     fprintf(stdout,"%s\n",argv[1]);
 18 }

我们先写一份没有重定向的代码运行结果

之后物品们对他进行重定向

基础IO[二]_第4张图片运行结果

我们发现输入重定向已经完成了到og.txt。如上,就是重定向的原理。

接下来讨论一下close()这时候添加了close之后我们发现可以输出到log.txt上,之前则不可以,这个是dep2的特性。这个和dup2有关。

这时候就要和一切皆文件有关了,要先谈谈的Linux的设计哲学,体现在操作系统的软件设计层方面。linux是C语言写的,如何用C语言实现面向对象甚至运行时多态?类内函数指针,指向不同的函数。计算机硬件底层一定是对应不同的操作方法,但是这些设备都是外设,所以每一个设备的核心函数都可以说read和write   根据冯诺伊曼结构IO,所有的设备都可以有自己的read和write,但是实现方法一定是个不一样的。既然每个都不一样,对于用户来说过于麻烦,操作系统就设计了一切皆文件。当打开一个磁盘的文件,内核中创建一个struc fail结构体,显示器也一样,然后让函数指针指向不同的实现函数方法。免去了用户操作,但是打开的文件太多了,那么就先描述再组织。那么再软件层就是一切皆文件。所以在linux操作系统之上就有了一切皆文件的概念。这种概念叫做VFS  虚拟文件系统。

缓冲区

什么是缓冲区——就是一段内存空间。

为什么要有缓冲区?

基础IO[二]_第5张图片

提高整机效率,快速成本低,提高用户响应速度 。

那么缓存区在哪里?

首先要提到缓冲区的刷新策略,立即刷新,和行刷新 \n 满一行就刷新,满刷新(只有内容满了才会刷新)。例外:用户强制刷新(fflush)或者进程退出。

刷新策略

所有的设备都倾向于全缓冲,全缓冲,大大减少了IO次数,极大的减少了时间浪费。

#include
    2 #include
    3 #include
    4 #include
    5 #include
    6 #include
    7 #include
    8 int main()
    9 {
   10     //C语言提供的
   11     printf("hello print\n");
   12     fprintf(stdout,"fprint\n");
   13     const char* str="hello fputs\n";
   14     fputs(str,stdout);
   15 
   16 
   17     const char*buff="hello write\n";                                                                                                                          
   18     write(1,buff,strlen(buff));
W> 19     pid_t id=fork();
   20 
   21 }

运行结果

基础IO[二]_第6张图片

发现有4条记录,但是清空log.txt重定向一下

基础IO[二]_第7张图片

发现多了几条,只有操作系统的接口,执行了一次,C语言提供的使用了2次。那么为什么fork会导致这种现象呢?缓冲区一定是C标准库提供的,不然两者结果应该一样。

函数已经被执行完了,不一定代表数据一定会被刷新。fork写时拷贝可能数据被拷贝2份。

fput并不是直接写入到操作系统,而是先写入到缓冲区之中。然后调用write接口写入操作系统内核,那些写入缓冲区有什么好处呢?这样子就只需要写入缓冲区其他的操作都不用管。因为是往显示器打的是行刷新策略。那么最后执行fork的时候一定是函数执行完了,数据已经被刷新了。按理说fork无意义。如果进行了重定向——要向磁盘文件打印——隐形的刷新策略变成了全缓冲,\n没有意义了。fork()执行的时候一定函数执行完了,数据没有刷新,在当前C标准库得到缓冲区中,这部分数据属于父进程数据吗?是的,是属于父进程的数据,fork之后父子各自退出。那么fork之后两个执行流,各自退出。此刻还有特殊的刷新策略,退出时书信。那么刷新到磁盘的时候本质上也是一种写入。可以得到结论,在退出的时候父子进程发生写时拷贝。

那么C语言的缓冲区在哪里呢?

我们在fork之前加入fflush,会发现这样的现象又没了,这是因为在fork之前已经刷新了。缓冲区没有数据了,那么为什么里面填了stdout呢?缓冲区在哪里呢?

C语言中打开文件fopen返回值是FILE*  。FILE是结构体,内部封装了fd,以及包含了大量的语言层缓冲区结构。

C语言开的FILE ,文件流。cout cin  类必定有fd文件描述符,缓冲区buffer,operator 运算符重载。拷贝到buffer定期刷新。内核也有内核的缓冲区。而数据一旦拷贝数据就已经属于内核。调用write也不是学到了外设里面,不是直接写到了外设。

自己设计一下用户层缓冲区

示例代码

#include
    2 #include
    3 #include
    4 #include
    5 #include
    6 #include
    7 #include
    8 #include
    9 #define NUM 1024
   10 
   11 //类似于C语言的FILE
   12   struct MYfile_
   13   {
   14     int fd;
   15   char buffer[1024];
   16   int end;//缓冲区结尾
   17   };
   18   typedef struct MYfile_ MYfile;
   19 
   20   MYfile*open_(const char*path,const char* mode)
   21   {
   22    assert(path);
   23   assert(mode);
   24   MYfile *fq=NULL;
   25   if(strcmp(mode,"r")==0)
   26   {
   27 
   28   }
   29   else if(strcmp(mode,"r+")==0)
   30   {
   31                                                                                                                                                               
   32   }
   33   else if(strcmp(mode,"w")==0)
   34   {
   35     int fd=open(path,O_WRONLY|O_TRUNC|O_CREAT,0666);
   36     if(fd>=0)
          {
   38     fq=(MYfile*)malloc(sizeof(MYfile));
W> 39     memset(fq,0,sizeof(fq));  
   40     fq->fd=fd;
   41     }
   42     else 
   43     {
   44 
   45     }
   46    // fq=(MsqYfile*)malloc(sizeof(MYfile));
   47   }
   48   else if(strcmp(mode,"w+"))
   49   {
   50       
   51   }
   52   else if(strcmp(mode,"a")==0)
   53   {
   54 
   55 }
   56   else if(strcmp(mode,"a+")==0)
   57 {
   58 
   59 }
   60   else 
   61 {
   62 
   63 }
   64 return fq;
   65 }
   66   void fputs_(const char*message,MYfile*fp)
    
   67   {
   68     assert(memset);
   69     strcpy(fp->buffer+fp->end,message);
   70     fp->end+=strlen(message);
   71     printf("%s\n",fp->buffer);
   72     //暂时没有刷新
   73     //那么刷新策略谁执行的?用户通过C语言中代码逻辑完成刷新动作。
   74     //那么效率提高体现在哪里?如果输出结果中没有\n的时候就暂时不刷新,等到有\n时再刷新。
   75     //暂时不IO 
   76     if(fp->fd==0)
   77     {
   78       //标准输入
   79     }
   80     else if(fp->fd==1)
   81     {
   82       if(fp->buffer[fp->end-1]=='\n')//\n\o
   83       {
   84         write(fp->fd,fp->buffer,fp->end);
   85         fp->end=0;
   86       }
   87       //标准输出
   88     }
   89     else{
   90       //其他文件

   91     }
   92   }
   93   
   94   void fflush_(MYfile*fp)
   95   {
   96       assert(fp);                                                                                                                                             
   97   //    printf("%s",fp->buffer);
   98       if(fp->end!=0)
   99       {//暂且认为刷新了————其实是写到了内核。
  100         write(fp->fd,fp->buffer,fp->end);
  101         //刷新到屏幕上需要syncfs
  102         syncfs(fp->fd);//写入到磁盘。
  103         fp->end=0;
  104       }
  105   }
  106  void fclose_(MYfile*fp)
  107   {
  108     assert(fp);
  109       fflush_(fp);
  110       close(fp->fd);
  111       free(fp);
  112   }
  113  int main()
  114  {
  115    MYfile*fq = open_("./log.txt","w");
  116    if(fq==NULL)
  117    {
  118      perror("openfail\n");
  119      return 1;
  120    }
  121    fputs_("11111hello world",fq);
  122   fork();
          fclose_(fq);
  124        return 0;
  125   }
  126  

运行结果如下

基础IO[二]_第8张图片

完美实现了之前的效果。  

你可能感兴趣的:(Linux,数据结构)