Windows XP Windows 7 Windows 2003 Windows Vista Windows 綜合教學 Linux 系統教學
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟體教學
您现在的位置: Windows教程網 >> Windows教程 >  >> Linux 系統教學 >> Linux 教學

Linux 作業系統緩存機制:頁面緩存詳解

編輯:Linux 教學

簡介

Linux 核心透過一套功能完善的緩衝與快取架構來提升系統效能。緩衝與快取利用部分實體記憶體,確保最重要、最常用的區塊裝置資料能直接從主記憶體取得,無需從低速裝置讀取;同時也將從高速裝置讀取的資料暫存於實體記憶體,使後續存取可直接在記憶體完成。透過綜合考量多種系統因素並延遲寫回,整體效能獲得顯著改善。

頁面緩存與塊緩存

核心為區塊裝置提供兩種通用快取方案:

  • 頁面緩存:以頁面為操作單位,考量特定架構的頁面大小。記憶體映射技術是主要應用範例,因其他檔案存取也基於此技術實作,頁面緩存實際上負責了區塊裝置大部分的快取工作。
  • 塊緩存:以區塊為操作單位。I/O 操作時存取單位為裝置的各個區塊,而非整個記憶體頁面。雖然頁面長度對所有檔案系統相同,但區塊長度取決於特定檔案系統或其設定,因此塊緩存必須能處理不同長度的區塊。目前用於區塊傳輸的標準資料結構已演變為 struct bio,可合併同一請求中的後續區塊,加速處理。

兩者常聯合使用。例如,快取頁面在寫入操作期間可劃分為不同緩衝區,以更細粒度識別被修改部分,寫回時僅需回寫修改部分,無需傳輸整個頁面。

address_space 核心資料結構

struct address_space 是高速快取的核心資料結構,區塊裝置的讀寫操作皆放置於此結構體中:

struct address_space {
    struct inode *host;                 // 擁有者:inode 或 block_device
    struct radix_tree_root page_tree;   // 基數樹根節點,列出所有實體記憶體頁面
    spinlock_t tree_lock;               // 保護基數樹的鎖
    unsigned int i_mmap_writable;       // VM_SHARED 映射計數
    struct prio_tree_root i_mmap;       // 包含與 inode 相關的所有普通記憶體映射
    struct list_head i_mmap_nonlinear;  // 非線性映射中的頁面列表
    spinlock_t i_mmap_lock;             // 保護樹、計數、列表
    unsigned int truncate_count;        // 截斷競爭條件保護
    unsigned long nrpages;              // 快取頁面總數
    pgoff_t writeback_index;            // 回寫起始索引
    const struct address_space_operations *a_ops; // 操作方法表
    unsigned long flags;                // 錯誤位元/GFP 遮罩
    struct backing_dev_info *backing_dev_info; // 後備儲存裝置資訊
    spinlock_t private_lock;            // address_space 私用鎖
    struct list_head private_list;      // 連接包含檔案系統中繼資料的 buffer_head
    struct address_space *assoc_mapping; // 關聯的 address_space 指標
} __attribute__((aligned(sizeof(long))));

後備儲存資訊 backing_dev_info

struct backing_dev_info {
    struct list_head bdi_list;
    struct rcu_head rcu_head;
    unsigned long ra_pages;             // 最大預讀數量(單位 PAGE_CACHE_SIZE)
    unsigned long state;                // 裝置狀態(原子操作)
    unsigned int capabilities;          // 裝置能力
    congested_fn *congested_fn;         // 若裝置為 md/dm 的函式指標
    void *congested_data;               // 擁塞函式輔助資料指標
    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, max_ratio, max_prop_frac;
    struct bdi_writeback wb;            // 預設回寫資訊
    spinlock_t wb_lock;                 // 保護 wb_list 更新側
    struct list_head wb_list;           // 掛載在此 bdi 的 flusher 執行緒
    unsigned long wb_mask;              // 註冊任務位元遮罩
    unsigned int wb_cnt;                // 註冊任務數量
    struct list_head work_list;
    struct device *dev;
    #ifdef CONFIG_DEBUG_FS
    struct dentry *debug_dir, *debug_stats;
    #endif
};

地址空間與核心其他部分的關聯

核心採用通用的地址空間方案,建立快取資料與其來源的關聯:

  1. 記憶體中的頁面分配給各地址空間,內容可由使用者程序或核心操作,代表快取內容。
  2. struct backing_dev_info 指定填充地址空間頁面資料的來源。地址空間關聯到處理器虛擬位址空間,是虛擬記憶體區域到裝置對應位置的映射。
  3. 若存取虛擬記憶體中無對應實體頁面的位置,核心可根據 address_space 結構找到讀取資料的來源。
  4. 每個地址空間提供一組操作,支援雙向資料傳輸互動。

基數樹管理頁面

核心使用基數樹管理與一個地址空間相關的所有頁面,以盡可能降低開銷。頁面快取的實作基於基數樹,屬於核心中效能要求最嚴苛的部分之一,且廣泛用於所有子系統,實作相對簡單。

地址空間操作 address_space_operations

struct address_space_operations {
    int (*writepage)(struct page *page, struct writeback_control *wbc);
    int (*readpage)(struct file *, struct page *);
    void (*sync_page)(struct page *);
    int (*writepages)(struct address_space *, struct writeback_control *);
    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);
    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 *);
    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 *);
};

頁面分配與加入頁面快取

分配頁面

static inline struct page *page_cache_alloc(struct address_space *x)
{
    return __page_cache_alloc(mapping_gfp_mask(x));
}

加入基數樹

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;
}

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;
}

總結

Linux 頁面快取機制透過 address_space、基數樹、backing_dev_info 等核心結構,實現了高效的區塊裝置資料快取與回寫。理解其資料結構與操作流程,有助於深入掌握核心儲存子系統的效能優化關鍵。

Copyright © Windows教程網 All Rights Reserved