page cache和buffer cache之间的关系以及验证

Linux系统存在两种缓存机制,分别是Page Cache和Buffer Cache, 其中Page Cache缓存文件的页以优化文件IO。Buffer Cache缓存块设备的块以优化块设备IO。为了加快IO访问,操作系统在文件系统和快设备之间使用内存缓存硬盘上的数据,这个就是buffer head. 但是基于块的设计,文件系层和块设备耦合比较紧密,而且文件系统并不都是基于块设备的,比如内存文件系统,网络文件系统等,再加上内和使用页的方式管理内存,因此如果使用物理页面PAGE缓存数据,支持上层文件系统,就可以和内核中的内存管理系统很好的结合。所以综合种种考虑,后来LINUX使用页代替块支持文件系统,缓存硬盘数据,相对于buffer cache,这些页面的集合相应的称之为page cache.

在linux kernel 2.4之前,这两个cache是不同的:文件在page cache中,磁盘块儿在buffer cache中。考虑到大多数的文件是由disk上的文件系统表示的,数据会在系统中有两份缓存,一份在page cache中,一份在buffer cache中。许多类unix系统都是这种模式。这种实现是很简单,但是明显的低效和冗余。在linux kernel 2.4之后,这两种caches统一了。虚拟内存子系统通过page cache驱动IO。如果数据同时在page cache和buffer cache中,buffer cache会简单的指向page cache,数据只会在内存中保有一份。Page cache就是你所知道的文件在内存中的缓存:它会加快文件访问速度。
无论如何内核要对块设备执行基于块的IO而不是虚拟内存page。由于绝大部分的块表示文件的数据部,所以大部分的buffer cache都可由page cache表达,但是有少量的比如文件元数据仍然是保留在buffer cache中来缓存。
我们简单看一下大概的结构,以比较常见的4K page,512 bytes 块儿缓冲区为例:

从page到lbn的转换,则需要 struct address_space 结构体了,图中没有绘制出来。

偏技术流的表示如下,根据block的大小,去分配buffer_head,通常是4个buffer_head,指向一个page.另外,下图是针对b_size为512B的1K的情况,如果b_size足够大,则只需要一个BH即可描述。

代码实现的地方,页面和地层块设备之间使用已有的块机制桥接起来,每个页面划分为若干个块,在为新文件分配一个新的页面时,文件系统将调用create_empty_buffers为页面分配对应的块。块和页面之间通过数据结构buffer_head的字段b_data关联起来,使用页面缓存之后,块没有自己的数据区了,块的字段b_data指向页内偏移。支撑同一个页面的块之间通过b_this_page字段链接起来。

图中的逻辑可以用下面这段内核代码证明:

磁盘操作底层通过submit_bio调用发挥功能,这一层其实不关心BUFFER HEAD结构,也不需要这个结构,struct buffer_head只是一个中间媒介,所以你有时候会看到某个PAGE页面缓存了文件内容,但是却没有buffer_head与之对应,比如ext4_readpage是读区一个完整的页面,这个时候可以直接用PAGE构造bio_vec(bio_add_page),而不需要再经过buffer_head(submit_bh)再走一遭了。

这也就是为什么,从normal file page cache中获取的页面,没有struct buffer_head(page_has_buffers探测不成功)对应的原因。

下图是一幅经典的描述Linux块儿设备工作原理的架构图:

通过sendfile接口,可以BYPASS掉用户层的缓存,直接在内核态对数据进行拷贝。

#include <sys/sendfile.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static int open_or_die(const char *filename, int flags)
{
    int fd = open(filename, flags);

    if (fd < 0) {
        printf("Failed to open '%s'; "
               "check prerequisites are available\n", filename);
        exit(1);
    }
    return fd;
}

static void exe_cp(const char *src, const char *dest)
{
    int in_fd = open_or_die(src, O_RDONLY);
    int out_fd = open(dest, O_RDWR|O_CREAT|O_TRUNC, 0755);
    struct stat info;

    fstat(in_fd, &info);

    sendfile(out_fd, in_fd, NULL, info.st_size);

    close(in_fd);
    close(out_fd);
}

int main(void)
{
    exe_cp("./a.out", "./b.out");
    return 0;
}

嗯,是的,上面的图有点乱,那我们再抽象一下看看。

这里写图片描述

关于扇区-簇-buffercache-pagecache的关系,可以用下图予以说明。

在这里插入图片描述

对文件的MMAP和文件的PAGE CACHE应该是共享同样的 PAGE CACHE物理页面,具体看文件映射的PAGE FAULT函数 filemap_fault.

在这里插入图片描述

[内核内存] page cache_pagecache是内核缓冲区吗_早起的虫儿有鹰吃的博客-CSDN博客

文件MMAP的物理页面和PAGE CACH的物理页面是同一个,核心关键函数是__do_page_cache_readahead,其分配了PAGE CACHE,并且进行预读操作,之后,映射给MMAP用户态。

口说无凭,我们实际调试一下内核,验证一下上述架构,同时我们也要澄清一个问题,这个问题可以描述为,文件存在于块设备中,如果从文件所在的设备文件,比如/dev/sda7等等读取块,和通过文件系统读取同样的文件块,他们在page cache中,乃至在buffer cache中,是否是同一份呢?

下面开始:


内核驱动模块:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/stat.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/file.h>
#include <linux/pagemap.h>
#include <linux/buffer_head.h>
 
/* 定义字符设备和驱动的名字 */
#define DEVICE_NAME "char_demo_test"
#define DRIVER_NAME "char_demo_test"
 
