红联Linux门户
Linux帮助

Linux缓存机制之页缓存

发布时间:2014-11-22 21:51:30来源:linux网站作者:bullbat

Linux运用一个功能广泛的缓冲和缓存框架来提高系统的速度。缓冲和缓存利用一部分系统物理内存,确保最重要、最常使用的块设备数据在操作时可直接从主内存获取,而无需从低速设备读取。物理内存还用于存储从快设备读取的数据,使得随后对该数据的访问可直接在物理内存进行,而无需从外部设备再次取用。考虑系统中多种因素然后延迟写回在总体上改进了系统的性能。前面分析的部分,例如内存管理的slab缓存是一个内存到内存的缓存,其目地不是加速对低速设备的操作,而是对现有资源进行更简单、更高效的使用。文件系统的Dentry缓存也用于减少对低速块设备的访问,但他无法推广到通用场合,因为他是专门用于处理单一数据类型的。


内核为块设备提供了两种通用的缓存方案:

1)页缓存,针对以页为单位的所有操作,并考虑了特定体系结构上的页长度。一个主要的例子是内存映射技术。因为其他类型的文件访问也是基于内核中的这一技术实现的。所以页缓存实际上负责了块设备的大部分缓存工作。

2)块缓存,以块为操作单位。在进行I/O操作时,存取的单位是设备的各个块,而不是整个内存页。尽管页长度对所有文件系统都是相同的,但块长度取决于特定的文件系统或其设置。因而,块缓存必须能够处理不同长度的块。

目前用于块传输的标准数据结构已经演变为struct bio。用这种方式进行块传输更为高效,因为他可以合并同一请求中后续的块,加速处理的进行。在许多场合下,页缓存和块缓存是联合使用的。例如,一个缓存的页在写操作期间可以划分为不同的缓冲区,这样可以在更细的力度下,识别出页被修改的部分。好处在于,在将数据写回时,只需要回写被修改的部分,无需将这个页面传输回底层的块设备。


页面缓存结构
[cpp]

/*高速缓存的核心数据结构,对块设备的读写操作都放在该结构体里*/ 
struct address_space { 
/*与地址空间所管理的区域之间的关联数据结构之一
inode结构指定了后备存储器*/ 
struct inode*host;  /* owner: inode, block_device */ 
/*与地址空间所管理的区域之间的关联之二
,page_tree列出了地址空间中所有的物理内存页*/ 
struct radix_tree_root  page_tree;  /* radix tree of all pages */ 
spinlock_t  tree_lock;  /* and lock protecting it */ 
/*所有用VM_SHARED属性创建的映射*/ 
unsigned inti_mmap_writable;/* count VM_SHARED mappings */ 
/*基数根节点,该树包含了与该inode相关的所有
普通内存映射。该树的任务在于,支持查找包含了
给定区间中至少一页的所有内存区域*/ 
struct prio_tree_root   i_mmap; /* tree of private and shared mappings */ 
/*包含所有在非线性映射中的页*/ 
struct list_headi_mmap_nonlinear;/*list VM_NONLINEAR mappings */ 
spinlock_t  i_mmap_lock;/* protect tree, count, list */ 
unsigned inttruncate_count; /* Cover race condition with truncate */ 
/*缓存页的总数*/ 
unsigned long   nrpages;/* number of total pages */ 
pgoff_t writeback_index;/* writeback starts here */ 
const struct address_space_operations *a_ops;   /* methods */ 
/*集主要用于保存映射页所来自的GFP内存区
的有关信息*/ 
unsigned long   flags;  /* error bits/gfp mask */ 
/*指向后备存储器结构,该结构包含了与地址空间相关的
后备存储器的有关信息,后备存储器是指与地址空间相关
的外部设备,用做地址空间中信息的来源。他通常是块设备
*/ 
struct backing_dev_info *backing_dev_info; /* device readahead, etc */ 
spinlock_t  private_lock;   /* for use by the address_space */ 
/*用于将包含文件系统元数据(通常是间接块)的buffer_head
实例彼此连接起来*/ 
struct list_headprivate_list;   /* ditto */ 
/*指向相关的地址空间的指针*/ 
struct address_space*assoc_mapping; /* ditto */ 
} __attribute__((aligned(sizeof(long))));


