1. 程式人生 > >四、文件內核數據結構和原子操作

四、文件內核數據結構和原子操作

保持 color 引用計數器 睡眠 stdlib.h app 原子 長度 文件創建

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\n", 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 節點中文件操作

四、文件內核數據結構和原子操作