【Linux】六、Linux 基础IO(二)|重定向|如何理解 Linux一切皆文件|缓冲区

news2024/11/24 13:10:15

目录

五、重定向

5.1 什么是重定向

5.2 系统调用 dup2

5.3 三种重定向测试

5.3.1 输出重定向(>)

5.3.2 追加重定向(>>) 

5.3.3 输入重定向(<)  

5.4 父子进程拷贝问题

六、如何理解 Linux一切皆文件

七、缓冲区

7.1 认识缓冲区

7.2 缓冲区的刷新策略

7.3 缓冲区的位置

7.4 解释上面的例子

7.5 实现一个简易的 C语言缓冲区

7.6 内核级缓冲区(用户级缓冲区与OS的关系)


五、重定向

5.1 什么是重定向

        在上面的文件描述符中,已经测试关掉了 0、2(标准输入流、标准错误流),那如果关掉 1(标准输出流)呢?会发生什么?

        注:标准输入流stdin 对应操作位置:键盘,标准输出流stdout 对应操作位置:屏幕,标准错误流stderr 对应操作位置:屏幕

测试代码

#include<stdio.h>    
#include<unistd.h>    
#include<sys/types.h>    
#include<sys/stat.h>    
#include<fcntl.h>    
    
int main()    
{    
    close(1);    
    
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);                                                                                                          
    if(fd < 0)    
    {    
        perror("open");    
        return 1;    
    }    
    
    printf("open fd: %d\n", fd);    
    
    close(fd);    
    
    return 0;    
}    

运行结果

屏幕上什么也没有打印,这是为什么?

我们先看一下文件里面的内容

文件里面的内容也没有,难道打印的内容没有刷新?我们试着刷新缓冲区,进行修改代码

注:缓存区下面讲 

#include<stdio.h>    
#include<unistd.h>                                                            
#include<sys/types.h>    
#include<sys/stat.h>    
#include<fcntl.h>    
    
int main()                    
{    
    close(1);//关闭标准输出流    
                         
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);    
    if(fd < 0)                  
    {    
        perror("open");    
        return 1;                                                             
    }                                                                         
                                                                              
    printf("open fd: %d\n", fd);                                               
    fflush(stdout);//刷新缓冲区                                                                                                                                            
                                                                                                 
    close(fd);                                                                                   
                                                                                                 
    return 0;                                                                                    
}                                          

 运行结果,显示器依旧没有内容

 再查看一下文件有没有内容

        调用 open 打开 log.txt 之前关闭了标准输出,那么其对应的1号 fd 就闲置了出来,而 fd 的分配规则是从小到大依次寻找未被使用的最小值,所以 log.txt 对应的 fd 就为1 ,这没毛病

         但是,内容居然打印到了文件里面,而不是打印到显示器上

        像这样,printf 本来应该往显示器上打印数据,但是数据却写入到文件中去了,这我们把种特性叫做重定向

        重定向的本质是上层使用的文件描述符不变(即数组下标不变),数组里面的内容发生变化,即在内核中更改 文件描述符 指向的 文件对象,使另一个文件对象指向原有的文件对象,从而使原有的文件对象被另一个文件对象覆盖

解释上面的例子:

        在文件创建之前,我们关闭了标准输出流,即 close(1) ,那就说明指向标准输出的文件描述符不再指向标准输出。

        调用 open 打开 log.txt 之前关闭了标准输出,那么其对应的 1号文件描述符就闲置了出来,而文件描述符的分配规则是从小到大依次寻找未被使用的最小值,并且 0 、1、 2默认被占用,所以 log.txt 对应的 fd 就为1,即文件描述符就为 1

        log.txt的文件描述符就为 1,即 fd_array[1] 指向的是新的文件对象,不再 fd_array[1] 不再指向标准输出

        注:printf 是往 fd_array[1] 里面的文件对象打数据,即stdout,stdout 则是往显示器上打印数据 

        例子中到了 printf 打印的时候,printf 往 fd_array[1] 打印数据,fd_array[1] 指向的是 log.txt 这个文件对象,所以 printf 打印的内容打印到了文件里面

如下图:

         所以,重定向的本质是上层使用的文件描述符不变(即数组下标不变),在内核中更改文件描述符对应的 struct file* 的地址,这也是重定向的原理

        重定向分 输出重定向(>),追加重定向(>>),输入重定向(<)

        刚才上面的例子就是输出重定向,把原本输出到屏幕的内容输出到了文件里面,但是上面的例子我们所写的重定向太 low 了,并且这种方式非常麻烦,系统里面有用于重定向的系统调用 dup2,方便我们进行重定向

