页高速缓存(一)

 一、 页高速缓存
页高速缓存是Linux内核使用的主要磁盘高速缓存。在大多数情况下,内核在读写磁盘的时候都会引用页高速缓存。如果页不在高速缓存中,新的页就会回到高速缓存中,然后从磁盘中读出数据来填充它。如果内存有足够的空闲空间,会让该页长期保留在高速缓存中,以提高访问磁盘的速度。
同样,把数据写到磁盘上也是一样的,如果该数据对应的页在高速缓存中,就可以直接写回磁盘,如果不在,就增加新页,然后写回磁盘,在下次写回磁盘就回提高写回的效率。
在页高速缓存中的文件有下面几种
(1) 普通文件的数据页。(2) 含有目录的页。(3) 直接从块设备文件读出的数据的页。(4) 用户态进程数据的页,但页中的数据已经被交换到磁盘上。(5) 属于特殊文件系统文件的页。
内核设计页高速缓存有两种需要:
(1)快速定位含有给定所有者相关数据的特定页。
(2)记录在读或写页中的数据时应当如何处理高速缓存中的每个页。
在页高速缓存中,它缓存的是一个完整的数据页。一个页中包含的磁盘块在物理上不一定是相邻的,所以不能用设备号和块号来识别,而是通过页的所有者和所有者数据中的索引来识别页高速缓存中的页。
二、 页高速缓存的address_space对象
它是嵌入在页所有都的索引节点对象中的数据,高速缓存中的许多页可能属于同一个所有者,从而可能被链接到同一个address_space对象。同时它可以在所有者的页和对这些页的操作之间建立起链接关系。
在address_space对象中,mapping字段指向拥有页的索引节点的address_space对象,index字段表示在所有都的地址空间中以页大小为单位的偏移量,也就是在所有者的磁盘映像中页中数据的位置。看一下address_space的数据结构

01struct address_space {
02 struct inode  *host;  /* owner: inode, block_device */
03 struct radix_tree_root page_tree; /* radix tree of all pages */
04 spinlock_t  tree_lock; /* and lock protecting it */
05 unsigned int  i_mmap_writable;/* count VM_SHARED mappings */
06 struct prio_tree_root i_mmap;  /* tree of private and shared mappings */
07 struct list_head i_mmap_nonlinear;/*list VM_NONLINEAR mappings */
08 spinlock_t  i_mmap_lock; /* protect tree, count, list */
09 unsigned int  truncate_count; /* Cover race condition with truncate */
10 unsigned long  nrpages; /* number of total pages */
11 pgoff_t   writeback_index;/* writeback starts here */
12 const struct address_space_operations *a_ops; /* methods */
13 unsigned long  flags;  /* error bits/gfp mask */
14 struct backing_dev_info *backing_dev_info; /* device readahead, etc */
15 spinlock_t  private_lock; /* for use by the address_space */
16 struct list_head private_list; /* ditto */
17 struct address_space *assoc_mapping; /* ditto */
18} __attribute__((aligned(sizeof(long))));

第2行指向拥有该对象的索引节点的指针
第3行表示拥有都页的基树的根
第4行保持基树的自旋锁
第5行地址空间中共享内存映射的个数
第6行radix优先搜索树的根
第7行地址空间中非线性内存区的链表
第8行保护radix优先搜索树的自旋锁
第9行截断文件时使用的顺序计数器
第10行所有者的页进行操作方法
第11行错误位和内存分配器的标志
第12行指向拥有所有者数据的块设备的backing_dev_info的指针
第13行通常是管理private_list链表时使用的自旋锁
第14行通常是与索引节点相关的间接块的脏缓冲区的链表。
第15行通常是指向间接块所在块设备的address_space对象的指针
如果页高速缓存中页的所有者是一个文件,address_space对象就嵌入在VFS索引节点对象的i_data字段中,索引节点的i_mapping字段总是指向索引节点的数据页所有者的address_space对象。address_space对象的host字段指向其所有者的索引节点对象。如果页中包含的数据来自块设备文件,那么address_space对象嵌入到与该块设备相关的特殊文件系统bdev中文件的”主”索引节点中。
address_space对象的方法

