從Linux 2.6.32開始,Linux內核臟頁回寫通過bdi_writeback機制實現,bdi的全拼是backing device info(持久化存儲設備信息,如ssd、hdd)。用戶態調用write系統調用寫入數據后,文件系統只在頁緩存中寫入數據便返回了write系統調用,并沒有分配實際的物理磁盤塊,ext4稱為延遲分配技術(delay allocation)。本文將介紹內核(kernel version 4.14)是在何時如何將寫入的數據回寫到磁盤。
核心數據結構初始化
回寫機制借助了Linux中工作隊列來完成,在內核啟動的時候,系統會使用alloc_workqueue函數申請一個用于回寫的工作隊列。具體實現在函數default_bdi_init中。
函數調用棧如下圖。
bdi_init()函數初始化bdi (struct backing_dev_info),該結構體包含了塊設備信息,代表一個設備。
struct backing_dev_info:描述一個塊設備。
struct bdi_writeback:管理一個塊設備所有的回寫任務。
struct wb_writeback_work :描述需要回寫的任務。
還有管理回寫任務的結構體bdi_writeback,描述任務的結構體wb_writeback_work,其三者的關系如下圖所示。
backing_dev_info中維護了wb_list鏈表,管理bdi_writeback,同時每個bdi_writeback中維護了dwork和work_list,前者代表處理任務的函數,后者則是任務列表。
在bdi_init中對bdi進行初始化后,會繼續調用倒wb_init(),該函數對bdi中的wb(struct bdi_writeback)進行初始化。
?
?
wb_init在初始化過程中,給wb->dwork字段賦值了函數wb_workfn,后面觸發回寫任務時,就會通過該函數進行執行回寫。
至此bdi_writeback機制初始化完成。
觸發回寫任務的時機
由于寫入的數據都緩存在內存中,猜想當空閑內存緊張的時候,內核會執行回寫任務。于是我們需要減少系統可用內存,使用如下命令在內存中創建文件系統然后往里面寫入文件。
使用 dd 命令在該目錄下創建文件。我們創建了一個79M的文件。
完成上述操作以后系統還剩余2M內存,內核并沒有立即觸發回寫,于是使用write系統調用繼續向磁盤寫入數據。
很快就觸發了內核函數wakeup_flusher_threads(事先添加了斷點),函數調用棧如下:
從內核函數調用棧來看是觸發了kswapd內核線程的非活躍LRU鏈表回收。shrink_inactive_list函數掃描不活躍頁面鏈表并且回收頁面,調用了wakeup_flusher_threads函數進行回寫操作。
函數代碼如下,該函數遍歷所有bdi設備下的writeback,并通過函數wb_start_writeback執行回寫操作:
GDB查看傳入wakeup_flusher_threads的參數值分別是nr_pages = 0和reason = WB_REASON_VMSCAN。
其中nr_pages等于0表示盡可能回寫所有的臟頁reason表示本次回寫觸發的原因。除了WB_REASON_VMSCAN,還定義了如下原因,如周期回寫:WB_REASON_PERIODIC,后臺回寫:WB_REASON_BACKGROUND。
我們繼續分析wb_start_writeback回寫函數。該函數創建并初始化了一個wb_writeback_work來描述本次回寫任務,最后調用wb_queue_work。
wb_queue_work調用mod_delayed_work將該任務掛入工作隊列(workqueue),在等待delay時間后由工作隊列的工作線程(worker)執行初始化時注冊的任務管理函數wb->dwork。Linux workqueue如何處理work的過程可以參考文章,本文跳過該過程,直接到回寫任務的處理函數wb_workfn繼續分析:
關于觸發內核回寫的函數調用總結如下圖:
回寫任務的執行
回寫的執行在文件系統層的函數調用如下所示。
函數wb_workfn正常路徑為遍歷work_list,執行wb_do_writeback函數。如果沒有足夠的worker則執行writeback_inodes_wb函數回寫1024個臟頁。
wb_do_writeback函數在遍歷wb并調用wb_writeback回寫結束后會進行定時回寫和臟頁是否超過閾值的回寫檢查。
wb_writeback根據是否包含superblock,分別調用writeback_sb_inodes和__writeback_inodes_wb。
writeback_sb_inodes調用__writeback_single_inode。
?
__writeback_single_inode調用do_writepages。
do_writepages就出現了我們熟悉的頁緩存函數操作集struct address_space_operations *a_ops。其中writepages函數在ext4中的實現為ext4_writepages。
接下來會在ext4_writepages中打包bio結構體,發送到通用塊層,繼續更底層的IO操作。
最后,bdi_writeback機制整體流程如下。
評論