5.2 系统调用 dup2

man 2 dup2 查看一下详细信息

定义

int dup2(int oldfd, int newfd);

 函数功能: dup2会将 fd_array[oldfd] 的内容拷贝到 fd_array[newfd] 当中,即把 fd_array[newfd] 的内容覆盖

头文件:<unistd.h>

oldfd:旧的文件描述符
newfd:新的文件描述符

函数返回值,成功返回 newfd,失败返回-1

5.3 三种重定向测试

5.3.1 输出重定向(>)

测试代码

#include<stdio.h>      
#include<unistd.h>      
#include<sys/types.h>      
#include<sys/stat.h>      
#include<fcntl.h>      
      
int main()      
{      
      
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);      
    if(fd < 0)      
    {      
        perror("open");      
        return 1;      
    }      
      
    dup2(fd, 1);//把 fd_array[fd] 的内容拷贝到 fd_array[1]                                                                                                                 
                                                                    
    printf("open fd: %d\n", fd);                                                                                                    
    fflush(stdout);//刷新缓冲区                                                                                                     
                                                                                                                                    
    close(fd);                                                                                                                      
                                                                                                                                    
    return 0;                                                                                                                       
}                                          

运行结果

也可以使用命令进行重定向

5.3.2 追加重定向(>>) 

        追加重定向只需要在打开文件时去掉 O_TRUNC 选项,加上 O_APPEND 选项即可

测试代码

  #include<stdio.h>                                                                                                         
  #include<unistd.h>                                                                                                        
  #include<sys/types.h>                                                                                                     
  #include<sys/stat.h>                                                                                                      
  #include<fcntl.h>    
  #include<string.h>                                                                                                                                                       
                                           
  int main()                               
  {                                        
                                                                        
      int fd = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);    
      if(fd < 0)                           
      {                                    
          perror("open");                  
          return 1;                        
      }                                    
                                                                
      dup2(fd, 1);//把 fd_array[fd] 的内容拷贝到 fd_array[1]    
                                                                                                                            
      printf("open fd: %d\n", fd);                                                                                          
                                           
      const char* msg = "hello world";     
      write(1, msg, strlen(msg));                                                                            
                                                                                                                   
      fflush(stdout);//刷新缓冲区                                                                                  
      close(fd);                                                                                                   
                                                                                                                   
      return 0;                                                                                                    
  }                                               

运行结果

5.3.3 输入重定向(<)  

        输入重定向就是通过 dup2(fd, 0) 系统调用将目标文件 fd 中的内容拷贝到 0 号 fd 中,从而将本该从标准输入 (键盘) 读入的数据转换为从目标文件中读入

测试代码 

  #include<stdio.h>    
  #include<unistd.h>    
  #include<sys/types.h>    
  #include<sys/stat.h>    
  #include<fcntl.h>    
  #include<string.h>                                                                                                                                                       
                                           
  int main()                               
  {                                        
                                             
      int fd = open("log.txt", O_RDONLY);    
      if(fd < 0)                           
      {                                    
          perror("open");                  
          return 1;                        
      }                                    
                                                                
      dup2(fd, 0);//把 fd_array[fd] 的内容拷贝到 fd_array[0]    
                                           
      char buffer[64];                                         
      while(fgets(buffer, sizeof(buffer)-1, stdin) != NULL)    
      {                                     
          buffer[strlen(buffer)] = '\0';    
          printf("%s", buffer);                                                                                                                    
      }                                                                                                                                            
                                                                                                                                                   
      fflush(stdout);//刷新缓冲区                                                                                                                  
      close(fd);                                                                                                                                   
                                                                                                                                                   
      return 0;                                                                                                                                    
  }                                                 

运行结果

