Linux 核心透過一套功能完善的緩衝與快取架構來提升系統效能。緩衝與快取利用部分實體記憶體,確保最重要、最常用的區塊裝置資料能直接從主記憶體取得,無需從低速裝置讀取;同時也將從高速裝置讀取的資料暫存於實體記憶體,使後續存取可直接在記憶體完成。透過綜合考量多種系統因素並延遲寫回,整體效能獲得顯著改善。
核心為區塊裝置提供兩種通用快取方案:
struct bio,可合併同一請求中的後續區塊,加速處理。兩者常聯合使用。例如,快取頁面在寫入操作期間可劃分為不同緩衝區,以更細粒度識別被修改部分,寫回時僅需回寫修改部分,無需傳輸整個頁面。
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))));
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
};
核心採用通用的地址空間方案,建立快取資料與其來源的關聯:
struct backing_dev_info 指定填充地址空間頁面資料的來源。地址空間關聯到處理器虛擬位址空間,是虛擬記憶體區域到裝置對應位置的映射。核心使用基數樹管理與一個地址空間相關的所有頁面,以盡可能降低開銷。頁面快取的實作基於基數樹,屬於核心中效能要求最嚴苛的部分之一,且廣泛用於所有子系統,實作相對簡單。
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 等核心結構,實現了高效的區塊裝置資料快取與回寫。理解其資料結構與操作流程,有助於深入掌握核心儲存子系統的效能優化關鍵。