頁高速緩存和頁回寫[1]

   有種欲哭無淚的感覺。又是一時失手,把原本寫的內容刷掉了。嗚嗚~~
  重新開始!!
  頁高速緩存(cache)是Linux內核實現的一種主要磁盤緩存。它主要用來減少對磁盤的IO操作。具體地講,就是通過把磁盤中的數據緩存到物理內存中,把對磁盤的訪問變爲對物理內存的訪問。
  磁盤緩存的優點在於:1,訪問磁盤的速度遠遠低於訪問內存的速度,因此從內存訪問數據要比從磁盤訪問速度快。2,數據一旦被訪問,就很有可能在短期內被再 次訪問到。這種在短期內集中訪問同一片數據的原理被稱作臨時局部原理temporal locality。臨時局部原理保證:如果在第一次訪問數據時緩存它,那就極有可能在短期內再次被高速緩存命中。
   頁高速緩存是由RAM中的物理頁組成,緩存中每一頁都對應着磁盤中的多個塊。每當內核開始執行一個頁IO操作時(通常對普通文件中頁大小的塊進行磁盤操作),首先會檢查需要的數據是否在高速緩存中,如果在,那麼內核就直接使用高速緩存中的數據,從而避免訪問磁盤。
   通過塊IO緩衝區把獨立的磁盤塊與頁高速緩存聯繫在一起。在塊IO層中,一個緩衝(buffer)就是一個單獨物理磁盤塊在內存中的表示,緩衝就是內存到 磁盤塊的映射描述符。因此通過磁盤塊以及緩衝塊IO操作,頁高速緩存同一可以減少塊IO操作期間的磁盤訪問量。這種緩存經常被稱爲“緩衝區高速緩存”,它 實際上並不是一個獨立的緩存,而是頁高速緩存的一部分。
  每次頁IO操作都要處理數據的全部頁面,就需要對一個以上的磁盤塊進行操作,所以頁高速緩存實際緩存的是頁面大小的文件塊。
  塊IO操作每次操作一個單獨的磁盤塊,比如讀寫索引節點就是一個典型的IO操作。內核提供bread()底層函數從磁盤讀單個塊,通過緩衝,這些磁盤塊被映射到內存中相應的頁面上,這樣就被緩存到頁高速緩存裏了。
  • 頁高速緩存
  頁高速緩存緩存的是頁面。緩存中的頁來自對正規文件、塊設備文件和內存映射文件的讀寫。這樣,頁高速緩存內就包含了最近被訪問的文件的全部頁面。
  一個物理頁可能由多個不連續的物理磁盤塊組成。因爲文件本身可能分佈在磁盤各個位置,所以頁面中映射的塊也不需要連續。這樣的話,在頁高速緩存中檢測特定數據是否已被緩存是件非常困難的事。因此不能用設備名稱和塊號來作頁高速緩存中數據的索引。
  Linux頁高速緩存的目標是緩存任何基於頁的對象,這包含各種類型的文件和各種類型的內存映射。Linux頁高速緩存使用address_space結構體描述頁高速緩存中的頁面。
  1. 在include/fs.h中:
  2. struct backing_dev_info;
  3. struct address_space {
  4.     struct inode        *host;      /* owner: inode, block_device */
  5.     struct radix_tree_root  page_tree;  /* radix tree of all pages */
  6.     rwlock_t        tree_lock;  /* and rwlock protecting it */
  7.     unsigned int        i_mmap_writable;/* count VM_SHARED mappings */
  8.     struct prio_tree_root   i_mmap;     /* tree of private and shared mappings */
  9.     struct list_head    i_mmap_nonlinear;/*list VM_NONLINEAR mappings */
  10.     spinlock_t      i_mmap_lock;    /* protect tree, count, list */
  11.     unsigned int        truncate_count; /* Cover race condition with truncate */
  12.     unsigned long       nrpages;    /* number of total pages */
  13.     pgoff_t         writeback_index;/* writeback starts here */
  14.     const struct address_space_operations *a_ops;   /* methods */
  15.     unsigned long       flags;      /* error bits/gfp mask */
  16.     struct backing_dev_info *backing_dev_info; /* device readahead, etc */
  17.     spinlock_t      private_lock;   /* for use by the address_space */
  18.     struct list_head    private_list;   /* ditto */
  19.     struct address_space    *assoc_mapping; /* ditto */
  20. } __attribute__((aligned(sizeof(long))));


  其中i_mmap字段是一個優先搜索樹,它的搜索範圍包含了在address_space中所有共享的與私有的映射頁面。優先搜索樹是一種巧妙的將堆和radix樹結合的快速檢索樹。
  1. 在linux/prio_tree.h中
  2. struct prio_tree_node {
  3.     struct prio_tree_node   *left;
  4.     struct prio_tree_node   *right;
  5.     struct prio_tree_node   *parent;
  6.     unsigned long       start;
  7.     unsigned long       last;   /* last location _in_ interval */
  8. };
  9. struct prio_tree_root {
  10.     struct prio_tree_node   *prio_tree_node;
  11.     unsigned short      index_bits;
  12.     unsigned short      raw;
  13.         /*
  14.          * 0: nodes are of type struct prio_tree_node
  15.          * 1: nodes are of type raw_prio_tree_node
  16.          */
  17. };
  優先搜索樹的具體實現在lib/prio.c中:
  1. static unsigned long index_bits_to_maxindex[BITS_PER_LONG];
  2. void __init prio_tree_init(void)
  3. {
  4.     unsigned int i;
  5.     for (i = 0; i < ARRAY_SIZE(index_bits_to_maxindex) - 1; i++)
  6.         index_bits_to_maxindex[i] = (1UL << (i + 1)) - 1;
  7.     index_bits_to_maxindex[ARRAY_SIZE(index_bits_to_maxindex) - 1] = ~0UL;
  8. }
  9. /*
  10.  * Insert a prio_tree_node @node into a radix priority search tree @root. The
  11.  * algorithm typically takes O(log n) time where 'log n' is the number of bits
  12.  * required to represent the maximum heap_index. In the worst case, the algo
  13.  * can take O((log n)^2) - check prio_tree_expand.
  14.  *
  15.  * If a prior node with same radix_index and heap_index is already found in
  16.  * the tree, then returns the address of the prior node. Otherwise, inserts
  17.  * @node into the tree and returns @node.
  18.  */
  19. struct prio_tree_node *prio_tree_insert(struct prio_tree_root *root,
  20.         struct prio_tree_node *node)
  21. {
  22.     struct prio_tree_node *cur, *res = node;
  23.     unsigned long radix_index, heap_index;
  24.     unsigned long r_index, h_index, index, mask;
  25.     int size_flag = 0;
  26.     get_index(root, node, &radix_index, &heap_index);
  27.     if (prio_tree_empty(root) ||
  28.             heap_index > prio_tree_maxindex(root->index_bits))
  29.         return prio_tree_expand(root, node, heap_index);
  30.     cur = root->prio_tree_node;
  31.     mask = 1UL << (root->index_bits - 1);
  32.     while (mask) {
  33.         get_index(root, cur, &r_index, &h_index);
  34.         if (r_index == radix_index && h_index == heap_index)
  35.             return cur;
  36.                 if (h_index < heap_index ||
  37.             (h_index == heap_index && r_index > radix_index)) {
  38.             struct prio_tree_node *tmp = node;
  39.             node = prio_tree_replace(root, cur, node);
  40.             cur = tmp;
  41.             /* swap indices */
  42.             index = r_index;
  43.             r_index = radix_index;
  44.             radix_index = index;
  45.             index = h_index;
  46.             h_index = heap_index;
  47.             heap_index = index;
  48.         }
  49.         if (size_flag)
  50.             index = heap_index - radix_index;
  51.         else
  52.             index = radix_index;
  53.         if (index & mask) {
  54.             if (prio_tree_right_empty(cur)) {
  55.                 INIT_PRIO_TREE_NODE(node);
  56.                 cur->right = node;
  57.                 node->parent = cur;
  58.                 return res;
  59.             } else
  60.                 cur = cur->right;
  61.         } else {
  62.             if (prio_tree_left_empty(cur)) {
  63.                 INIT_PRIO_TREE_NODE(node);
  64.                 cur->left = node;
  65.                 node->parent = cur;
  66.                 return res;
  67.             } else
  68.                 cur = cur->left;
  69.         }
  70.         mask >>= 1;
  71.         if (!mask) {
  72.             mask = 1UL << (BITS_PER_LONG - 1);
  73.             size_flag = 1;
  74.         }
  75.     }
  76.     /* Should not reach here */
  77.     BUG();
  78.     return NULL;
  79. }


  address_space結構體往往會和某些內核對象關聯。通常會與一個索引節點inode關聯,這時host字段就會指向該索引節點,如果關聯對象不是索引節點,host就會被設置爲NULL。
   a_ops字段指向地址空間對象中的操作函數表,這與VFS對象及其操作表關係類似:
  1. 在include/fs.h中

  2. /*
  3.  * oh the beauties of C type declarations.
  4.  */
  5. struct page;
  6. struct address_space;
  7. struct writeback_control;

  8. struct address_space_operations {
  9.     int (*writepage)(struct page *page, struct writeback_control *wbc);
  10.     int (*readpage)(struct file *, struct page *);
  11.     void (*sync_page)(struct page *);

  12.     /* Write back some dirty pages from this mapping. */
  13.     int (*writepages)(struct address_space *, struct writeback_control *);

  14.     /* Set a page dirty.  Return true if this dirtied it */
  15.     int (*set_page_dirty)(struct page *page);

  16.     int (*readpages)(struct file *filp, struct address_space *mapping,
  17.             struct list_head *pages, unsigned nr_pages);

  18.     /*
  19.      * ext3 requires that a successful prepare_write() call be followed
  20.      * by a commit_write() call - they must be balanced
  21.      */
  22.     int (*prepare_write)(struct file *, struct page *, unsigned, unsigned);
  23.     int (*commit_write)(struct file *, struct page *, unsigned, unsigned);
  24.     /* Unfortunately this kludge is needed for FIBMAP. Don't use it */
  25.     sector_t (*bmap)(struct address_space *, sector_t);
  26.     void (*invalidatepage) (struct page *, unsigned long);
  27.     int (*releasepage) (struct page *, gfp_t);
  28.     ssize_t (*direct_IO)(intstruct kiocb *, const struct iovec *iov,
  29.             loff_t offset, unsigned long nr_segs);
  30.     struct page* (*get_xip_page)(struct address_space *, sector_t,
  31.             int);
  32.     /* migrate the contents of a page to the specified target */
  33.     int (*migratepage) (struct address_space *,
  34.             struct page *, struct page *);
  35.     int (*launder_page) (struct page *);
  36. };

  readpage()和writerpage()兩個方法最主要。
  先看讀操作中包含的步驟:
1. 一個address_space對象和一個偏移量會被傳給readpage()方法,這兩個參數用來在頁高速緩存中搜索需要的數據,調用find_get_page()方法,
2. 如果搜索的頁面並沒有在高速緩存中,那麼內核將分配一個新的頁面,調用page_cache_alloc_cold()方法;然後將其加入到頁高速緩存中,調用add_to_page_lru()方法
3. 需要的數據從磁盤被讀入,在被加入到頁高速緩存中,然後返回給用戶:調用a_ops->readpage()方法
  寫操作和讀操作有少許不同。對於文件映射來說,當頁被修改了,VM僅僅需要調用
SetPageDirty(page);
  內核會在晚些時候通過writepage()方法把頁寫出。
  對特定文件的寫操作比較複雜,實現在mm/filemap.c中,包含以下步驟:
1. 調用__grab_cache_page()方法,在頁高速緩存中搜索需要的頁,如果需要的頁不在告訴緩存中,那麼內核在高速緩存分配一個空閒項
2. 調用a_ops->prepare_write()方法創建一個寫請求。
3. 調用filemap_copy_from_user()方法將數據從用戶空間拷貝到內核緩衝
4. 通過a_ops->commit_write()函數將數據寫入磁盤
  • 基樹
  在任何頁IO操作之前內核都要檢查頁是否已經在頁高速緩存中了,主要通過兩個參數(address_space對象和一個偏移量)進行查找。每個address_space對象都有惟一的基樹(radix tree),它保存在page_tree結構體中。
  基樹是一個二叉樹,只要指定了文件偏移量,就可以在基樹中迅速檢索到希望的數據。頁高速緩存的搜索函數find_get_page()要調用radix_tree_lookup()函數,該函數會在指定基樹中搜索到指定的頁面。
  基樹核心代碼的通用形式可在文件lib/radix-tree.c中找到。
  1. struct radix_tree_node {
  2.     unsigned int    height;     /* Height from the bottom */
  3.     unsigned int    count;
  4.     struct rcu_head rcu_head;
  5.     void        *slots[RADIX_TREE_MAP_SIZE];
  6.     unsigned long   tags[RADIX_TREE_MAX_TAGS][RADIX_TREE_TAG_LONGS];
  7. };

  8. struct radix_tree_path {
  9.     struct radix_tree_node *node;
  10.     int offset;
  11. };


  12. /*
  13.  * Radix tree node cache.
  14.  */
  15. static struct kmem_cache *radix_tree_node_cachep;


  16. void __init radix_tree_init(void)
  17. {
  18.     radix_tree_node_cachep = kmem_cache_create("radix_tree_node",
  19.             sizeof(struct radix_tree_node), 0,
  20.             SLAB_PANIC, radix_tree_node_ctor, NULL);
  21.     radix_tree_init_maxindex();
  22.     hotcpu_notifier(radix_tree_callback, 0);
  23. }


  24. /**
  25.  *  radix_tree_lookup    -    perform lookup operation on a radix tree
  26.  *  @root:      radix tree root
  27.  *  @index:     index key
  28.  *
  29.  *  Lookup the item at the position @index in the radix tree @root.
  30.  *
  31.  *  This function can be called under rcu_read_lock, however the caller
  32.  *  must manage lifetimes of leaf nodes (eg. RCU may also be used to free
  33.  *  them safely). No RCU barriers are required to access or modify the
  34.  *  returned item, however.
  35.  */
  36. void *radix_tree_lookup(struct radix_tree_root *root, unsigned long index)
  37. {
  38.     unsigned int height, shift;
  39.     struct radix_tree_node *node, **slot;

  40.     node = rcu_dereference(root->rnode);
  41.     if (node == NULL)
  42.         return NULL;

  43.     if (radix_tree_is_direct_ptr(node)) {
  44.         if (index > 0)
  45.             return NULL;
  46.         return radix_tree_direct_to_ptr(node);
  47.     }

  48.     height = node->height;
  49.     if (index > radix_tree_maxindex(height))
  50.         return NULL;

  51.     shift = (height-1) * RADIX_TREE_MAP_SHIFT;

  52.     do {
  53.         slot = (struct radix_tree_node **)
  54.             (node->slots + ((index>>shift) & RADIX_TREE_MAP_MASK));
  55.         node = rcu_dereference(*slot);
  56.         if (node == NULL)
  57.             return NULL;

  58.         shift -= RADIX_TREE_MAP_SHIFT;
  59.         height--;
  60.     } while (height > 0);

  61.     return node;
  62. }


  要想使用基樹,需要包含頭文件linux/radix_tree.h。
  1. /*** radix-tree API starts here ***/

  2. #define RADIX_TREE_MAX_TAGS 2

  3. /* root tags are stored in gfp_mask, shifted by __GFP_BITS_SHIFT */
  4. struct radix_tree_root {
  5.     unsigned int        height;
  6.     gfp_t           gfp_mask;
  7.     struct radix_tree_node  *rnode;
  8. };

  9. #define RADIX_TREE_INIT(mask)   {                   /
  10.     .height = 0,                            /
  11.     .gfp_mask = (mask),                     /
  12.     .rnode = NULL,                          /
  13. }

  14. #define RADIX_TREE(name, mask) /
  15.     struct radix_tree_root name = RADIX_TREE_INIT(mask)

  16. #define INIT_RADIX_TREE(root, mask)                 /
  17. do {                                    /
  18.     (root)->height = 0;                     /
  19.     (root)->gfp_mask = (mask);                  /
  20.     (root)->rnode = NULL;                       /
  21. while (0)

  在2.6版本之前,內核頁高速緩存不是通過基樹檢索,而是通過一個維護了系統中所有頁的全局散列表進行檢索。對於給定的同一個鍵值,該散列表會返回一個雙 向鏈表。如果需要的頁駐留在緩存中,那麼鏈表中的一項會與其對於。否則,頁就不在頁高速緩存中,散鏈函數返回NULL。
  • 緩衝區高速緩存
  在2.2版本的內核中,存在兩個獨立的磁盤緩存:頁高速緩存和緩衝區高速緩存。前者緩存頁,後者緩衝緩衝。一個磁盤塊可以在兩種緩存中同時存在,因此需要對兩個緩存中的同一拷貝進行很麻煩的同步操作。
  現在Linux中已經不再有獨立的緩衝區高速緩存了,只有唯一的磁盤緩存---頁高速緩存。內核仍然需要在內存中使用緩衝區來表示磁盤塊,緩衝是用頁映射塊的,它正好在頁高速緩存中。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章