5.4 父子进程拷贝问题

        在进程概念篇章,我们已经知道 子进程是以父进程为模板创建出来的,PCB 肯定会被拷贝一份(即task_struct 会被拷贝),但是 task_struct 里面 files* 指针指向的 files_struct 的表,这张 files_struct 表需不需要拷贝一份?

        答案是肯定要的,因为进程具有独立性。假设子进程进行重定向,重定向会改变这张表里面的内容,子进程修改不能影响父进程,所以这张 files_struct 表也会被子进程拷贝一份,因为进程需要具有独立性

        那 struct file 需要被子进程拷贝吗?

        答案是不需要。struct file 属于文件部分,属于文件系统,与你的进程有什么关系呢,所以 struct file 不需要被子进程拷贝

        那 struct file 不被子进程拷贝的话,那父进程和子进程同时指向一个文件,比如 myfile,如果子进程把这个文件关掉了,那父进程还能访问到这个文件吗?

        答案是可以。因为每个文件里面都有一个计数器 f_count,这个叫做引用计数。这个计数器记录了有多少个指针指向这个文件,比如 子进程把这个文件关掉,文件并不是真正被关掉了,而是 f_count-1,当这个 f_count 为 0 时,这个文件才会真正被OS关闭,所以父进程依旧可以访问这个文件,这个工作也是OS完成的

        进程在进行程序替换的时候,会不会影响曾经进程打开的重定向文件? 

        答案是不会。进程替换的是物理空间上的代码和数据,task_struct 属于内核数据结构,进行程序替换的时候不影响内核数据结构!

六、如何理解 Linux一切皆文件

        之前这个概念是直接给出的,没有进行理解,现在如何理解 Linux下一切皆文件?

        在 计算机的软硬件体系结构中我们学到,操作系统是一款管理软件,它通过向下管理好各种软硬件资源 (手段),来向上提供良好 (安全、稳定、高效) 的运行环境 (目的);也就是说,键盘、显示器、磁盘、网卡等硬件也是由操作系统来管理的。

        而操作系统管理软硬件的方法是 先描述、再组织,即先将这些设备的各种属性抽象出来组成一个结构体,然后为每一个设备都创建一个结构体对象,再用某种数据结构将这些对象组织起来

        同时,每种硬件的访问方法都是不一样的,比如,向磁盘中读写数据与向网卡中读写数据是有明显差异的,所以操作系统需要为每一种硬件都单独提供对应的 Read、Write 方法,这些方法位于驱动层

        struct file 结构体中有一个函数指针变量,用于指向具体的 Write 和 Read 方法函数(即指向硬件的读写方法),这样每一个硬件都可以通过自己 struct file 对象中的 writep 和 readp 函数指针变量来找到位于驱动层的 Write 和 Read 方法

        注:有些硬件天然不支持读写,它们的读写方法直接设置为空,不影响硬件依旧有读写方法

        站在操作系统(OS)的层面来看,操作系统所看到的所有的软硬件设备和文件统一都是 struct file 对象,底层硬件的差异可以直接摒弃,让操作系统以统一的视角来看待这些文件,即 Linux 下一切皆文件

        这些一个个 struct file 就构成了多态,同时,struct file 是操作系统当中虚拟出来的一层文件对象,在 Linux 中,我们一般将这一层称为 虚拟文件系统 vfs,通过它,我们就可以摒弃掉底层设备的差别,统一使用文件接口的方式来进行文件操作 

七、缓冲区

7.1 认识缓冲区

测试代码

#include<stdio.h>    
#include<string.h>    
#include<unistd.h>                                                                                                                                                         
    
int main()    
{    
    //C语言接口    
    printf("hello printf\n");    
    fprintf(stdout, "hello fprintf\n");    
    const char* fputsString = "hello fputs\n";    
    fputs(fputsString, stdout);    
    
    //系统接口    
    const char* wString = "hello write\n";    
    write(1, wString, strlen(wString));    
    
    return 0;    
}    

运行结果,现象1

输出重定向到 log.txt,现象2

在原有的代码末尾使用 fork 创建子进程,子进程什么也不做

#include<stdio.h>    
#include<string.h>    
#include<unistd.h>    
    
int main()    
{    
    //C语言接口    
    printf("hello printf\n");    
    fprintf(stdout, "hello fprintf\n");    
    const char* fputsString = "hello fputs\n";    
    fputs(fputsString, stdout);    
    
    //系统接口    
    const char* wString = "hello write\n";    
    write(1, wString, strlen(wString));    
    
    //创建子进程,子进程什么也不做                                                                                                                                         
    fork();                                                                                                                                
                                                                                                                                           
    return 0;                                                                                                                              
}                            

 运行结果,现象3

输出重定向到 log.txt 文件中,现象4

        四个现象对比,很明显第四个现象有问题,观察发现,C语言的接口打印都打印了两次,系统接口 write 只打印了一次,这是为什么? 

