概述
关于Linux内核页高速缓存的介绍网络上有很多优秀的资源如下:
1、Linux 内核之页高速缓存与页回写
https://www.jianshu.com/p/d33ec2707e7f
2、页高速缓存和页回写
https://blog.csdn.net/yusiguyuan/article/details/12022811
3、《Linux内核设计与实现》读书笔记(十六)页高速缓存和页回写
https://www.cnblogs.com/wang_yb/archive/2013/11/21/3436126.html
4、计算机底层知识拾遗(六)理解页缓存page cache和地址空间address_space
https://blog.csdn.net/ITer_ZC/article/details/44195731
5、Linux内核——第十五章:页高速缓存
https://blog.csdn.net/duxingxia356/article/details/41982771
本文主要讲述内核的查找页相关的处理函数。基于 linux-4.4.4
一、address_space
页高速缓存的核心数据时address_space对象,它是一个嵌入在页所有者的索引节点对象中的数据结构。高速缓存中的许多页可能属于同一个所有者,从而可能被链接到同一个address_space对象。该对象还在所有者的页和对这些页的操作之间建立起链接关系。
每个页描述符都包括把页链接到页高速缓存的两个字段mapping和index。mapping字段指向拥有页的索引节点的address_space对象,index字段表示在所有者的地址空间中以页大小为单位的偏移量,也就是在所有者的磁盘映射中页中数据的位置。在页高速缓存中查找时使用这两个字段。
address_space对象定义在include/linux/fs.h中,其内容如下:
struct address_space {
struct inode *host; /* owner: inode, block_device */
struct radix_tree_root page_tree; /* radix tree of all pages */
spinlock_t tree_lock; /* and lock protecting it */
atomic_t i_mmap_writable;/* count VM_SHARED mappings */
struct rb_root i_mmap; /* tree of private and shared mappings */
struct rw_semaphore i_mmap_rwsem; /* protect tree, count, list */
/* Protected by tree_lock together with the radix tree */
unsigned long nrpages; /* number of total pages */
unsigned long nrshadows; /* number of shadow entries */
pgoff_t writeback_index;/* writeback starts here */
const struct address_space_operations *a_ops; /* methods */
unsigned long flags; /* error bits/gfp mask */
spinlock_t private_lock; /* for use by the address_space */
struct list_head private_list; /* ditto */
void *private_data; /* ditto */
} __attribute__((aligned(sizeof(long))));
如果页高速缓存中页的所有者是一个文件,address_space对象就嵌入在VFS索引节点对象的i_data字段中。索引节点的i_mapping字段总是指向索引节点的数据页所有者的address_space对象。address_space对象的host字段指向其所有者的索引节点对象。因此,如果页属于一个文件,那么页的所有者就是文件的索引节点,而且相应的address_space对象存放在VFS索引节点对象的i_data字段中。索引节点的i_mapping字段指向同一个索引节点的i_data字段,而address_space对象的host字段也指向这个索引节点。
address space对象的关键字段是a_ops,指向一个类型为address_space_operations的表,定义在include/linux/fs.h中,表中定义了对所有者的页进行处理的各种方法。
address_space_operations内容如下:
struct address_space_operations {
int (*writepage)(struct page *page, struct writeback_control *wbc);
int (*readpage)(struct file *, 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);
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 int, unsigned int);
int (*releasepage) (struct page *, gfp_t);
void (*freepage)(struct page *);
ssize_t (*direct_IO)(struct kiocb *, struct iov_iter *iter, loff_t offset);
/*
* migrate the contents of a page to the specified target. If
* migrate_mode is MIGRATE_ASYNC, it must not block.
*/
int (*migratepage) (struct address_space *,
struct page *, struct page *, enum migrate_mode);
int (*launder_page) (struct page *);
int (*is_partially_uptodate) (struct page *, unsigned long,
unsigned long);
void (*is_dirty_writeback) (struct page *, bool *, bool *);
int (*error_remove_page)(struct address_space *, struct page *);
/* swapfile support */
int (*swap_activate)(struct swap_info_struct *sis, struct file *file,
sector_t *span);
void (*swap_deactivate)(struct file *file);
};
这些方法指针指向那些为指定缓存对象实现的页I/O操作。每个后备存储都通过自己的address_space_operation描述自己如何与页高速缓存交互。比如ext4文件系统在文件fs/ext4/inode.c中定义了自己的操作表如下所示:
static const struct address_space_operations ext4_aops = {
.readpage = ext4_readpage,
.readpages = ext4_readpages,
.writepage = ext4_writepage,
.writepages = ext4_writepages,
.write_begin = ext4_write_begin,
.write_end = ext4_write_end,
.bmap = ext4_bmap,
.invalidatepage = ext4_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,
};
这些方法提供了管理页高速缓存的各种行为,包括最常用的读页到缓存、更新缓存数据等。
二、内核页高速缓存查找页处理函数
函数find_get_page()接收的参数为指向address_space对象的指针和偏移量。find_get_page()函数定义在include/linux/pagemap.h中,是一个内联函数,其内容如下:
static inline struct page *find_get_page(struct address_space *mapping,
pgoff_t offset)
{
return pagecache_get_page(mapping, offset, 0, 0);
}
实际上是调用了pagecache_get_page ()函数,进入到这个函数中查看究竟。pagecache_get_page()函数定义在mm/filemap.c中,其内容如下:
struct page *pagecache_get_page(struct address_space *mapping, pgoff_t offset,
int fgp_flags, gfp_t gfp_mask)
{
struct page *page;
repeat:
page = find_get_entry(mapping, offset);
if (radix_tree_exceptional_entry(page))
page = NULL;
if (!page)
goto no_page;
if (fgp_flags & FGP_LOCK) {
if (fgp_flags & FGP_NOWAIT) {
if (!trylock_page(page)) {
page_cache_release(page);
return NULL;
}
} else {
lock_page(page);
}
/* Has the page been truncated? */
if (unlikely(page->mapping != mapping)) {
unlock_page(page);
page_cache_release(page);
goto repeat;
}
VM_BUG_ON_PAGE(page->index != offset, page);
}
if (page && (fgp_flags & FGP_ACCESSED))
mark_page_accessed(page);
no_page:
if (!page && (fgp_flags & FGP_CREAT)) {
int err;
if ((fgp_flags & FGP_WRITE) && mapping_cap_account_dirty(mapping))
gfp_mask |= __GFP_WRITE;
if (fgp_flags & FGP_NOFS)
gfp_mask &= ~__GFP_FS;
page = __page_cache_alloc(gfp_mask);
if (!page)
return NULL;
if (WARN_ON_ONCE(!(fgp_flags & FGP_LOCK)))
fgp_flags |= FGP_LOCK;
/* Init accessed so avoid atomic mark_page_accessed later */
if (fgp_flags & FGP_ACCESSED)
__SetPageReferenced(page);
err = add_to_page_cache_lru(page, mapping, offset,
gfp_mask & GFP_RECLAIM_MASK);
if (unlikely(err)) {
page_cache_release(page);
page = NULL;
if (err == -EEXIST)
goto repeat;
}
}
return page;
}
在pagecache_get_page()函数中,首先调用了find_get_entry()函数返回了一个page,进入到find_get_entry()函数中,该函数同样定义在mm/filemap.c文件中,其内容如下:
struct page *find_get_entry(struct address_space *mapping, pgoff_t offset)
{
void **pagep;
struct page *page;
rcu_read_lock();
repeat:
page = NULL;
pagep = radix_tree_lookup_slot(&mapping->page_tree, offset);
if (pagep) {
page = radix_tree_deref_slot(pagep);
if (unlikely(!page))
goto out;
if (radix_tree_exception(page)) {
if (radix_tree_deref_retry(page))
goto repeat;
/*
* A shadow entry of a recently evicted page,
* or a swap entry from shmem/tmpfs. Return
* it without attempting to raise page count.
*/
goto out;
}
if (!page_cache_get_speculative(page))
goto repeat;
/*
* Has the page moved?
* This is part of the lockless pagecache protocol. See
* include/linux/pagemap.h for details.
*/
if (unlikely(page != *pagep)) {
page_cache_release(page);
goto repeat;
}
}
out:
rcu_read_unlock();
return page;
}
可以看到在这个里面进行了radix tree的相关搜索操作,如果搜索的页并没有在高速缓存中,find_get_entry返回一个NULL。
继续回到pagecache_get_page()函数中,可以看到下面继续执行了如下操作:
如果没有找到page:
……
//分配页
page = __page_cache_alloc(gfp_mask);
……
//将其加入到页面调整缓存
err = add_to_page_cache_lru(page, mapping, offset,
gfp_mask & GFP_RECLAIM_MASK);
最后
以上就是明理帅哥为你收集整理的Linux页高速缓存内核处理函数,查找页的全部内容,希望文章能够帮你解决Linux页高速缓存内核处理函数,查找页所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复