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