vlambda博客
学习文章列表

浅谈Linux内核中页缓存和块缓存


作者 时间 QQ技术交流群
[email protected] 2022/04/11 672152841



概述

  • 运行在用户态的应用程序需要经常访问磁盘数据,进行读写操作,由于磁盘(HDD)相对较慢,没有任何缓存的情况下,每次应用读写操作时延页非常慢;在内核设计之初,添加了缓存设计,将磁盘数据保存在RAM中,后续的读写操作转换为在RAM中的操作,从而加快应用读写操作的速度。

  • 页高速缓存(Page Cache)的用途是加速访问文件数据,给定inode索引节点和文件的页面的偏移量,快速的在内存中找到文件页的内容。这个Page Cache是存在于VFS和实际文件系统之间。如果应用指定了O_DIRECT方式访问文件,则直接绕开Page Cache直接访问块设备层。Page Cache中缓存的Page大小为4K。Page Cache高速缓存使用的是物理页帧,以页为单位将文件内容缓存,逻辑文件(struct file)中每一个页可以划分为块单位,将每个块映射到磁盘的盘块,因此一个文件的页可以和多个Buffer Cache中块缓存关联,每个块缓存和磁盘的盘块进行关联。

  • 块缓存中缓存的单个块大小是以磁盘扇区大小,默认是512个字节。无论应用程序读取多少个字节,在最终访问磁盘的时候,都必须以扇区大小(512个字节)读取;对应的块缓存中缓存块大小页是扇区的大小。



Page Cache(页缓存)

  • Linux页高速缓存任何基于页的数据,所缓存的Page包括普通文件内容、块设备文件、内存映射文件的读写。页缓存中一个页帧的文件数据锁对应的磁盘块不必是连续的。如果是普通文件内容它们只是逻辑上连续的磁盘盘块,这些磁盘块在磁盘上可以是不连续的。针对块设备文件的页缓存则是磁盘盘块在物理磁盘上是连续的。


  • 页缓存中采用了struct address_space数据结构来管理。它特指一个文件内容所形成的的页缓存空间。struct address_space不仅仅管理某个文件已经读入的页帧内容,同时也管理这些页帧到进程空间的文件映射关系(多个进程打开同一个文件下,多个进程读取不同位置的数据)。如果一个struct address_space和一个文件对应,所有进程访问的页缓存通过一个struct address_space进行管理。如果文件类型是普通文件,struct address_space和文件inode中的i_data进行关联,同时inode中的i_mapping指向i_data这个成员。如果文件类型是块设备文件,struct address_space嵌入到块设备中文件的主索引节点,struct block_device中的db_inode指向块设备这个inode.struct address_space`结构如下:


struct address_space {
// 对应物理文件的inide
struct inode *host;
// 缓存的radix的root节点
struct radix_tree_root i_pages;
atomic_t i_mmap_writable;
// i_mmap是红黑树,管理页帧相关的VMA的映射关系
struct rb_root_cached i_mmap;
struct rw_semaphore i_mmap_rwsem;
// page的个数
unsigned long nrpages;

unsigned long nrexceptional;
pgoff_t writeback_index;
// address_space的操作操作函数,每个磁盘文件系统都会有自己的操作函数
const struct address_space_operations *a_ops;
unsigned long flags;
spinlock_t private_lock;
gfp_t gfp_mask;
struct list_head private_list;
void *private_data;
errseq_t wb_err;
} __attribute__((aligned(sizeof(long)))) __randomize_layout;


// 以ext4磁盘文件系统操作函数
static const struct address_space_operations ext4_da_aops = {
.readpage = ext4_readpage,
.readpages = ext4_readpages,
.writepage = ext4_writepage,
.writepages = ext4_writepages,
.write_begin = ext4_da_write_begin,
.write_end = ext4_da_write_end,
.set_page_dirty = ext4_set_page_dirty,
.bmap = ext4_bmap,
.invalidatepage = ext4_da_invalidatepage,
.releasepage = ext4_releasepage,
.direct_IO = ext4_direct_IO,
.migratepage = buffer_migrate_page,
.is_partially_uptodate = block_is_partially_uptodate,
.error_remove_page = generic_error_remove_page,
};


Buffer Cache(块缓存)

  • 块缓存和页缓存是相对独立的两种缓存机制,通常也可以结合在一起共同描述页缓存中保存文件的数据,向上以页为单位于页缓存交互,向下以块缓存为单位和通用设备层进行交互。在内核中块缓存是通过struct buffer_head进行管理的。


 
struct buffer_head {
// 块缓存的标记
unsigned long b_state;
// 同一个页缓存的块缓存构成的环状链表
struct buffer_head *b_this_page;
// 所属的页缓存
struct page *b_page;
// 块缓存个数
sector_t b_blocknr;
size_t b_size; /* size of mapping */
// 块缓存的起点
char *b_data;
// 所属的块设备
struct block_device *b_bdev;
bh_end_io_t *b_end_io; /* I/O completion */
void *b_private; /* reserved for b_end_io */
struct list_head b_assoc_buffers; /* associated with another mapping */
// 所属的地址空间
struct address_space *b_assoc_map;
atomic_t b_count; /* users using this buffer_head */
};
  • 内核中按照块访问的场景不多,主要是针对超级块和索引节点等磁盘数据管理操作时候才会用到。例如sb_readsb_getblk根据传入的盘号将盘块读入到块缓存中。