这与缓冲区有关

        缓冲区本质上就是段内存,它是内存的一部分 

        这个缓冲区谁申请的?属于谁的?为什么要有缓冲区?这些问题下面一一解答

 为什么要有缓冲区?

先举一个小栗子:

        假设你在四川,你的好朋友张三在北京,你想要送一个物品给你的好朋友张三,然后你屁颠屁颠的骑自行车去到北京,来回一趟花费了大量的时间,假设用了一个月,这种方式成本高又浪费时间

        又过了一段时间,你又想送物品给张三,舍友提醒你下面有快递站点,可以直接寄送给张三,于是你早上十点就把物品拿给快递站点,让快递站点进行寄送,你直接告诉张三,快递到了记得领快递。这种方式成本低又可以省时间

        下午五点你又想起还有东西要给张三,你又去快递站点寄快递,你这是发现你早上寄的快递还没有寄出去,这时候你质问快递员:怎么我早上寄的快递怎么还没有寄出去?快递员耐心给你解释道:快递不会为了你一个人的一件快递就运输一趟,寄快递而是快递积累到一定数量时统一运输

将上述例子类比:

        四川相当于内存,你相当于一个进程,北京相当于磁盘,你的好朋友张三就相当于磁盘里的一个文件,你送给张三的物品就相当于一段数据,送给张三就相当于往磁盘里面的文件写数据

        你直接骑自行车把物品送给张三就相当于内存直接跟磁盘打交道,内存直接访问磁盘写数据,这种方式效率低并且浪费时间,因为大量的时间都用于访问磁盘上了

        缓冲区是存在于内存里面的,快递站点就相当于缓冲区,缓冲区也是达到一定数量才会刷新到磁盘,就如你寄快递一样:快递不会为了你一个人的一件快递就运输一趟,所以缓冲区也不会一有数据就立马刷新,而是会采取一定的刷新策略

        在例子中,快递的意义是节省发送者的时间,计算机中,缓冲区的意义是节省进程进行数据 IO 的时间,这就是为什么要有缓冲区

        与其理解 fwrite 是将数据写入到文件的函数,不如理解 fwrite 是进行数据拷贝的函数,因为 fwrite 函数只是将数据从进程拷贝到缓冲区中,并没有真正将数据写入到磁盘文件中,是缓冲区刷新才会把数据写入文件中

7.2 缓冲区的刷新策略

        上面已经说过,缓冲区也是达到一定数量才会刷新到磁盘,就如你寄快递一样:快递不会为了你一个人的一件快递就运输一趟,所以为了提高 IO 效率,缓冲区也不会一有数据就立马刷新,而是会采取一定的刷新策略

        假设内存直接访问磁盘,写入一次数据一共要花费 1s,那么其中 990ms 都在等待磁盘(外设)就绪,只有 10ms 左右的时间在进行数据写入

        所以,为了提高 IO 效率,缓冲区一定会结合具体的设备定制自己的刷新策略

缓存区刷新策略有三种:

  1. 立即刷新,无缓冲

  2. 行刷新,行缓冲

  3. 缓冲区满,全缓冲

立即刷新 (无缓冲):

  • 缓冲区中一出现数据就立马刷新,IO 效率低,很少使用

行刷新 (行缓冲):

  • 每拷贝一行数据就刷新一次,显示器采用的就是这种刷新策略,因为显示器是给人看了,而按行刷新符合人的阅读习惯,同时IO效率也不会太低

缓冲区满 (全缓冲):

  • 待数据把缓冲区填满后再刷新,这种刷新方式 IO 效率最高

两种特殊情况

  1. 用户强制刷新缓冲区
  2. 进程退出,进程退出都要进行缓冲区刷新

7.3 缓冲区的位置

从现象4 中可以知道:

  1. 这种现象一定和缓冲区有关
  2. 缓冲区一定不在内核中(如果在操作系统(内核)中,write也应该打印两次,因为这些 C语言接口的函数,底层调用的是系统接口,系统接口是操作系统提供的,所以缓冲区一定不在内核中)

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

        对于C语言来说,缓冲区位于 FILE* 中,FILE 是一个结构体,在这个结构体里面已经说过有 fd(文件描述符),而这个缓冲区也在这个 FILE 结构体里面

        所以我们要强制刷新缓冲区的时候,传入的一定是文件指针,关闭文件也是如此,即:fflush(文件指针),fclose(文件指针),这个文件指针就是 FILE* 类型的,即这个文件指针里面包含了 fd 和 缓冲区

