參考





《Linux内核设计与实现》





*******************************************





页快速缓存是linux内核实现的一种主要磁盘缓存,它主要用来降低对磁盘的IO操作,详细地讲,是通过把磁盘中的数据缓存到物理内存中,把对磁盘的訪问变为对物理内存的訪问。为什么要这么做呢?一,速度;二暂时局部原理。有关这两个概念,相信熟悉操作系统的我们不会太陌生。页快速缓存是由RAM中的物理页组成的,缓存中的每一页都相应着磁盘中的多个块。每当内核開始运行一个页IO操作时,就先到快速缓存中找。这样就能够大大降低磁盘操作。

      一个物理页可能由多个不连续的物理磁盘块组成。也正是因为页面中映射的磁盘块不一定连续,所以在页快速缓存中检測特定数据是否已被缓存就变得不那么easy了。另外linux页快速缓存对被缓存页的范围定义的很宽。缓存的目标是不论什么基于页的对象,这包括各种类型的文件和各种类型的内存映射。为了满足普遍性要求,linux使用定义在linux/fs.h中的结构体address_space结构体描写叙述页快速缓存中的页面,例如以下:

struct address_space {
struct inode *host; /* owning inode */
struct radix_tree_root page_tree; /* radix tree of all pages */
spinlock_t tree_lock; /* page_tree lock */
unsigned int i_mmap_writable; /* VM_SHARED ma count */
struct prio_tree_root i_mmap; /* list of all mappings */
struct list_head i_mmap_nonlinear; /* VM_NONLINEAR ma list */
spinlock_t i_mmap_lock; /* i_mmap lock */
atomic_t truncate_count; /* truncate re count */
unsigned long nrpages; /* total number of pages */
pgoff_t writeback_index; /* writeback start offset */
struct address_space_operations *a_ops; /* operations table */
unsigned long flags; /* gfp_mask and error flags */
struct backing_dev_info *backing_dev_info; /* read-ahead information */
spinlock_t private_lock; /* private lock */
struct list_head private_list; /* private list */
struct address_space *assoc_mapping; /* associated buffers */
};

当中的i_mmap字段是一个优先搜索树,它的搜索范围包括了在address_sapce中私有的和共享的页面。nrpages反应了address_space空间的大小。address_space结构往往会和某些内核对象关联。通常情况下,会与一个索引节点(inode)关联,这时host域就会指向该索引节点。假设关联对象不是一个索引节点的话,比方address_space和swapper关联时,这是host域会被置为NULL。a_ops域指向地址空间对象中的操作函数表,这与VFS对象及其操作函数表关系类似,操作函数表定义在linux/fs.h中,由address_space_operations表示,例如以下:

struct address_space_operations {
int (*writepage)(struct page *, struct writeback_control *);
int (*readpage) (struct file *, struct page *);
int (*sync_page) (struct page *);
int (*writepages) (struct address_space *, struct writeback_control *);
int (*set_page_dirty) (struct page *);
int (*readpages) (struct file *, struct address_space *,struct list_head *, unsigned);
int (*prepare_write) (struct file *, struct page *, unsigned, unsigned);
int (*commit_write) (struct file *, struct page *, unsigned, unsigned);
sector_t (*bmap)(struct address_space *, sector_t);
int (*invalidatepage) (struct page *, unsigned long);
int (*releasepage) (struct page *, int);
int (*direct_IO) (int, struct kiocb *, const struct iovec *,loff_t, unsigned long);
};

background-color: rgb(255, 255, 255);">这里面最重要的两个就是readpage()与writepage()了。对于readpage()方法而言,首先,一个address_space对象和一个偏移量会被传给该方法,这两个參数用来在页快速缓存中搜素须要的数据:

page = find_get_page(mapping, index);

mapping是指定的地址空间,index是文件里的指定位置。假设要搜索的页并没在快速缓存中,那么内核将分配一个新页面,然后将其增加到页快速缓存中,例如以下
int error; cached_page = page_cache_alloc_cold(mapping);
if (!cached_page)
/* error allocating memory */
error = add_to_page_cache_lru(cached_page, mapping, index, GFP_KERNEL);
if (error)
/* error adding page to page cache */

