zoukankan      html  css  js  c++  java
  • Page Cache(页缓存)

    Page Cache

    • 由内存中的物理page组成,其内容对应磁盘上的block。
    • page cache的大小是动态变化的。
    • backing store: cache缓存的存储设备
    • 一个page通常包含多个block, 而block不一定是连续的。

    读Cache

    • 当内核发起一个读请求时, 先会检查请求的数据是否缓存到了page cache中。

      • 如果有,那么直接从内存中读取,不需要访问磁盘, 此即 cache hit(缓存命中)
      • 如果没有, 就必须从磁盘中读取数据, 然后内核将读取的数据再缓存到cache中, 如此后续的读请求就可以命中缓存了。
    • page可以值缓存一个文件的部分内容, 而不需要吧整个文件都缓存进来。

    写Cache

    • 当内核发起一个写请求时, 也是直接往cache中写入, 后备存储中的内容不会直接更新。
    • 内核会将被写入的page标记为dirty, 并将其加入到dirty list中。
    • 内核会周期性地将dirty list中的page写回到磁盘上, 从而使磁盘上的数据和内存中缓存的数据一致。

    Cache回收

    • Page cache的另一个重要工作是释放page, 从而释放内存空间。
    • cache回收的任务是选择合适的page释放
      • 如果page是dirty的, 需要将page写回到磁盘中再释放。

    LRU算法

    • LRU(Least Recently used): 最近最少使用算法, Redis中也有此策略, 该算法在Java中可以使用LinkedHashMap进行实现。

    Two-List策略

    • Two-List策略维护了两个list: active list && inactive list
      • 在active list上的page被认为是hot的,不能释放
      • 只有inactive list上的page可以被释放的。
    • 首次缓存的数据的page会被加入到inactive list中,已经在inactive list中的page如果再次被访问,就会移入active list中。
    • 两个链表都使用了伪LRU算法维护,新的page从尾部加入,移除时从头部移除
    • 如果active list中page的数量远大于inactive list,那么active list头部的页面会被移入inactive list中,从而实现两个表的平衡。

    Linux中Page Cache的具体实现

    • 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 */
      };
      
      
      • 其中 host域指向对应的inode对象,host有可能为NULL,这意味着这个address_space不是和一个文件关联,而是和swap area相关,swap是Linux中将匿名内存(比如进程的堆、栈等,没有一个文件作为back store)置换到swap area(比如swap分区)从而释放物理内存的一种机制。page_tree保存了该page cache中所有的page,使用基数树(radix Tree)来存储。i_mmap是保存了所有映射到当前page cache(物理的)的虚拟内存区域(VMA)。nrpages是当前address_space中page的数量。
    • address_space操作函数

      • address_space中的a_ops域指向操作函数表(struct address_space_operations),每个后备存储都要实现这个函数表。
    • 内核使用函数表中的函数管理page cache,其中最重要的两个函数是readpage() 和writepage()

      • readpage()函数
        • readpage()首先会调用find_get_page(mapping, index)在page cache中寻找请求的数据
        • mapping是要寻找的page cache对象,即address_space对象
        • index是要读取的数据在文件中的偏移量
        • 如果请求的数据不在该page cache中,那么内核就会创建一个新的page加入page cache中,并将要请求的磁盘数据缓存到该page中,同时将page返回给调用者。
      • writepage()函数
        • 对于文件映射, page每次修改后都会调用SetPageDirty(page)将page标识为dirty。
        • 内核首先在指定的address_space寻找目标page
          • 如果没有,就分配一个page并加入到page cache中,然后内核发起一个写请求将数据从用户空间拷入内核空间,最后将数据写入磁盘中。

    Buffer Cache

    • 用于表示内存到磁盘映射的buffer_head结构

    • 每个buffer-block映射都有一个buffer_head结构,buffer_head中的b_assoc_map指向了address_space。

    • 值得注意的是在Linux2.4中,buffer cache和 page cache之间是独立的

      • 前者使用老版本的buffer_head进行存储,这导致了一个磁盘block可能在两个cache中同时存在,造成了内存的浪费
      • 2.6内核中将两者合并到了一起,使buffer_head只存储buffer-block的映射信息,不再存储block的内容, 从而减少了内存浪费。

    Flusher Threads

    • Page Cache推迟了文件写入后备存储的时间, 但是dirty page最终还是要被写回磁盘的
    • 内核会在以下三种情况下将dirty page 写回磁盘:
      • 用户进程调用sync() 和 fsync()系统调用
      • 空闲内存低于特定的阈值(threshold)
      • Dirty数据在内存中驻留的时间超过一个特定的阈值
    • 线程群的特点是让一个线程负责一个存储设备(比如一个磁盘驱动器),多少个存储设备就用多少个线程, 从而避免阻塞或者竞争的情况,提高效率。
    • 当空闲内存低于阈值时,内核就会调用wakeup_flusher_threads()来唤醒一个或者多个flusher线程,将数据写回磁盘。
    • 为了避免dirty数据在内存中驻留过长时间(避免在系统崩溃时丢失过多数据),内核会定期唤醒一个flusher线程,将驻留时间过长的dirty数据写回磁盘。
  • 相关阅读:
    003 Rabbitmq的简单队列
    002 Rabbitmq的安装
    001 消息中间件--Rabbitmq
    013 mybatis整合
    013 Durid监控
    012 druid数据源
    011 嵌入式容器
    集成支付宝IOS
    2015起航,
    python 创建用户
  • 原文地址:https://www.cnblogs.com/ronnieyuan/p/12377059.html
Copyright © 2011-2022 走看看