文件系统和裸块设备的page cache问题

Linux内核之旅 | 阿克曼

普通文件的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_SIZE-1);

  17.    last_index = (*ppos + iter->count + PAGE_CACHE_SIZE-1) >> 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 <stdio.h>

  2. #include <unistd.h>

  3. #include <fcntl.h>

  4. #include <sys/types.h>

  5. #include <sys/stat.h>

  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 <stdio.h>

  3. #include <unistd.h>

  4. #include <fcntl.h>

  5. #include <sys/types.h>

  6. #include <sys/stat.h>

  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
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值