后备存储信息
[cpp]

struct backing_dev_info { 
struct list_head bdi_list; 
struct rcu_head rcu_head; 
/*最大预读数量,单位为PAGE_CACHE_SIZE*/ 
unsigned long ra_pages; /* max readahead in PAGE_CACHE_SIZE units */ 
/*对该成员,总是使用原子操作,指定了后备存储器的状态*/ 
unsigned long state;/* Always use atomic bitops on this */ 
/*设备能力*/ 
unsigned int capabilities; /* Device capabilities */ 
congested_fn *congested_fn; /* Function pointer if device is md/dm */ 
void *congested_data;   /* Pointer to aux data for congested func */ 
void (*unplug_io_fn)(struct backing_dev_info *, struct page *); 
void *unplug_io_data; 
 
char *name; 
 
struct percpu_counter bdi_stat[NR_BDI_STAT_ITEMS]; 
 
struct prop_local_percpu completions; 
int dirty_exceeded; 
 
unsigned int min_ratio; 
unsigned int max_ratio, max_prop_frac; 
 
struct bdi_writeback wb;  /* default writeback info for this bdi */ 
spinlock_t wb_lock;   /* protects update side of wb_list */ 
struct list_head wb_list; /* the flusher threads hanging off this bdi */ 
unsigned long wb_mask;/* bitmask of registered tasks */ 
unsigned int wb_cnt;  /* number of registered tasks */ 
 
struct list_head work_list; 
 
struct device *dev; 
 
#ifdef CONFIG_DEBUG_FS  
struct dentry *debug_dir; 
struct dentry *debug_stats; 
#endif  
};


内核采用一种通用的地址空间方案,来建立缓存数据与其来源之间的关联。

1)内存中的页分配到每个地址空间。这些页的内容可以由用户进程或内核本身使用各式各样的方法操作。这些数据表示了缓存中的内容;

2)后备存储器struct backing_dev_info指定了填充地址空间中页的数据的来源。地址空间关联到处理器的虚拟地址空间,是由处理器在虚拟内存中管理的一个区域到设备device上对应位置之间的一个映射。

如果访问了虚拟内存中的某个位置,该位置没有关联到物理内存页,内核可根据地址空间结构来找到读取数据的来源。

为支持数据传输,每个地址空间都提供了一组操作,以容许地址空间所涉及双方面的交互。

地址空间是内核中最关键的数据结构之一,对该数据结构的管理,已经演变为内核面对的最关键的问题之一。 页缓存的任务在于,获得一些物理内存页,以加速在块设备上按页为单位执行的操作。

内核使用了基数树来管理与一个地址空间相关的所有页,以便尽可能降低开销。对于基数树的理解在这里就不分析了,后面有空的时候再做分析。


地址空间操作

[cpp]