在 Linux 下,我们可以在 /usr/include/libio.h 中找到 C语言缓冲区的相关信息, /usr/include/stdio.h 下有缓冲区的 typedef

回答上面的三个问题

  1. 缓冲区是谁申请的?用户级语言
  2. 缓冲区属于谁?属于FILE 结构体
  3. 为什么要有缓冲区?提高进程 IO 的效率

7.4 解释上面的例子

现象1 解释:

  • printf、fprintf、fputs 三种C语言接口函数都是向标准输出即显示器中打印数据,而显示器采用的是行缓冲区,同时,我们每条打印语句后面都带有换行符,所以 printf、fprintf、fputs 语句执行后立即刷新缓冲区
  • 而 write 是系统调用,没有 C语言的缓冲区,即 write 没有缓冲区,所以也是语句执行后立即刷新
  • 所以输出结果是四条语句顺序打印

现象2 解释: 

  • 我们通过输入重定向指令 > 将本该写入到显示器文件中的数据写入到了磁盘文件中,由于磁盘文件采用全缓冲刷新策略,所以 printf、fprintf、fputs 三条语句执行完毕后数据并不会刷新,因为缓冲区并没有被写满,而是等到进程退出这种特殊情况才会将三条语句刷新到磁盘文件中
  • 但此时,write 语句也已经执行完毕,而 write 系统调用没有缓冲区,执行立即写入;所以输出结果是 write 在最前面

现象3 解释:  

  • 显示器采用行缓冲,所以在 fork 之前 printf、fprintf、fputs 三条语句的数据均已刷新到显示器上了
  • 而对于进程数据来说,如果数据位于缓冲区内,那么该数据属于进程,此时 fork 子进程也会指向该数据
  • 但如果该数据已经写入到磁盘文件了,那么数据就不属于进程了,此时 fork 子进程也不在指向该数据了
  • 所以,这里 fork 子进程不会做任何事情,结果和现象1一样。

现象4 解释:

  • 使用重定向指令将本该写入显示器文件的数据写入到磁盘文件中,而磁盘文件采用全缓冲,所以 fork 子进程时 printf、fprintf、fputs 的数据还存在于缓冲区中 (缓冲区没满,同时父进程还没有退出,所以缓冲区没有刷新)
  • 也就是说,此时数据还属于父进程,那么 fork 之后子进程也会指向该数据
  • 而 fork 之后紧接着就是进程退出,父子进程某一方先退出时会刷新缓冲区,由于刷新缓冲区会清空缓冲区中的数据,为了保持进程独立性,先退出的一方会发生 写时拷贝,然后向磁盘文件中写入 printf、fprintf、fputs 三条数据;
  • 然后,后退出的一方也会进行缓冲区的刷新;
  • 所以,最终 printf、fprintf、fputs 的数据会写入两份 (父子进程各写入一份),且 write 由于属于系统调用没有缓冲区,所以只写入一份数据且最先写入

7.5 实现一个简易的 C语言缓冲区

(1)myStdio.h

#pragma once    
    
#include<stdio.h>    
#include<stdlib.h>    
#include<assert.h>    
#include<string.h>    
#include<unistd.h>    
#include<sys/types.h>    
#include<sys/stat.h>    
#include<fcntl.h>    
#include<errno.h>    
    
#define SIZE 1024 //缓存区的大小    
#define SYNC_NOW 1 //立即刷新,无缓冲    
#define SYNC_LINE 2 //行缓冲    
#define SYNC_FULL 4 //全缓冲    
    
typedef struct _FILE{    
    int flags; //刷新方式    
    int fileno; //文件描述符    
    int cap; //buffer的总容量    
    int size; //buffer的当前使用量    
    char buffer[SIZE];    
}FILE_;    
    
//声明    
FILE_ *fopen_(const char* path_name, const char* mode); //第一个参数是打开文件的名字,第二个参数是访问打开模式'w''r''a'    
void fwrite_(const void* ptr, int num, FILE_* fp); //    
void fclose_(FILE_* fp);    
void fflush_(FILE_* fp);                                     

(2)myStdio.c

#include "myStdio.h"                                                                                                                                                                                                                        

