zoukankan      html  css  js  c++  java
  • 四、文件IO——内核数据结构和原子操作

    4.1 缓存 buff 说明

      一般设置缓存 buff  的大小是由一定的规律的,就是根据磁盘块的大小来定。

      Linux下输入命令: df -k  查看磁盘

      

      可以用命令查看下 /dev/sda1 磁盘的磁盘说明

    1 sudo tune2fs -l /dev/sda1

      

      Block size 就是磁盘块的大小,这个磁盘块的大小为 4M ,那么就可以设置缓存 buff 大小为 4096,一次就可以将数据写入。

      设置的缓存大小最好与磁盘块的大小保持一致,有利于提升读写文件的效率。

    4.2 操作文件中内核数据结构简要介绍

    • 一个打开的文件再内核中使用三种数据结构表示
      • 文件描述符
        • 文件描述符标志
        • 文件表项指针
      • 文件表项
        • 文件状态标志
          • 读、写、追加、同步和非阻塞等状态标志  
        • 当前文件偏移量
        • i 节点表项指针
        • 引用计数器  
      • i 节点   
        • 文件类型和对该文件的操作函数指针
        • 当前文件长度
        • 文件所有者
        • 文件所在的设备、文件访问权限
        • 指向文件数据在磁盘上所在位置的指针等   

      

    4.3 原子操作

    4.3.1 介绍

      主要是open 函数中的文件追加和文件创建

    • 文件追加
      • 打开文件时,使用 O_APPEND 标志,进程对到文件偏移量调整和数据追加成为原子操作
      • 内核每次对文件写之前,都将进程的当前偏移量设置为该文件的尾端。这样不再需要 lseek 来调整偏移量  
    • 文件创建
      • 对 open 函数的 O_CREAT 和 O_EXCL 的同时使用,而该文件存在,open 将失败,否则创建该文件,并且使得文件是否存在的判定和创建过程成为原子操作。  

      例子:两个进程对同一文件进行追加,没有使用 append 的时候

      file_append.c

     1 #include <sys/types.h>
     2 #include <sys/stat.h>
     3 #include <fcntl.h>
     4 #include <unistd.h>
     5 #include <string.h>
     6 #include <errno.h>
     7 #include <stdlib.h>
     8 #include <stdio.h>
     9 #include <fcntl.h>
    10 
    11 int main(int argc, char *argv[])
    12 {
    13     if(argc < 3) {
    14         fprintf(stderr, "usage: %s content destfile
    ", argv[0]);
    15         exit(1);
    16     }
    17 
    18     int fd;
    19     int ret;
    20     size_t size;
    21 
    22     fd = open(argv[2], O_WRONLY);
    23     if(fd < 0){
    24         perror("open error");
    25         exit(1);
    26     }
    27 
    28     //定位到文件尾部
    29     ret = lseek(fd, 0L, SEEK_END);
    30     if(ret == -1) {
    31         perror("lseek error");
    32         close(fd);
    33         exit(1);
    34     }
    35     
    36     sleep(10); //睡眠 10s
    37 
    38     //往文件中追加内容
    39     size = strlen(argv[1]) * sizeof(char);
    40     if(write(fd, argv[1], size) != size) {
    41         perror("write error");
    42         close(fd);
    43         exit(1);
    44     }
    45 
    46     return 0;
    47 }

      编译:gcc -o bin/file_append src/file_append.c

      创建一个  append.txt 文件,然后开启两个终端运行此程序

      第一个终端:

      第二各终端:

      第二个终端在第一个终端之后运行,运行完之后,查看 append.txt 的内容:

      

      现象上说明,第二个终端的写入将第一个终端的写入给覆盖掉了。

      第一个进程运行的时候,文件表项中的当前偏移量来源于 i 节点的文件长度(即调用 lseek 的时候),第二个进程运行的时候也是用 lseek 来获取偏移量,但是 i 节点中的文件长度没有增加,所以文件表项中的 当前偏移量 依然未变,因此第二个进程追加的内容覆盖掉了第一个进程中的内容。

      要想不覆盖,则要使用原子操作。将 open 和 注释掉 lseek 的代码做修改:

    1     //fd = open(argv[2], O_WRONLY);
    2     fd = open(argv[2], O_WRONLY | O_APPEND);

      删除 appent.txt 中的内容,然后再次在两个终端中运行两个程序:

      

      加了 O_APPEND 后,write 函数做了几件事情,此时整个 write 成为一个原子操作,只有当第一个进程的 write 执行完后,第二个进程的 write 才后执行:

    1. 从 i 节点中读取文件长度作为当前偏移量
    2. 往文件中写入数据
    3. 修改 i 节点中文件操作
  • 相关阅读:
    gitLab、docker
    Spring源码分析
    Tomcat堆内存分析
    Kafka入门一
    Java NIO
    spring注解
    websocket即时通讯
    pycharm安装dlib库
    python+opencv人脸识别是否戴口罩
    2021年暑假周总结1
  • 原文地址:https://www.cnblogs.com/kele-dad/p/9033195.html
Copyright © 2011-2022 走看看