struct address_space_operations {
01 int (*writepage)(struct page *page, struct writeback_control *wbc);
02 int (*readpage)(struct file *, struct page *);
03 void (*sync_page)(struct page *);
04
05 /* Write back some dirty pages from this mapping. */
06 int (*writepages)(struct address_space *, struct writeback_control *);
07
08 /* Set a page dirty.  Return true if this dirtied it */
09 int (*set_page_dirty)(struct page *page);
10
11 int (*readpages)(struct file *filp, struct address_space *mapping,
12   struct list_head *pages, unsigned nr_pages);
13
14 int (*write_begin)(struct file *, struct address_space *mapping,
15    loff_t pos, unsigned len, unsigned flags,
16    struct page **pagep, void **fsdata);
17 int (*write_end)(struct file *, struct address_space *mapping,
18    loff_t pos, unsigned len, unsigned copied,
19    struct page *page, void *fsdata);
20
21 /* Unfortunately this kludge is needed for FIBMAP. Don't use it */
22 sector_t (*bmap)(struct address_space *, sector_t);
23 void (*invalidatepage) (struct page *, unsigned long);
24 int (*releasepage) (struct page *, gfp_t);
25 ssize_t (*direct_IO)(int, struct kiocb *, const struct iovec *iov,
26   loff_t offset, unsigned long nr_segs);
27 int (*get_xip_mem)(struct address_space *, pgoff_t, int,
28      void **, unsigned long *);
29 /* migrate the contents of a page to the specified target */
30 int (*migratepage) (struct address_space *,
31   struct page *, struct page *);
32 int (*launder_page) (struct page *);
33 int (*is_partially_uptodate) (struct page *, read_descriptor_t *,
34     unsigned long);
35};

第1行写操作
第2行读操作
第3行如果对所有者页进行的操作已准备好,则立刻开始I/O数据传输
第6行把指定数量的所有者脏页写回磁盘
第9行把所有者的页设置为脏页
第11行从磁盘中读所有者页的链表
第22行从文件块索引中获取逻辑块号
第23行使所有者的页无效
第24行由日志文件系统使用以准备释放页
第25行所有者页直接I/O传输
为了加快数据页的查找,内核引入了基树,当然也可以用链表和哈希表进行查找,没有基树的效率高。
三. 基树
基树可以查看相关资料
四. 页高速缓存的处理函数
(1) 查找页find_get_page函数

struct page *find_get_page(struct address_space *mapping, pgoff_t offset)
{
01 void **pagep;
02 struct page *page;
03
04 rcu_read_lock();
05repeat:
06 page = NULL;
07 pagep = radix_tree_lookup_slot(&mapping->page_tree, offset);
08 if (pagep) {
09  page = radix_tree_deref_slot(pagep);
10  if (unlikely(!page || page == RADIX_TREE_RETRY))
11   goto repeat;
12
13  if (!page_cache_get_speculative(page))
14   goto repeat;
15
16  /*
17   * Has the page moved?
18   * This is part of the lockless pagecache protocol. See
19   * include/linux/pagemap.h for details.
20   */
21  if (unlikely(page != *pagep)) {
22   page_cache_release(page);
23   goto repeat;
24  }
25 }
26 rcu_read_unlock();
27
28 return page;
29}

这个函数接收的参数为指向address_space对象的指针和偏移量。它获取地址空间的自旋锁,调用radix_tree_lookup()函数搜索拥有指定偏移量的基树的叶子节点。
第7行查找基树的slot,返回slot相应的位置在基树中。
第9行对一个页的引用
第22行减小对页的引用
第26行释放读写锁
(2) 增加页add_to_page_cache()函数

static inline int add_to_page_cache(struct page *page,
  struct address_space *mapping, pgoff_t offset, gfp_t gfp_mask)
{
1 int error;
2 __set_page_locked(page);
3 error = add_to_page_cache_locked(page, mapping, offset, gfp_mask);
4 if (unlikely(error))
5  __clear_page_locked(page);
6 return error;
7}