FILE_ *fopen_(const char* path_name, const char* mode)
{
    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);
    }
    else
    {
        //其他打开方式,暂时不考虑
        //TODO
    }

    //使用系统调用打开文件
    int fd = 0;
    if(flags & O_RDONLY) //文件已经存在
        fd = open(path_name, flags);
    else //文件不存在
        fd = open(path_name, flags, defaultMode);
    if(fd < 0)
    {
        const char* error = strerror(errno);
        write(2, error, strlen(error));
        return NULL; //C语言打开文件失败会返回 NULL 的原因
    }


    //缓冲区申请空间
    FILE_* fp = (FILE_*)malloc(sizeof(FILE_));
    assert(fp);

    fp->flags = SYNC_LINE; //默认设置成行刷新
    fp->fileno = fd;
    fp->cap = SIZE;
    fp->size = 0;
    memset(fp->buffer, 0, SIZE);

    return fp; //C语言打开一个文件会返回一个FILE* 指针的原因    
}

void fwrite_(const void* ptr, int num, FILE_* fp)
{
    //写入到缓冲区
    memcpy(fp->buffer + fp->size, ptr, num);
    fp->size += num;

    //判断是否刷新
    if(fp->fileno & SYNC_NOW)//无缓冲
    {
        write(fp->fileno, fp->buffer, fp->size);
        fp->size = 0; //清空缓冲区
    }
    else if(fp->flags & SYNC_FULL) //全缓冲
    {
        if(fp->cap == fp->size)
        {
            write(fp->fileno, fp->buffer, fp->size);
            fp->size = 0;
        }
    }
    else if(fp->flags & SYNC_FULL) //行缓冲
    {
        if(fp->buffer[fp->size-1] == '\n')//不考虑'\0'出现在字符串中间的情况
        {
            write(fp->fileno, fp->buffer, fp->size);
            fp->size = 0;
        }
    }                                                                                                                                                                                                                                       
    else
    {
        //TODO
    }
}

void fflush_(FILE_* fp)
{
    if(fp->size > 0)
        write(fp->fileno, fp->buffer, fp->size);
    fp->size = 0;
}

void fclose_(FILE_* fp)
{
    fflush_(fp);
    close(fp->fileno);
}                                                     

(3)main.c

#include "myStdio.h"    
    
int main()    
{    
    FILE_* fp = fopen_("./log.txt", "w");    
    if(fp == NULL)    
    {    
        return 0;    
    }    
    
    //const char* msg = "hello\n";    
    const char* msg = "hello ";                                                                                                                                            
    int cnt = 10;    
    while(cnt--)    
    {    
        fwrite_(msg, strlen(msg), fp);    
        printf("cnt: %d\n", cnt);    
        sleep(1);    
    }    
    
    fclose_(fp);    
    
    return 0;    
}    

(4)makefile

7.6 内核级缓冲区(用户级缓冲区与OS的关系)

        上面所谈的 C语言文件操作向磁盘文件写入数据的过程是:程序运行,进程通过 fwrite 等函数将数据拷贝到缓冲区中,然后再由缓冲区以某种刷新方式刷新 (写入) 到磁盘文件中;

        但实际上缓冲区并不是直接将数据写入到磁盘文件中的(用户级语言级缓冲区在用户部分),这个过程还要经过操作系统处理,然后数据才写入磁盘的文件中

        实际上缓冲区并不是直接将数据写入到磁盘文件中而是先将数据拷贝到内核缓冲区

        内核缓冲区位于操作系统(内核数据结构)的 struct file 的 file 结构体中

        数据最后再由操作系统自主决定以什么样的刷新策略将数据写入到外设(磁盘)中,而这个写入的过程和用户没有关系

也就是说,向磁盘中写入数据都要经过这个三个步骤 

  1. 数据先通过 fwrite 等文件操作接口将进程数据拷贝到语言级的缓冲区里面
  2. 然后再语言级缓冲区再根据自己的的刷新策略通过 write 系统调用将数据拷贝到 file 结构体中的内核缓冲区中,
  3. 最后再由操作系统自主将数据(内核级缓冲区也有自己刷新数据的策略)真正的写入到磁盘的文件中,到这一步数据才真正意义上写入磁盘的文件中

注:内核级缓冲区的刷新数据的策略与用户语言级缓冲区刷新数据的策略不一样,内核级缓冲区的刷新数据的策略并不是行缓冲、全缓冲、无缓冲这么简单,而是更复杂

特殊情况:数据被拷贝到内核缓冲区中,这时候操作系统突然宕机了必会出现数据丢失。为了保护数据安全,操作系统提供了一个系统调用函数 fsync,其作用就是将内核缓冲区中的数据立刻直接刷新到外设中,而不再采用操作系统内核缓冲区的刷新策略

