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

2022-08-17 13:11:05 浏览数 (1)

概述

  • 运行在用户态的应用程序需要经常访问磁盘数据,进行读写操作,由于磁盘(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`结构如下:
代码语言:javascript复制
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进行管理的。
代码语言:javascript复制
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根据传入的盘号将盘块读入到块缓存中。

0 人点赞