標籤:
參考
《Linux核心設計與實現》
*******************************************
頁快速緩衝是linux核心實現的一種主要磁碟緩衝,它主要用來降低對磁碟的IO操作,詳細地講,是通過把磁碟中的資料緩衝到實體記憶體中,把對磁碟的訪問變為對實體記憶體的訪問。為什麼要這麼做呢?一,速度;二暫時局部原理。有關這兩個概念,相信熟悉作業系統的我們不會太陌生。頁快速緩衝是由RAM中的物理頁組成的,緩衝中的每一頁都相應著磁碟中的多個塊。每當核心開始運行一個頁IO操作時,就先到快速緩衝中找。這樣就能夠大大降低磁碟操作。
一個物理頁可能由多個不連續的物理磁碟塊組成。也正是因為頁面中映射的磁碟塊不一定連續,所以在頁快速緩衝中檢測特定資料是否已被緩衝就變得不那麼easy了。另外linux頁快速緩衝對被快取頁面的範圍定義的很寬。緩衝的目標是不論什麼基於頁的對象,這包括各種類型的檔案和各種類型的記憶體映射。為了滿足普遍性要求,linux使用定義在linux/fs.h中的結構體address_space結構體描寫敘述頁快速緩衝中的頁面,例如以下:
struct address_space { struct inode *host; /* owning inode */ struct radix_tree_root page_tree; /* radix tree of all pages */ spinlock_t tree_lock; /* page_tree lock */ unsigned int i_mmap_writable; /* VM_SHARED ma count */ struct prio_tree_root i_mmap; /* list of all mappings */ struct list_head i_mmap_nonlinear; /* VM_NONLINEAR ma list */ spinlock_t i_mmap_lock; /* i_mmap lock */ atomic_t truncate_count; /* truncate re count */ unsigned long nrpages; /* total number of pages */ pgoff_t writeback_index; /* writeback start offset */ struct address_space_operations *a_ops; /* operations table */ unsigned long flags; /* gfp_mask and error flags */ struct backing_dev_info *backing_dev_info; /* read-ahead information */ spinlock_t private_lock; /* private lock */ struct list_head private_list; /* private list */ struct address_space *assoc_mapping; /* associated buffers */};
當中的i_mmap欄位是一個優先搜尋樹,它的搜尋範圍包括了在address_sapce中私人的和共用的頁面。nrpages反應了address_space空間的大小。address_space結構往往會和某些核心對象關聯。通常情況下,會與一個索引節點(inode)關聯,這時host域就會指向該索引節點。假設關聯對象不是一個索引節點的話,比方address_space和swapper關聯時,這是host域會被置為NULL。a_ops域指向地址空間對象中的操作函數表,這與VFS對象及其操作函數表關係類似,操作函數表定義在linux/fs.h中,由address_space_operations表示,例如以下:
struct address_space_operations { int (*writepage)(struct page *, struct writeback_control *); int (*readpage) (struct file *, struct page *); int (*sync_page) (struct page *); int (*writepages) (struct address_space *, struct writeback_control *); int (*set_page_dirty) (struct page *); int (*readpages) (struct file *, struct address_space *,struct list_head *, unsigned); int (*prepare_write) (struct file *, struct page *, unsigned, unsigned); int (*commit_write) (struct file *, struct page *, unsigned, unsigned); sector_t (*bmap)(struct address_space *, sector_t); int (*invalidatepage) (struct page *, unsigned long); int (*releasepage) (struct page *, int); int (*direct_IO) (int, struct kiocb *, const struct iovec *,loff_t, unsigned long);};
background-color: rgb(255, 255, 255);">這裡面最重要的兩個就是readpage()與writepage()了。對於readpage()方法而言,首先,一個address_space對象和一個位移量會被傳給該方法,這兩個參數用來在頁快速緩衝中搜素須要的資料:
page = find_get_page(mapping, index);mapping是指定的地址空間,index是檔案裡的指定位置。假設要搜尋的頁並沒在快速緩衝中,那麼核心將分配一個新頁面,然後將其增加到頁快速緩衝中,例如以下int error;cached_page = page_cache_alloc_cold(mapping);if (!cached_page) /* error allocating memory */error = add_to_page_cache_lru(cached_page, mapping, index, GFP_KERNEL);if (error) /* error adding page to page cache */
最後,須要的資料從磁碟被讀入,再被增加頁快速緩衝,然後返回給使用者:error = mapping->a_ops->readpage(file,page);
寫操作和讀操作有少許不同。對於檔案對應來說,當頁被改動了,VM只須要調用: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);
首先,在頁快速緩衝中搜尋須要的頁,假設須要的頁不在快速緩衝中,那麼核心在快速緩衝中新分配一空暇項;下一步,prepare_write()方法被調用,建立一個寫請求;接著資料被從使用者空間複製到核心緩衝;最後通過commit_write()函數將資料寫入磁碟。
由於在不論什麼頁IO操作前核心都要檢查頁是否已經在頁快速緩衝中了,所以這樣的檢查必須迅速,高效。否則得不償失了。前邊已經說過,也快速緩衝通過兩個參數address_space對象和一個位移量進行搜尋。每一個address_space對象都有唯一的基樹(radix tree),它保證在page_tree結構體中。基樹是一個二叉樹,僅僅要指定了檔案位移量,就能夠在基樹中迅速檢索到希望的資料,頁快速緩衝的搜尋函數find_get_
page()要調用函數radix_tree_lookup(),該函數會在指定基樹中搜尋指定頁面。基樹核心代碼的通用形式能夠在檔案lib/radix-tree.c中找到,另外想要使用基樹,須要包括標頭檔linux/radix_tree.h.
在記憶體中累積起來的髒頁必須被寫回到磁碟,在一下兩種情況下,髒頁會被寫會到磁碟:
1.在空暇記憶體低於一個特定的閾值時,核心必須將髒頁寫回磁碟,以便釋放記憶體。
2.當髒頁在記憶體中駐留超過一定的閾值時,核心必須將逾時的髒頁寫會磁碟,以確保髒頁不會無限期地駐留在記憶體中。
如今你僅僅需知道,2.6核心中,使用pdflush後台回寫常式來完畢這個工作。那麼詳細是怎麼實現的呢:
首先,pdflush線程在系統中的空暇記憶體低於一個特定的閾值時,將髒頁重新整理回磁碟。該後台回寫常式的目的在於在可用實體記憶體過低時,釋放髒頁以又一次獲得記憶體。上面提到的特定的記憶體閾值能夠通過dirty_background_ratio系統調用設定。一旦空暇記憶體比這個指小時,核心便會調用函數wakeup_bdflush() 喚醒一個pdflush線程,隨後pdflush線程進一步調用函數background_writeout()開始將髒頁寫會到磁碟,函數background_writeout()須要一個長整型參數,該參數指定試圖寫回的頁面數目。函數background_writeout會連續地寫會資料,直到滿足一下兩個條件:
1.已經有指定的最小數目的頁被寫回到磁碟。
2.空暇記憶體頁已經回升,超過了閾值dirty_background_ration.
pdflush線程(實如今mm/pdflush.c中,回寫機制的實現代碼在檔案mm/page-writeback.c和fs/fs-writeback.c中)周期地被喚醒而且把超過特定期限的髒頁寫回磁碟。系統管理員能夠在/proc/sys/vm中設定回寫相關的參數,也能夠通過sysctl系統調用來設定它們。下表給出了能夠設定的量:
Linux頁快速緩衝與回寫機制分析