man 2 fsync 查看一下

注:内核缓冲区是作为扩展知识,平时理解到用户级语言层面的缓冲区就已经完全足够了 

总的来说缓冲区有两种:

        用户级语言缓冲区和内核级缓冲区,用户级语言缓冲区就是语言级别的缓冲区,对于C语言来说,用户缓冲区就在  FILE 结构体中,其他的语言也类似

        内核级缓冲区属于操作系统层面,刷新策略是按照OS的实际情况进行刷新的,与用户层无关

----------------我是分割线---------------

文章到这里就结束了,下一篇即将更新

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

连续系统PID的Simulink仿真-2

仍以二阶线性传递函数为被控对象&#xff0c;进行模拟PID 控制。被控对象形式为,其中b为在[103,163]范围内随机变化&#xff0c;a为在[15,35]范围内随机变化&#xff0c;则被控对象的描述方式可转换为&#xff1a;S函数是Simulink一项重要的功能&#xff0c;采用S函数可实现在S…

Pandas 数据清洗

Pandas 数据清洗数据清洗是对一些没有用的数据进行处理的过程。很多数据集存在数据缺失、数据格式错误、错误数据或重复数据的情况&#xff0c;如果要对使数据分析更加准确&#xff0c;就需要对这些没有用的数据进行处理。在这个教程中&#xff0c;我们将利用 Pandas包来进行数…

微软ATP带你看| 爆火的ChatGPT是什么?

&#xff08;本文阅读时间&#xff1a;7分钟&#xff09;OpenAI最新聊天机器人ChatGPT火爆全网&#xff01;能写代码、编剧本&#xff0c;马斯克都盛赞它“好得吓人”&#xff01;ChatGPT是什么GPT(Generative Pre-trained Transformer)系列是由OpenAI提出的非常强大的预训练语…

MaxCompute SQL示例解析

MaxCompute SQL示例解析 介绍MaxCompute SQL常见使用场景&#xff0c;掌握SQL的写法。 准备数据集 本文以emp表和dept表为示例数据集。您可以自行在MaxCompute项目上创建表并上传数据。 emp.csv中数据如下 7369,SMITH,CLERK,7902,1980-12-17 00:00:00,800,20 7499,ALLEN,SALES…

aws codebuild 使用和配置codebuild测试报告

参考资料 使用 Amazon CodeBuild 中的测试报告在 CodeBuild 使用AWS CLI样本中创建测试报告aws-codebuild-samples 在codebuild构建过程中获取有关在构建期间运行的测试的详细信息。 codebuild测试报告 通过在buildspec.yaml中配置报告组&#xff0c;运行构建项目时系统将运…

【代码随想录】96.不同的二叉搜索树

96.不同的二叉搜索树 思路 n为1的时候有一棵树&#xff0c;n为2有两棵树&#xff0c;这个是很直观的。 n为3的时候&#xff0c;有哪几种情况。 当1为头结点的时候&#xff0c;其右子树有两个节点&#xff0c;看这两个节点的布局&#xff0c;是不是和 n 为2的时候两棵树的布…

控价公司可以帮我们做什么?什么时候需要找第三方控价公司?

如果&#xff0c;我们品牌的销售渠道遭遇了低价乱价、窜货、侵权、假冒等问题&#xff0c;扰乱了我们品牌的渠道秩序&#xff0c;或者是我们在品牌发展的过程中&#xff0c;想通过对行业和竞品的了解来明确发展方向和策略&#xff0c;而自己又分身乏术或无从下手&#xff0c;这…

车辆信息查询

要想查一辆汽车的信息&#xff0c;除了去各个汽车平台上查询&#xff0c;比如汽车之家、易车网、懂车帝等&#xff0c;还可以使用“汽车公告查询”。 通过常规网页百度搜索引擎&#xff0c;输入关键字“汽车公告查询”&#xff0c;就会获取到相关搜索结果&#xff0c;汽车公告查…

C进阶_内存库函数

目录 memcpy 模拟实现memcpy memmove 模拟实现memmove memcmp memcpy 它的函数原型为&#xff1a; void * memcpy ( void * destination, const void * source, size_t num ); 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。 这个函数…

104、【树与二叉树】leetcode ——98. 验证二叉搜索树:递归法[先序+中序+后序]+迭代法(C++版本)

