内核采用一种通用的地址空间方案,来建立缓存数据与其来源之间的关联。
1) 内存中的页分配到每个地址空间。这些页的内容可以由用户进程或内核本身使用各式各样的方法操作。这些数据表示了缓存中的内容;
2) 后备存储器struct backing_dev_info指定了填充地址空间中页的数据的来源。地址空间关联到处理器的虚拟地址空间,是由处理器在虚拟内存中管理的一个区域到设备device上对应位置之间的一个映射。
如果访问了虚拟内存中的某个位置,该位置没有关联到物理内存页,内核可根据地址空间结构来找到读取数据的来源。
为支持数据传输,每个地址空间都提供了一组操作,以容许地址空间所涉及双方面的交互。
地址空间是内核中最关键的数据结构之一,对该数据结构的管理,已经演变为内核面对的最关键的问题之一。 页缓存的任务在于,获得一些物理内存页,以加速在块设备上按页为单位执行的操作。
内核使用了基数树来管理与一个地址空间相关的所有页,以便尽可能降低开销。对于基数树的理解在这里就不分析了,后面有空的时候再做分析。
地址空间操作
[cpp]
structaddress_space_operations {
/*将地址空间的一页或多页写回到底层设备
这是通过向块层发出一个相应的请求来完成的*/
int(*writepage)(structpage *page,structwriteback_control *wbc);
/*从后备存储器将一页或多个连续的页读入页帧*/
int(*readpage)(structfile *,structpage *);
/*对尚未回写到后备存储器的数据进行同步*/
void(*sync_page)(structpage *);
/* Write back some dirty pages from this mapping. */
int(*writepages)(structaddress_space *,structwriteback_control *);
/* Set a page dirty. Return true if this dirtied it */
int(*set_page_dirty)(structpage *page);
int(*readpages)(structfile *filp,structaddress_space *mapping,
structlist_head *pages, unsigned nr_pages);
/*执行由write系统调用触发的写操作*/
int(*write_begin)(structfile *,structaddress_space *mapping,
loff_t pos, unsigned len, unsigned flags,
structpage **pagep,void**fsdata);
int(*write_end)(structfile *,structaddress_space *mapping,
loff_t pos, unsigned len, unsigned copied,
structpage *page,void*fsdata);
/* Unfortunately this kludge is needed for FIBMAP. Don't use it */
sector_t (*bmap)(structaddress_space *, sector_t);
void(*invalidatepage) (structpage *, unsignedlong);
int(*releasepage) (structpage *, gfp_t);
ssize_t (*direct_IO)(int,structkiocb *,conststructiovec *iov,
loff_t offset, unsignedlongnr_segs);
int(*get_xip_mem)(structaddress_space *, pgoff_t,int,
void**, unsignedlong*);
/* migrate the contents of a page to the specified target */
int(*migratepage) (structaddress_space *,
structpage *,structpage *);
int(*launder_page) (structpage *);
int(*is_partially_uptodate) (structpage *, read_descriptor_t *,
unsignedlong);
int(*error_remove_page)(structaddress_space *,structpage *);
};
页面缓存的实现基于基数树,缓存属于内核中性能要求最苛刻的部分之一,而且广泛用于内核的所有子系统,实现也比较简单。举两个例子,其他的暂时不做分析了。
分配页面用于加入地址空间
[cpp]
/*从伙伴系统中分配页面,页面的标志根据地址空间中的标志进行设置*/
staticinlinestructpage *page_cache_alloc(structaddress_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.
*/
staticinlineintadd_to_page_cache(structpage *page,
structaddress_space *mapping, pgoff_t offset, gfp_t gfp_mask)
{
interror;
__set_page_locked(page);
/*实际的添加工作*/
error = add_to_page_cache_locked(page, mapping, offset, gfp_mask);
if(unlikely(error))
__clear_page_locked(page);
returnerror;
}
[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.
*/
intadd_to_page_cache_locked(structpage *page,structaddress_space *mapping,
pgoff_t offset, gfp_t gfp_mask)
{
interror;
VM_BUG_ON(!PageLocked(page));
error = mem_cgroup_cache_charge(page, current->mm,
gfp_mask & GFP_RECLAIM_MASK);
if(error)
gotoout;
/*树的相关结构申请*/
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:
returnerror;
}