歡迎光臨
每天分享高質量文章

檔案系統和裸塊設備的page cache問題

普通檔案的address space

        檔案系統讀取檔案一般會使用do_generic_file_read(),mapping指向普通檔案的address space。如果一個檔案的某一塊不在page cache中,在find_get_page函式中會創建一個page,並將這個page根據index插入到這個普通檔案的address space中。這也是我們熟知的過程。

  1. static ssize_t do_generic_file_read(struct file *filp, loff_t *ppos,
  2.        struct iov_iter *iter, ssize_t written)
  3. {
  4.    struct address_space *mapping = filp->f_mapping;
  5.    struct inode *inode = mapping->host;
  6.    struct file_ra_state *ra = &filp->f_ra;
  7.    pgoff_t index;
  8.    pgoff_t last_index;
  9.    pgoff_t prev_index;
  10.    unsigned long offset;      /* offset into pagecache page */
  11.    unsigned int prev_offset;
  12.    int error = 0;
  13.  
  14.    index = *ppos >> PAGE_CACHE_SHIFT;
  15.    prev_index = ra->prev_pos >> PAGE_CACHE_SHIFT;
  16.    prev_offset = ra->prev_pos & (PAGE_CACHE_SIZE1);
  17.    last_index = (*ppos + iter->count + PAGE_CACHE_SIZE1) >> PAGE_CACHE_SHIFT;
  18.    offset = *ppos & ~PAGE_CACHE_MASK;
  19.  
  20.    for (;;) {
  21.        struct page *page;
  22.        pgoff_t end_index;
  23.        loff_t isize;
  24.        unsigned long nr, ret;
  25.  
  26.        cond_resched();
  27. find_page:
  28.        page = find_get_page(mapping, index);
  29.        if (!page) {
  30.            page_cache_sync_readahead(mapping,
  31.                    ra, filp,
  32.                    index, last_index index);
  33.            page = find_get_page(mapping, index);
  34.            if (unlikely(page == NULL))
  35.                goto no_cached_page;
  36.        }
  37.       ……//此處省略約200行
  38. }

塊設備的address space

        但是在讀取檔案系統元資料的時候,元資料對應的page會被加入到底層裸塊設備的address space中。下麵代碼的bdev_mapping指向塊設備的address space,呼叫find_get_page_flags()後,一個新的page(如果page不在這個塊設備的address space)就被創建並且插入到這個塊設備的address space。

  1. static struct buffer_head *
  2. __find_get_block_slow(struct block_device *bdev, sector_t block)
  3. {
  4.    struct inode *bd_inode = bdev->bd_inode;
  5.    struct address_space *bd_mapping = bd_inode->i_mapping;
  6.    struct buffer_head *ret = NULL;
  7.    pgoff_t index;
  8.    struct buffer_head *bh;
  9.    struct buffer_head *head;
  10.    struct page *page;
  11.    int all_mapped = 1;
  12.  
  13.    index = block >> (PAGE_CACHE_SHIFT bd_inode->i_blkbits);
  14.    page = find_get_page_flags(bd_mapping, index, FGP_ACCESSED);
  15.    if (!page)
  16.        goto out;
  17.    ……//此處省略幾十行
  18. }

兩份快取?

        前面提到的情況是正常的操作流程,屬於普通檔案的page放在檔案的address space,元資料對應的page放在塊設備的address space中,大家井水不犯河水,和平共處。但是世事難料,總有一些不按套路出牌的家伙。檔案系統在塊設備上歡快的跑著,如果有人繞過檔案系統,直接去操作塊設備上屬於檔案的資料塊,這會出現什麼情況?如果這個資料塊已經在普通檔案的address space中,這次直接的資料塊修改能夠立馬體現到普通檔案的快取中嗎?

        答案是直接修改塊設備上塊會新建一個對應這個塊的page,並且這個page會被加到塊設備的address space中。也就是同一個資料塊,在其所屬的普通檔案的address space中有一個對應的page。同時,在這個塊設備的address space中也會有一個與其對應的page,所有的修改都更新到這個塊設備address space中的page上。除非重新從磁盤上讀取這一塊的資料,否則普通檔案的檔案快取並不會感知這一修改。

實驗

        口說無憑,實踐是檢驗真理的唯一標準。我在這裡準備了一個實驗,先將一個檔案的資料全部加載到page cache中,然後直接操作塊設備修改這個檔案的資料塊,再讀取檔案的內容,看看有沒有被修改。

        為了確認一個檔案的資料是否在page cache中,我先介紹一個有趣的工具—vmtouch,這個工具可以顯示出一個檔案有多少內容已經被加載到page cache。大家可以在github上獲取到它的原始碼,並自行編譯安裝

https://github.com/hoytech/vmtouch

現在開始我們的表演:

        首先,我們找一個測試檔案,就拿我家目錄下的read.c來測試,這個檔案的內容就是一些凌亂的c代碼。

➜ ~ cat read.c

  1. #include
  2. #include
  3. #include
  4. #include
  5. #include
  6.  
  7. char buf[4096] = {0};
  8.  
  9. int main(int argc, char *argv[])
  10. {
  11. int fd;
  12. if (argc != 2) {
  13. printf(“argument error.\n”);
  14. return 1;
  15. }
  16.  
  17. fd = open(argv[1], O_RDONLY);
  18. if (fd < 0) {
  19. perror(“open failed:”);
  20. return 1;
  21. }
  22.  
  23. read(fd, buf, 4096);
  24. //read(fd, buf, 4096);
  25. close(fd);
  26. }
  27.  ~

  1.  

        接著運行vmtouch,看看這個檔案是否在page cache中了,由於這個檔案剛纔被讀取過,所以檔案已經全部儲存在page cache中了。

  1.  ~ vmtouch read.c                  
  2.           Files: 1
  3.     Directories: 0
  4.  Resident Pages: 1/1  4K/4K  100%
  5.         Elapsed: 0.000133 seconds
  6.  ~

        然後我通過debugfs找到read.c的資料塊,並且通過dd命令直接修改資料塊。

  1. Inode: 3945394   Type: regular    Mode:  0644   Flags: 0x80000
  2. Generation: 659328746    Version: 0x00000000:00000001
  3. User:     0   Group:     0   Project:     0   Size: 386
  4. File ACL: 0
  5. Links: 1   Blockcount: 8
  6. Fragment:  Address: 0    Number: 0    Size: 0
  7. ctime: 0x5ad2f108:60154d80 Sun Apr 15 14:28:24 2018
  8. atime: 0x5ad2f108:5db2f37c Sun Apr 15 14:28:24 2018
  9. mtime: 0x5ad2f108:5db2f37c Sun Apr 15 14:28:24 2018
  10. crtime: 0x5ad2f108:5db2f37c Sun Apr 15 14:28:24 2018
  11. Size of extra inode fields: 32
  12. EXTENTS:
  13. (0):2681460
  14.  
  15.  ~ dd if=/dev/zero of=/dev/sda2 seek=2681460 bs=4096 count=1
  16. 1+0 records in
  17. 1+0 records out
  18. 4096 bytes (4.1 kB, 4.0 KiB) copied, 0.000323738 s, 12.7 MB/s

  1.  

        修改已經完成,我們看看直接讀取這個檔案會怎麼樣。

  1.  ~ cat read.c
  2. #include
  3. #include
  4. #include
  5. #include
  6. #include
  7.  
  8. char buf[4096] = {0};
  9.  
  10. int main(int argc, char *argv[])
  11. {
  12. int fd;
  13. if (argc != 2) {
  14. printf(“argument error.\n”);
  15. return 1;
  16. }
  17.  
  18. fd = open(argv[1], O_RDONLY);
  19. if (fd < 0) {
  20. perror(“open failed:”);
  21. return 1;
  22. }
  23.  
  24. read(fd, buf, 4096);
  25. //read(fd, buf, 4096);
  26. close(fd);
  27. }
  28.  
  29.  ~ vmtouch read.c
  30.           Files: 1
  31.     Directories: 0
  32.  Resident Pages: 1/1  4K/4K  100%
  33.         Elapsed: 0.00013 seconds

  1.  

        檔案依然在page cache中,所以我們還是能夠讀取到檔案的內容。然而當我們drop cache以後,再讀取這個檔案,會發現檔案內容被清空。

  1.  ~ vmtouch read.c
  2.           Files: 1
  3.     Directories: 0
  4.  Resident Pages: 1/1  4K/4K  100%
  5.         Elapsed: 0.00013 seconds
  6.  ~ echo 3 > /proc/sys/vm/drop_caches                        
  7.  ~ vmtouch read.c                  
  8.           Files: 1
  9.     Directories: 0
  10.  Resident Pages: 0/1  0/4K  0%
  11.         Elapsed: 0.000679 seconds
  12.  ~ cat read.c
  13.  ~

總結

        普通檔案的資料可以儲存在它的地址空間中,同時直接訪問塊設備中此檔案的塊,也會將這個檔案的資料儲存在塊設備的地址空間中。這兩份快取相互獨立,kernel並不會為這種非正常訪問同步兩份快取,從而避免了同步的開銷。        

赞(0)

分享創造快樂