题目描述 原题链接&#xff1a;98. 验证二叉搜索树 解题思路 BST的特点是&#xff1a;当前结点的值&#xff0c;比左子树中的全部结点都大&#xff0c;比右子树中全部结点都小。在代码实现中&#xff0c;要注意不要对比的是某一结点和某一侧的全部值之间的关系&#xff0c;不…

【论文阅读】CenterNet

论文题目&#xff1a;Objects as Points&#xff08;CVPR2019&#xff09; 论文地址&#xff1a;https://arxiv.org/pdf/1904.07850.pdf 发布时间&#xff1a;2019.4.16 机构&#xff1a;UT Austin&#xff0c;UC Berkeley 代码&#xff1a;https://github.com/xingyizhou/…

小程序事件基础

小程序事件--基础小程序事件事件简介小程序事件—事件传参事件对象属性target和currentTarget事件对象属性获取和设置data数据获取&设置data获取和设置data数据—进阶小程序的渲染层与逻辑层小程序事件 事件简介 事件是视图层到逻辑层的通讯方式。负责将用户对于的页面的操…

云上的米开朗基罗:在不确定时代,寻找建筑般的确定性

文艺复兴三杰之一的米开朗基罗&#xff0c;被称为“天才建筑师”。其实他一生留下的建筑并不多&#xff0c;仅仅有美第奇礼拜堂、卡比多广场、圣彼得大教堂穹顶等寥寥几座。但米开朗基罗却凭借对建筑层次与结构的精妙把握&#xff0c;影响了此后数百年的建筑风格。很多人认为&a…

【代码随想录】动态规划:关于01背包问题,你该了解这些!(滚动数组)

01 背包 有n件物品和一个最多能背重量为w的背包 第i件物品的重量是weight[i]&#xff0c; 得到的价值是value[i] &#xff0c; 每件物品只能用一次&#xff0c;求解将哪些物品装入背包里物品价值总和最大。 每一件物品其实只有两个状态&#xff0c;取或者不取&#xff0c;所以…

win下编译opencv+libjpeg-turbo

文章目录前言编译环境下载opencv和jpeg-turbo源码编译jpeg-turbo编译opencv失败&#xff1f;那就直接调用jpeg-turbo库进行编解码前言 opencv默认自带第三方jpeg编解码库&#xff0c;但其性能一般&#xff0c;对高性能需求的程序来说是不适合的&#xff0c;因此我们可以把jpeg…

设计模式学习(八):Proxy代理模式

一、什么是Proxy模式 Proxy是“代理人”的意思&#xff0c;它指的是代替别人进行工作的人。当不一定需要本人亲自进行工作时&#xff0c;就可以寻找代理人去完成工作。但代理人毕竟只是代理人&#xff0c;能代替本人做的事情终究是有限的。因此&#xff0c;当代理人遇到无法自己…

文件上传oss,并查询上传进度(SpringBoot+Redis+Oss+Swagger3)

文章目录诉求技术选型pom配置项目结构文件树图示结构代码实现配置相关配置文件yamlSwagger3配置跨域问题配置oss相关ServiceControllerApplicationSwagger接口操作获取上传文件标识号获取文件上传进度小结诉求 将文件上传到oss&#xff0c;并实时监听上传进度&#xff0c;并将进…

【javaSE】中基本类型和引用类型对象的比较及PriorityQueue中的比较方法

写博客是为了提升自己&#xff0c;也是为了展现自己的学习成果&#xff0c;坚持!坚持!坚持&#xff01;未来是什么样的&#xff0c;闯一闯就知道啦。喜欢就留个关注吧&#xff01;&#xff01;! 目录 一、java对象的比较 1.1java中基本类型的比较 1.2引用对象的比较 1.3引用…

使用云端的GPU进行yolov5的训练

前言本文介绍了使用云端GPU进行yolov5训练环境配置的过程一、创建实例这里使用的是恒源云的GPU服务器&#xff0c;官方网址为恒源云_GPUSHARE-恒源智享云他的用户文档为Tmux - 恒源云用户文档一般的问题在用户文档中都可以找到解决办法。注册并登录后的界面如下图所示。点击云市…

c++11 标准模板(STL)(std::forward_list)(十)

定义于头文件 <forward_list> template< class T, class Allocator std::allocator<T> > class forward_list;(1)(C11 起)namespace pmr { template <class T> using forward_list std::forward_list<T, std::pmr::polymorphic_…