第2行锁定该页
第3行添加锁定的页到页高速缓存中
第5行进行解锁该页

int add_to_page_cache_locked(struct page *page, struct address_space *mapping,
  pgoff_t offset, gfp_t gfp_mask)
{
01 int error;
02
03 VM_BUG_ON(!PageLocked(page));
04
05 error = mem_cgroup_cache_charge(page, current->mm,
06     gfp_mask & GFP_RECLAIM_MASK);
07 if (error)
08  goto out;
09
10 error = radix_tree_preload(gfp_mask & ~__GFP_HIGHMEM);
11 if (error == 0) {
12  page_cache_get(page);
13  page->mapping = mapping;
14  page->index = offset;
15
16  spin_lock_irq(&mapping->tree_lock);
17  error = radix_tree_insert(&mapping->page_tree, offset, page);
18  if (likely(!error)) {
19   mapping->nrpages++;
20   __inc_zone_page_state(page, NR_FILE_PAGES);
21   spin_unlock_irq(&mapping->tree_lock);
22  } else {
23   page->mapping = NULL;
24   spin_unlock_irq(&mapping->tree_lock);
25   mem_cgroup_uncharge_cache_page(page);
26   page_cache_release(page);
27  }
28  radix_tree_preload_end();
29 } else
30  mem_cgroup_uncharge_cache_page(page);
31out:
32 return error;
33}

第10行radix_tree_preload函数,它禁用内核抢占,并把一些空的radix_tree_node结构赋给每cpu变量radix_tree_preloads.。radix_tree_node结构的分配由slab分配器高速缓存radix_tree_node_cachep来完成。如果radix_tree_preload()预分配radix_tree_node结构不成功,函数add_to_page_cache()就终止并返回错误。
第16行获取mapping->tree_lock自旋锁。
第17行在树中插入新节点,
第19行递增在地址空间所缓存页的计数器
第21行释放地址空间的自旋锁
第28行重新启用内核抢占
(3) 删除页

void remove_from_page_cache(struct page *page)
{
01 struct address_space *mapping = page->mapping;
02
03 BUG_ON(!PageLocked(page));
04
05 spin_lock_irq(&mapping->tree_lock);
06 __remove_from_page_cache(page);
07 spin_unlock_irq(&mapping->tree_lock);
08 mem_cgroup_uncharge_cache_page(page);
09}

第5行锁定该页
第6行从页高速缓存中删除该页
第7行进行解锁该页

void __remove_from_page_cache(struct page *page)
{
01 struct address_space *mapping = page->mapping;
02
03 radix_tree_delete(&mapping->page_tree, page->index);
04 page->mapping = NULL;
05 mapping->nrpages--;
06 __dec_zone_page_state(page, NR_FILE_PAGES);
07 BUG_ON(page_mapped(page));
08
09 /*
10  * Some filesystems seem to re-dirty the page even after
11  * the VM has canceled the dirty bit (eg ext3 journaling).
12  *
13  * Fix it up by doing a final dirty accounting check after
14  * having removed the page entirely.
15  */
16 if (PageDirty(page) && mapping_cap_account_dirty(mapping)) {
17  dec_zone_page_state(page, NR_FILE_DIRTY);
18  dec_bdi_stat(mapping->backing_dev_info, BDI_RECLAIMABLE);
19 }
20}

第3行从树中删除节点。该函数接收树根的地址和要删除的页索引作为参数。
第4行把page->mapping字段置为空
第5行把所缓存的mapping->nrpages计数器的值减1
(4) 更新页 read_cache_page()函数

struct page *read_cache_page(struct address_space *mapping,
    pgoff_t index,
    int (*filler)(void *,struct page*),
    void *data)
{
01 struct page *page;
02
03 page = read_cache_page_async(mapping, index, filler, data);
04 if (IS_ERR(page))
05  goto out;
06 wait_on_page_locked(page);
07 if (!PageUptodate(page)) {
08  page_cache_release(page);
09  page = ERR_PTR(-EIO);
10 }
11 out:
12 return page;
13}.
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值