zoukankan      html  css  js  c++  java
  • Linux内核设计与实现 总结笔记(第十六章)页高速缓存和页回写

    页高速缓存是Linux内核实现磁盘缓存。磁盘告诉缓存重要源自:第一,访问磁盘的速度要远远低于访问内存。

    第二,数据一旦被访问,就很有可能在短期内再次被访问到。这种短时期内集中访问同一片数据的原理称作临时局部原理。

    一、缓存手段

    1.1 写缓存

    通常来讲缓存一般实现成三种策略:

    ①不缓存

    ②写操作将自动更新内存缓存

    ③Linux采用的回写。程序写进缓存

    1.2 缓存回收

    1.最近少用原则:简称LRU,LRU回收需要跟踪每个页面的访问踪迹,以便能回收最老时间戳的页面。

    2.双链策略:Linux实现的是一个修改过的LRU,也称为双链策略。Linux维护两个链表:活跃链表和非活跃链表。处于活跃链表是不会被换出的,而在非活跃链表上则是可以被换出的。这种链表方式也称作LUR/2,更普遍的是n个链表,故称LRU/n

    二、Linux页高速缓存

    高速缓存缓存的是内存页面,缓冲中的也来自对正规文件、块设备文件和内存映射文件的读写。

    2.1 address_space对象

    为了维持页高速缓存的普遍性,Linux页高速缓存使用了一个新对象管理缓存项和页I/O操作。address_space有点不确切,应该叫他page_cache_entity或者physical_page_of_a_file比较好。该结构定义在文件<linux/fs.h>中。

    struct address_space {
        struct inode        *host;        /* owner: inode, block_device 拥有节点*/
        struct radix_tree_root    page_tree;    /* radix tree of all pages 包含全部页面的radix树*/
        spinlock_t        tree_lock;    /* and lock protecting it 保护page_tree自旋锁*/
        atomic_t        i_mmap_writable;/* count VM_SHARED mappings VM_SHARED计数*/
        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 gfp_mask掩码与错误标识*/
        spinlock_t        private_lock;    /* for use by the address_space 私有address_space锁*/
        struct list_head    private_list;    /* ditto 私有address_space链表*/
        void            *private_data;    /* ditto */
    } __attribute__((aligned(sizeof(long))))
    struct address_space

    2.2 address_space操作

    address_space_operations定义在文件<linux/fs.h>中

    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);
    };
    struct address_space_operations

     里面readpage()和writepage()两个方法最为重要,其中读操作会包括如下动作:

    Linux内核试图在页高速缓存中找到需要的数据,find_get_page()方法负责完成这个动作检查。一个address_space对象和一个偏移量会传给find_get_page()方法,用于在页高速缓存中搜索需要的数据。

    page = find_get_page(mapping, index);

    mapping:指定的地址空间,index:文件中的指定位置,以页面为单位。

    如果搜索页没在高速缓存中返回NULL,内核将分配一个新页面。

    struct page *page;
    int error;
    
    /* 分配页... */
    page = page_cache_alloc_cold(mapping);
    if(!page)
        /* 内存分配出错 */
    
    /* ...然后将其加入到页面调整缓存 */
    error = add_to_page_cache_lru(page, mapping, index, GFP_KERNEL);
    if(error)
        /* 页面被加入到页面高速缓存时,出错 */
    
    /* 需要的数据从磁盘被读入,再被加入页高速缓存,然后返回给用户 */
    error = mapping->a_ops->readpage(file, page);
    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);
    写出

    2.3 基数

    每个address_space都有唯一的基树(radix tree),保存在page_tree。基树是二叉树,指定了文件偏移量,就可以在基树中迅速检索到希望的页。

    find_get_page()要调用函数radix_tree_lookup()。基树核心代码的通用形式可以在文件lib/radix-tree.c中找到,想要使用基树,需要头文件<linux/radix_tree.h>

    2.4 以前的页散列表

    全局散列表主要的存在四个问题:

    • 由于使用单个的全局锁保护散列表,所以即使在中等规模的机器中,锁的征用情况也会相当严重,造成性能受损。
    • 由于散列表需要包含所有也高速缓存中的页,搜索需要的只是当前文件相关的那些页,所以散列表包含的页面相比搜索需要的页面要大的多。
    • 如果散列搜索失败,执行速度比希望的要慢的多,这是因为检索必须遍历指定散列键值对应的整个链表
    • 散列表比其他方法会消耗更多的内存

     2.6版本内核引入基于基树的页高速缓存来解决这些问题。

    三、缓冲区高速缓存

    独立的磁盘块通过块I/O缓冲也要被存入页高速缓存。 因为它缓存磁盘块和减少块I/O操作,这个缓存童话参观称为缓冲区高速缓存,虽然没有独立缓存,而是作为页高速缓存的一部分。

    块I/O操作一次操作一个单独的磁盘块

    四、flusher线程

    当页高速缓存中的数据比后台存储的数据更新时,该数据就称作脏数据。在内存中积累的脏页最终必须被写回磁盘。3中情况脏页写回磁盘:

    • 当空闲内存低于一个特定的阈值时,内核必须将脏页写回磁盘以便释放内存。(只有干净的内存才可以被回收)
    • 当脏页在内存中驻留时间超过一个特定的阈值时,内核必须将超时的脏页写回磁盘。
    • 当用户进程调用sync()和fsync()系统调用时,内核会按要求执行回写动作。

    flusher线程实现代码在文件mm/page-writeback.c和mm/backing-dev.c中,回写机制的实现代码在文件fs/fs-writeback.c中。

    4.1 膝上型计算机模式

    4.2 历史上bdflush、kupdated和pdflush

    4.3 避免堵塞的方法:使用多线程

    内核通过使用多个flusher线程来解决上述问题。每个线程可以互相独立地将脏页刷新回磁盘,而且不同的flusher线程处理不同的设备队列。

  • 相关阅读:
    深度学习中的特征(feature)指的是什么?
    随机权值平均的原理解释
    第04组 Beta冲刺(4/4)
    2019 SDN上机第7次作业
    第04组 Beta冲刺(3/4)
    第04组 Beta冲刺(2/4)
    第04组 Beta冲刺(1/4)
    2019 SDN上机第6次作业
    2019 SDN上机第5次作业
    SDN课程阅读作业(2)
  • 原文地址:https://www.cnblogs.com/ch122633/p/11900987.html
Copyright © 2011-2022 走看看