Windows XP Windows 7 Windows 2003 Windows Vista Windows教程綜合 Linux 系統教程
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟件教程
 Windows教程網 >> Linux系統教程 >> Linux教程 >> Linux操作系統緩存機制之頁緩存

Linux操作系統緩存機制之頁緩存

日期:2017/2/7 9:23:00      編輯:Linux教程

Linux運用一個功能廣泛的緩沖和緩存框架來提高系統的速度。緩沖和緩存利用一部分系統物理內存,確保最重要、最常使用的塊設備數據在操作時可直接從主內存獲取,而無需從低速設備讀取。物理內存還用於存儲從快設備讀取的數據,使得隨後對該數據的訪問可直接在物理內存進行,而無需從外部設備再次取用。考慮系統中多種因素然後延遲寫回在總體上改進了系統的性能。前面分析的部分,例如內存管理的slab緩存是一個內存到內存的緩存,其目地不是加速對低速設備的操作,而是對現有資源進行更簡單、更高效的使用。文件系統的Dentry緩存也用於減少對低速塊設備的訪問,但他無法推廣到通用場合,因為他是專門用於處理單一數據類型的。內核為塊設備提供了兩種通用的緩存方案:1) 頁緩存,針對以頁為單位的所有操作,並考慮了特定體系結構上的頁長度。一個主要的例子是內存映射技術。因為其他類型的文件訪問也是基於內核中的這一技術實現的。所以頁緩存實際上負責了塊設備的大部分緩存工作。2) 塊緩存,以塊為操作單位。在進行I/O操作時,存取的單位是設備的各個塊,而不是整個內存頁。盡管頁長度對所有文件系統都是相同的,但塊長度取決於特定的文件系統或其設置。因而,塊緩存必須能夠處理不同長度的塊。目前用於塊傳輸的標准數據結構已經演變為struct bio.用這種方式進行塊傳輸更為高效,因為他可以合並同一請求中後續的塊,加速處理的進行。在許多場合下,頁緩存和塊緩存是聯合使用的。例如,一個緩存的頁在寫操作期間可以劃分為不同的緩沖區,這樣可以在更細的力度下,識別出頁被修改的部分。好處在於,在將數據寫回時,只需要回寫被修改的部分,無需將這個頁面傳輸回底層的塊設備。

  頁面緩存結構[cpp] view plaincopyprint?

  /*高速緩存的核心數據結構,對塊設備的讀寫操作都放在該結構體裡*/ structaddress_space { /*與地址空間所管理的區域之間的關聯數據結構之一inode結構指定了後備存儲器*/ struct inode *host; /* owner: inode, block_device */ /*與地址空間所管理的區域之間的關聯之二,page_tree列出了地址空間中所有的物理內存頁*/ struct radix_tree_root page_tree; /* radix tree of all pages */ spinlock_t tree_lock; /* and lock protecting it */ /*所有用VM_SHARED屬性創建的映射*/ unsigned int i_mmap_writable;/* count VM_SHARED mappings */ /*基數根節點,該樹包含了與該inode相關的所有普通內存映射。該樹的任務在於,支持查找包含了給定區間中至少一頁的所有內存區域*/ struct prio_tree_root i_mmap; /* tree of private and shared mappings */ /*包含所有在非線性映射中的頁*/ struct list_head i_mmap_nonlinear;/*list VM_NONLINEAR mappings */ spinlock_t i_mmap_lock; /* protect tree, count, list */ unsigned int truncate_count; /* Cover race condition with truncate */ /*緩存頁的總數*/ unsigned long nrpages; /* number of total pages */ pgoff_t writeback_index;/* writeback starts here */ const struct address_space_operations *a_ops; /* methods */ /*集主要用於保存映射頁所來自的GFP內存區的有關信息*/ unsigned long flags; /* error bits/gfp mask */ /*指向後備存儲器結構,該結構包含了與地址空間相關的後備存儲器的有關信息,後備存儲器是指與地址空間相關的外部設備,用做地址空間中信息的來源。他通常是塊設備*/ struct backing_dev_info *backing_dev_info; /* device readahead, etc */ spinlock_t private_lock; /* for use by the address_space */ /*用於將包含文件系統元數據(通常是間接塊)的buffer_head實例彼此連接起來*/ struct list_head private_list; /* ditto */ /*指向相關的地址空間的指針*/ struct address_space *assoc_mapping; /* ditto */ } __attribute__((aligned(sizeof(long))));後備存儲信息[cpp] view plaincopyprint?

  struct backing_dev_info { struct list_head bdi_list;struct rcu_head rcu_head;/*最大預讀數量,單位為PAGE_CACHE_SIZE*/ unsigned long ra_pages; /* max readahead in PAGE_CACHE_SIZE units */ /*對該成員,總是使用原子操作,指定了後備存儲器的狀態*/ unsigned long state; /* Always use atomic bitops on this */ /*設備能力*/ unsigned int capabilities; /* Device capabilities */ congested_fn *congested_fn; /* Function pointer if device is md/dm */ void *congested_data; /* Pointer to aux data for congested func */ void (*unplug_io_fn)(struct backing_dev_info *, struct page *);void *unplug_io_data;

  char *name;

  struct percpu_counter bdi_stat[NR_BDI_STAT_ITEMS];

  struct prop_local_percpu completions;int dirty_exceeded;

  unsigned int min_ratio;unsigned int max_ratio, max_prop_frac;

  struct bdi_writeback wb; /* default writeback info for this bdi */ spinlock_t wb_lock; /* protects update side of wb_list */ struct list_head wb_list; /* the flusher threads hanging off this bdi */ unsigned long wb_mask; /* bitmask of registered tasks */ unsigned int wb_cnt; /* number of registered tasks */

  struct list_head work_list;

  struct device *dev;

  #ifdef CONFIG_DEBUG_FS struct dentry *debug_dir;struct dentry *debug_stats;#endif };下圖為地址空間與內核其他部分的關聯。

  內核采用一種通用的地址空間方案,來建立緩存數據與其來源之間的關聯。

  1) 內存中的頁分配到每個地址空間。這些頁的內容可以由用戶進程或內核本身使用各式各樣的方法操作。這些數據表示了緩存中的內容;2) 後備存儲器struct backing_dev_info指定了填充地址空間中頁的數據的來源。地址空間關聯到處理器的虛擬地址空間,是由處理器在虛擬內存中管理的一個區域到設備device上對應位置之間的一個映射。

  如果訪問了虛擬內存中的某個位置,該位置沒有關聯到物理內存頁,內核可根據地址空間結構來找到讀取數據的來源。

  為支持數據傳輸,每個地址空間都提供了一組操作,以容許地址空間所涉及雙方面的交互。

  地址空間是內核中最關鍵的數據結構之一,對該數據結構的管理,已經演變為內核面對的最關鍵的問題之一。 頁緩存的任務在於,獲得一些物理內存頁,以加速在塊設備上按頁為單位執行的操作。

  內核使用了基數樹來管理與一個地址空間相關的所有頁,以便盡可能降低開銷。對於基數樹的理解在這裡就不分析了,後面有空的時候再做分析。

  地址空間操作[cpp] view plaincopyprint?

  struct address_space_operations { /*將地址空間的一頁或多頁寫回到底層設備這是通過向塊層發出一個相應的請求來完成的*/ int (*writepage)(struct page *page, struct writeback_control *wbc);/*從後備存儲器將一頁或多個連續的頁讀入頁幀*/ int (*readpage)(struct file *, struct page *);/*對尚未回寫到後備存儲器的數據進行同步*/ void (*sync_page)(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);/*執行由write系統調用觸發的寫操作*/ 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 long);int (*releasepage) (struct page *, gfp_t);ssize_t (*direct_IO)(int, struct kiocb *, const struct iovec *iov,loff_t offset, unsigned long nr_segs);int (*get_xip_mem)(struct address_space *, pgoff_t, int,void **, unsigned long *);/* migrate the contents of a page to the specified target */ int (*migratepage) (struct address_space *,struct page *, struct page *);int (*launder_page) (struct page *);int (*is_partially_uptodate) (struct page *, read_descriptor_t *,unsigned long);int (*error_remove_page)(struct address_space *, struct page *);};頁面緩存的實現基於基數樹,緩存屬於內核中性能要求最苛刻的部分之一,而且廣泛用於內核的所有子系統,實現也比較簡單。舉兩個例子,其他的暫時不做分析了。

  分配頁面用於加入地址空間[cpp] view plaincopyprint?

  /*從伙伴系統中分配頁面,頁面的標志根據地址空間中的標志進行設置*/ static inline struct page *page_cache_alloc(struct address_space *x)

  { return __page_cache_alloc(mapping_gfp_mask(x));}分配完了添加到基數樹中[cpp] view plaincopyprint?

  /* * Like add_to_page_cache_locked, but used to add newly allocated pages:* the page is new, so we can just run __set_page_locked() against it. */ static inline int add_to_page_cache(struct page *page,struct address_space *mapping, pgoff_t offset, gfp_t gfp_mask)

  { int error;

  __set_page_locked(page);/*實際的添加工作*/ error = add_to_page_cache_locked(page, mapping, offset, gfp_mask);if (unlikely(error))

  __clear_page_locked(page);return error;} [cpp] view plaincopyprint?

  /** * add_to_page_cache_locked - add a locked page to the pagecache * @page: page to add * @mapping: the page's address_space * @offset: page index * @gfp_mask: page allocation mode * * This function is used to add a page to the pagecache. It must be locked. * This function does not add the page to the LRU. The caller must do that. */ int add_to_page_cache_locked(struct page *page, struct address_space *mapping,pgoff_t offset, gfp_t gfp_mask)

  { int error;

  VM_BUG_ON(!PageLocked(page));

  error = mem_cgroup_cache_charge(page, current->mm,gfp_mask & GFP_RECLAIM_MASK);if (error)

  goto out;/*樹的相關結構申請*/ error = radix_tree_preload(gfp_mask & ~__GFP_HIGHMEM);if (error == 0) { page_cache_get(page);/*使用計數加一*/ page->mapping = mapping;page->index = offset;

  spin_lock_irq(&mapping->tree_lock);/*實際的插入操作*/ error = radix_tree_insert(&mapping->page_tree, offset, page);if (likely(!error)) { mapping->nrpages++;__inc_zone_page_state(page, NR_FILE_PAGES);if (PageSwapBacked(page))

  __inc_zone_page_state(page, NR_SHMEM);spin_unlock_irq(&mapping->tree_lock);} else { page->mapping = NULL;spin_unlock_irq(&mapping->tree_lock);mem_cgroup_uncharge_cache_page(page);page_cache_release(page);} radix_tree_preload_end();} else mem_cgroup_uncharge_cache_page(page);out:return error;}

Copyright © Windows教程網 All Rights Reserved