四、文件內核數據結構和原子操作
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 才後執行:
- 從 i 節點中讀取文件長度作為當前偏移量
- 往文件中寫入數據
- 修改 i 節點中文件操作
四、文件內核數據結構和原子操作