struct address_space_operations { 
/*将地址空间的一页或多页写回到底层设备
这是通过向块层发出一个相应的请求来完成的*/ 
int (*writepage)(struct page *page, struct writeback_control *wbc); 
/*从后备存储器将一页或多个连续的页读入页帧*/ 
int (*readpage)(struct file *, struct page *); 
/*对尚未回写到后备存储器的数据进行同步*/ 
void (*sync_page)(struct page *); 
 
/* Write back some dirty pages from this mapping. */ 
int (*writepages)(struct address_space *, struct writeback_control *); 
 
/* Set a page dirty.  Return true if this dirtied it */ 
int (*set_page_dirty)(struct page *page); 
 
int (*readpages)(struct file *filp, struct address_space *mapping, 
struct list_head *pages, unsigned nr_pages); 
/*执行由write系统调用触发的写操作*/ 
int (*write_begin)(struct file *, struct address_space *mapping, 
loff_t pos, unsigned len, unsigned flags, 
struct page **pagep, void **fsdata); 
int (*write_end)(struct file *, struct address_space *mapping, 
loff_t pos, unsigned len, unsigned copied, 
struct page *page, void *fsdata); 
 
/* Unfortunately this kludge is needed for FIBMAP. Don't use it */ 
sector_t (*bmap)(struct address_space *, sector_t); 
void (*invalidatepage) (struct page *, unsigned long); 
int (*releasepage) (struct page *, gfp_t); 
ssize_t (*direct_IO)(int, struct kiocb *, const struct iovec *iov, 
loff_t offset, unsigned long nr_segs); 
int (*get_xip_mem)(struct address_space *, pgoff_t, int, 
void **, unsigned long *); 
/* migrate the contents of a page to the specified target */ 
int (*migratepage) (struct address_space *, 
struct page *, struct page *); 
int (*launder_page) (struct page *); 
int (*is_partially_uptodate) (struct page *, read_descriptor_t *, 
unsigned long); 
int (*error_remove_page)(struct address_space *, struct page *); 
};


页面缓存的实现基于基数树,缓存属于内核中性能要求最苛刻的部分之一,而且广泛用于内核的所有子系统,实现也比较简单。举两个例子,其他的暂时不做分析了。

分配页面用于加入地址空间
[cpp]

/*从伙伴系统中分配页面,页面的标志根据地址空间中的标志进行设置*/ 
static inline struct page *page_cache_alloc(struct address_space *x) 

return __page_cache_alloc(mapping_gfp_mask(x)); 
}


分配完了添加到基数树中
[cpp]

/*
* Like add_to_page_cache_locked, but used to add newly allocated pages:
* the page is new, so we can just run __set_page_locked() against it.
*/ 
static inline int add_to_page_cache(struct page *page, 
struct address_space *mapping, pgoff_t offset, gfp_t gfp_mask) 

int error; 
 
__set_page_locked(page); 
/*实际的添加工作*/ 
error = add_to_page_cache_locked(page, mapping, offset, gfp_mask); 
if (unlikely(error)) 
__clear_page_locked(page); 
return error; 

[cpp]

/**
* add_to_page_cache_locked - add a locked page to the pagecache
* @page:   page to add
* @mapping:the page's address_space
* @offset: page index
* @gfp_mask:   page allocation mode
*
* This function is used to add a page to the pagecache. It must be locked.
* This function does not add the page to the LRU.  The caller must do that.
*/ 
int add_to_page_cache_locked(struct page *page, struct address_space *mapping, 
pgoff_t offset, gfp_t gfp_mask) 

int error; 
 
VM_BUG_ON(!PageLocked(page)); 
 
error = mem_cgroup_cache_charge(page, current->mm, 
gfp_mask & GFP_RECLAIM_MASK); 
if (error) 
goto out; 
/*树的相关结构申请*/ 
error = radix_tree_preload(gfp_mask & ~__GFP_HIGHMEM); 
if (error == 0) { 
page_cache_get(page);/*使用计数加一*/ 
page->mapping = mapping; 
page->index = offset; 
 
spin_lock_irq(&mapping->tree_lock); 
/*实际的插入操作*/ 
error = radix_tree_insert(&mapping->page_tree, offset, page); 
if (likely(!error)) { 
mapping->nrpages++; 
__inc_zone_page_state(page, NR_FILE_PAGES); 
if (PageSwapBacked(page)) 
__inc_zone_page_state(page, NR_SHMEM); 
spin_unlock_irq(&mapping->tree_lock); 
} else { 
page->mapping = NULL; 
spin_unlock_irq(&mapping->tree_lock); 
mem_cgroup_uncharge_cache_page(page); 
page_cache_release(page); 

radix_tree_preload_end(); 
} else 
mem_cgroup_uncharge_cache_page(page); 
out: 
return error; 
}