/* 定义设备号和设备数及大小 */
#define DEVICE_MAJOR 	240
#define DRIVER_MINOR 	6
#define DEVICE_NUM 	2
#define DEVICE_SIZE 	3000
 
/* 申明主设备号的次设备号 */
int major_num = DEVICE_MAJOR;
int minor_num = DRIVER_MINOR;
 
/* 定义模块参数 */
module_param(major_num, int , 0664);
module_param(minor_num, int, 0664);
 
static struct class *myclass;
struct reg_dev 
{
	char *data;
	unsigned long size;
	struct cdev cdev;
};
 
struct reg_dev *my_devices;
 
 
static int char_demo_open(struct inode *inode, struct file *file){
	printk(KERN_EMERG "chardevnode_open is success!imajor %d, iminor %d.\n", imajor(inode), iminor(inode));
	
	return 0;
}
 
static int char_demo_close(struct inode *inode, struct file *file)
{
	printk(KERN_EMERG "chardevnode_release is success!\n");
	return 0;
}

static void dump_memory(unsigned char *buf, long num)
{
	long cnt;
	printk("Dump:");

	for(cnt = 0; cnt < num; cnt ++)
	{
		if(cnt % 16 == 0)
		{
			printk("\n0x%p", buf);
		}
		printk("  %02x", *buf);
		buf ++;
	}
    
}

void probe_file(long fd)
{
	char buf[128];
	char *name;
	struct inode *fino;
	struct address_space * mapping;
	struct page * page1;
	struct page * page2;
	struct page * page3;
	unsigned char *page1_addr, *page2_addr, *page3_addr;
	struct buffer_head *bh1, *bh2, *bh3;

        struct file * f = fget(fd);
	memset(buf, 0x00, 128);
	if (!f)
	{
		printk("%s line %d, file is null\n", __func__, __LINE__);
		return ;
	}

	name = file_path(f, buf, sizeof(buf));
	printk("%s line %d, name %s.\n", __func__, __LINE__, name);
	fino = file_inode(f);

	mapping = f->f_mapping;
	printk("%s line %d, PAGE_SIZE = %ld, nrpages = %ld.\n", __func__, __LINE__, PAGE_SIZE, mapping->nrpages);

    page1 = find_get_page(mapping, 0);
	page2 = find_get_page(mapping, 1);
	page3 = find_get_page(mapping, 2);

	if(!page1 || !page2 || !page3)
	{
		printk("%s line %d, page is null.\n", __func__, __LINE__);
		return;
	}
	printk("%s line %d, page1 = %p, page2 = %p, page3 = %p.\n", __func__, __LINE__,page1, page2, page3);

	page1_addr = page_address(page1);
	page2_addr = page_address(page2);
	page3_addr = page_address(page3);

	if(!page1_addr || !page2_addr || !page3_addr)
	{
		printk("%s line %d, page addr is null.\n", __func__, __LINE__);
		return;
	}
	dump_memory(page1_addr, 16);
	dump_memory(page2_addr, 16);
	dump_memory(page3_addr, 16);

	bh1 = page_buffers(page1);	
	bh2 = page_buffers(page2);	
	bh3 = page_buffers(page3);	

	if(!bh1 || !bh2 || !bh3)
	{
		printk("%s line %d, bh is null.\n", __func__, __LINE__);
		return;
	}

	printk("%s line %d, lbn1=%lld, lbn2=%lld, lbn3=%lld.\n", __func__, __LINE__, bh1->b_blocknr, bh2->b_blocknr, bh3->b_blocknr);
	printk("%s line %d, bsize1=%ld, bsize2=%ld, bsize3=%ld.\n", __func__, __LINE__, bh1->b_size, bh2->b_size, bh3->b_size);
	printk("%s line %d, bh1->b_this_page = %p, bh1 = %p.\n", __func__, __LINE__, bh1->b_this_page, bh1);
	printk("%s line %d, bh2->b_this_page = %p, bh2 = %p.\n", __func__, __LINE__, bh2->b_this_page, bh2);
	printk("%s line %d, bh3->b_this_page = %p, bh3 = %p.\n", __func__, __LINE__, bh3->b_this_page, bh3);

	if(bh1->b_this_page != bh1)
	{
		printk("%s line %d, bh1 is not identical with bh1->b_this_page\n", __func__, __LINE__);
	}
	else
	{
		printk("%s line %d, bh1 is identical with bh1->b_this_page\n", __func__, __LINE__);
	}

	if(bh2->b_this_page != bh2)
	{
		printk("%s line %d, bh2 is not identical with bh2->b_this_page\n", __func__, __LINE__);
	}
	else
	{
		printk("%s line %d, bh2 is identical with bh2->b_this_page\n", __func__, __LINE__);
	}

	if(bh3->b_this_page != bh3)
	{
		printk("%s line %d, bh3 is not identical with bh3->b_this_page\n", __func__, __LINE__);
	}
	else
	{
		printk("%s line %d, bh3 is identical with bh3->b_this_page\n", __func__, __LINE__);
	}

	if(bh3->b_page != page3)
	{
		printk("%s line %d, b_page is not identical with page3\n", __func__, __LINE__);
	}
	else
	{
		printk("%s line %d, b_page is identical with page3\n", __func__, __LINE__);
	}

	if(bh2->b_page != page2)
	{
		printk("%s line %d, b_page is not identical with page2\n", __func__, __LINE__);
	}
	else
	{
		printk("%s line %d, b_page is identical with page2\n", __func__, __LINE__);
	}

	if(bh1->b_page != page1)
	{
		printk("%s line %d, b_page is not identical with page1\n", __func__, __LINE__);
	}
	else
	{
		printk("%
  • 8
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

papaofdoudou

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值