最后,须要的数据从磁盘被读入,再被增加页快速缓存,然后返回给用户:error = mapping->a_ops->readpage(file,page);

      写操作和读操作有少许不同。对于文件映射来说,当页被改动了,VM只须要调用:setPageDirty(page);内核晚些时候通过writepage()方法把页写出。对特定文件的写操作会比較复杂----它的代码在文件mm/filemap.c中,通常写操作路径基本上要包括一下各步:

page = __grab_cache_page(mapping, index, &cached_page, &lru_pvec);
status = a_ops->prepare_write(file, page, offset, offset+bytes);
page_fault = filemap_copy_from_user(page, offset, buf, bytes);
status = a_ops->commit_write(file, page, offset, offset+bytes);

首先,在页快速缓存中搜索须要的页,假设须要的页不在快速缓存中,那么内核在快速缓存中新分配一空暇项;下一步,prepare_write()方法被调用,创建一个写请求;接着数据被从用户空间复制到内核缓冲;最后通过commit_write()函数将数据写入磁盘。





由于在不论什么页IO操作前内核都要检查页是否已经在页快速缓存中了,所以这样的检查必须迅速,高效。否则得不偿失了。前边已经说过,也快速缓存通过两个參数address_space对象和一个偏移量进行搜索。每一个address_space对象都有唯一的基树(radix tree),它保证在page_tree结构体中。基树是一个二叉树,仅仅要指定了文件偏移量,就能够在基树中迅速检索到希望的数据,页快速缓存的搜索函数find_get_

page()要调用函数radix_tree_lookup(),该函数会在指定基树中搜索指定页面。基树核心代码的通用形式能够在文件lib/radix-tree.c中找到,另外想要使用基树,须要包括头文件linux/radix_tree.h.





      在内存中累积起来的脏页必须被写回到磁盘,在一下两种情况下,脏页会被写会到磁盘:





1.在空暇内存低于一个特定的阈值时,内核必须将脏页写回磁盘,以便释放内存。

2.当脏页在内存中驻留超过一定的阈值时,内核必须将超时的脏页写会磁盘,以确保脏页不会无限期地驻留在内存中。

      如今你仅仅需知道,2.6内核中,使用pdflush后台回写例程来完毕这个工作。那么详细是怎么实现的呢:





首先,pdflush线程在系统中的空暇内存低于一个特定的阈值时,将脏页刷新回磁盘。该后台回写例程的目的在于在可用物理内存过低时,释放脏页以又一次获得内存。上面提到的特定的内存阈值能够通过dirty_background_ratio系统调用设置。一旦空暇内存比这个指小时,内核便会调用函数wakeup_bdflush() 唤醒一个pdflush线程,随后pdflush线程进一步调用函数background_writeout()開始将脏页写会到磁盘,函数background_writeout()须要一个长整型參数,该參数指定试图写回的页面数目。函数background_writeout会连续地写会数据,直到满足一下两个条件:





1.已经有指定的最小数目的页被写回到磁盘。

2.空暇内存页已经回升,超过了阈值dirty_background_ration.

      pdflush线程(实如今mm/pdflush.c中,回写机制的实现代码在文件mm/page-writeback.c和fs/fs-writeback.c中)周期地被唤醒而且把超过特定期限的脏页写回磁盘。系统管理员能够在/proc/sys/vm中设置回写相关的參数,也能够通过sysctl系统调用来设置它们。下表给出了能够设置的量:

Linux页快速缓存与回写机制分析的更多相关文章

  1. linux下数据同步、回写机制分析

    一.前言在linux2.6.32之前,linux下数据同步是基于pdflush线程机制来实现的,在linux2.6.32以上的版本,内核彻底删掉了pdflush机制,改为了基于per-bdi线程来实现 ...

  2. linux块设备的IO调度算法和回写机制

    ************************************************************************************** 參考: <Linux ...

  3. CDN 的缓存与回源机制解析

    CDN的缓存与回源机制解析 CDN (Content Delivery Network,即内容分发网络)指的是一组分布在各个地区的服务器.这些服务器存储着数据的副本,因此服务器可以根据哪些服务器与用户 ...

  4. Linux 3.2中回写机制的变革

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://alanwu.blog.51cto.com/3652632/1109952 wri ...

  5. rabbitMQ的简单实例——amqp协议带数据回写机制

    rabbitMQ是一种高性能的消息队列,支持或者说它实现了AMQP协议(advanced message queue protocol高级消息队列协议). 下面简单讲一讲一个小例子.我们首先要部署好r ...

  6. 《Linux内核设计与实现》读书笔记(十六)- 页高速缓存和页回写

    好久没有更新了... 主要内容: 缓存简介 页高速缓存 页回写 1. 缓存简介 在编程中,缓存是很常见也很有效的一种提高程序性能的机制. linux内核也不例外,为了提高I/O性能,也引入了缓存机制, ...

  7. Linux内核设计与实现 总结笔记(第十六章)页高速缓存和页回写

    页高速缓存是Linux内核实现磁盘缓存.磁盘告诉缓存重要源自:第一,访问磁盘的速度要远远低于访问内存. 第二,数据一旦被访问,就很有可能在短期内再次被访问到.这种短时期内集中访问同一片数据的原理称作临 ...

  8. 聊聊高并发(三十四)Java内存模型那些事(二)理解CPU快速缓存的工作原理

    在上一篇聊聊高并发(三十三)从一致性(Consistency)的角度理解Java内存模型 我们说了Java内存模型是一个语言级别的内存模型抽象.它屏蔽了底层硬件实现内存一致性需求的差异,提供了对上层的 ...

  9. linux页缓存

    2017-04-25 本节就聊聊页缓存这个东西…… 一.概述 页缓存是一个相对独立的概念,其根本目的是为了加速对后端设备的IO效率,比如文件的读写.页缓存顾名思义是以页为单位的,目前我能想到的在两个地 ...

随机推荐

  1. 【嵌入式开发】写入开发板Linux系统-模型S3C6410

    笔者 : 万境绝尘 转载请著名出处 最终拿到板子了, 嵌入式开发正式开启. 板子型号 : 三星 S3C6410 基于ARM11, 指令集基于arm6指令集; 为毛不是 Cortext A9的板子; 烧 ...

  2. 使用SQLCMD在SQLServer执行多个脚本

    原文:使用SQLCMD在SQLServer执行多个脚本 概述: 作为DBA,经常要用开发人员提供的SQL脚本来更新正式数据库,但是一个比较合理的开发流程,当提交脚本给DBA执行的时候,可能已经有几百个 ...

  3. c语言实现hashtable,相似C++的map和iOS的NSDictionary

    跟线性数组和链表不同.HashTable是高速查找的数据结构.本文中的HashTable使用链表处理数组. 该HashTable能够指定table的长度.提供了遍历的方法. 包含table的长度的选择 ...

  4. 分布式数据库中间件–(2) Cobar与client握手身份验证

    Cobar启动完毕,监听特定端口.整个认证的流程图: NIOAcceptor类继承自Thread类,该类的对象会以线程的方式执行,进行连接的监听. NIOAcceptor启动的初始化步骤例如以下: 1 ...

  5. js 面向对象选项卡

      <!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" conte ...

  6. Partitioner分区过程分析

    Partition中国人意味着分区,意义的碎片,这个阶段也是整个MapReduce该过程的第三阶段.在Map返回任务,是使key分到通过一定的分区算法.分到固定的区域中.给不同的Reduce做处理,达 ...

  7. 用Ghostscript API将PDF格式转换为图像格式(C#)

    原文:用Ghostscript API将PDF格式转换为图像格式(C#) 由于项目需要在.net下将pdf转换为普通图像格式,在网上搜了好久终于找到一个解决方案,于是采用拿来主义直接用.来源见代码中注 ...

  8. AndroidUI组件之TabHost

    package com.gc.tabhost; /** * @author Android将军 * * * * 1.TabHost是一种非常有用的组件,TabHost能够非常方便地在窗体上放置 * 多 ...

  9. C# LDAP 管理(创建新用户)

    今天用C#实现了一套LDAP域账号的创建和查询,感受挺多. 算是第一次接触LDAP吧,之前曾经做了一个登录的验证,就是查询功能,那个相对比较简单,用到了一个方法就搞定了. 这次的需求是要用编程的方式创 ...

  10. 添加AD验证(域身份验证)到现有网站

    每个网站几乎都会有用户登录的模块,登录就会涉及到身份验证的过程.通常的做法是在页面上有个登录的Form,然后根据用户名和密码到数据库中去进行验证. 而验证后如何在网站的各个页面维持这种认证过的状态,有 ...