浅谈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_read
和sb_getblk
根据传入的盘号将盘块读入到块缓存中。