前段時(shí)間在開(kāi)發(fā)一個(gè)使用SSD做緩存的系統(tǒng),在高速寫入數(shù)據(jù)時(shí)會(huì)出現(xiàn)大量的磁盤緩存。太多的磁盤緩存如果沒(méi)有及時(shí)的寫入磁盤中,在機(jī)器出現(xiàn)問(wèn)題時(shí)是非常危險(xiǎn)的,這樣會(huì)導(dǎo)致很多的數(shù)據(jù)丟失,但是如果實(shí)時(shí)的將數(shù)據(jù)刷入磁盤中,這樣寫入效率有太低了。為了弄明白Linux系統(tǒng)的這種磁盤寫入特性,最近深入的學(xué)習(xí)了一下。 VFS(Virtual File System)的存在使得Linux可以兼容不同的文件系統(tǒng),例如ext3、ext4、xfs、ntfs等等,其不僅具有為所有的文件系統(tǒng)實(shí)現(xiàn)一個(gè)通用的外接口的作用,還具有另一個(gè)與系統(tǒng)性能相關(guān)的重要作用——緩存。VFS中引入了高速磁盤緩存的機(jī)制,這屬于一種軟件機(jī)制,允許內(nèi)核將原本存在磁盤上的某些信息保存在RAM中,以便對(duì)這些數(shù)據(jù)的進(jìn)一步訪問(wèn)能快速進(jìn)行,而不必慢速訪問(wèn)磁盤本身。高速磁盤緩存可大致分為以下三種:
目錄項(xiàng)高速緩存——主要存放的是描述文件系統(tǒng)路徑名的目錄項(xiàng)對(duì)象
索引節(jié)點(diǎn)高速緩存——主要存放的是描述磁盤索引節(jié)點(diǎn)的索引節(jié)點(diǎn)對(duì)象
頁(yè)高速緩存——主要存放的是完整的數(shù)據(jù)頁(yè)對(duì)象,每個(gè)頁(yè)所包含的數(shù)據(jù)一定屬于某個(gè)文件,同時(shí),所有的文件讀寫操作都依賴于頁(yè)高速緩存。其是Linux內(nèi)核所使用的主要磁盤高速緩存。 正是由于緩存的引入,所以VFS文件系統(tǒng)采用了文件數(shù)據(jù)延遲寫的技術(shù),因此,如果在調(diào)用系統(tǒng)接口寫入數(shù)據(jù)時(shí)沒(méi)有使用同步寫模式,那么大多數(shù)據(jù)將會(huì)先保存在緩存中,待等到滿足某些條件時(shí)才將數(shù)據(jù)刷入磁盤里。
內(nèi)核是如何將數(shù)據(jù)刷入磁盤的呢?在看完以下兩點(diǎn)后就能得到答案。
1. 把臟頁(yè)寫入磁盤 正如我們所了解的,內(nèi)核不斷用包含塊設(shè)備數(shù)據(jù)的頁(yè)填充頁(yè)高速緩存。只要進(jìn)程修改了數(shù)據(jù),相應(yīng)的頁(yè)就被標(biāo)記為臟頁(yè),即把它的PG_dirty標(biāo)志位置。 Unix系統(tǒng)允許把臟緩沖區(qū)寫入塊設(shè)備的操作延遲執(zhí)行,因?yàn)檫@種策略可以顯著地提高系統(tǒng)的性能。對(duì)高速緩存中的頁(yè)的幾次寫操作可能只需對(duì)相應(yīng)的磁盤塊進(jìn)行一次緩慢的物理更新就可以滿足。此外,寫操作沒(méi)有讀操作那么緊迫,因?yàn)檫M(jìn)程通常是不會(huì)因?yàn)檠舆t寫而掛起,而大部分情況都因?yàn)檠舆t讀而掛起。正是由于延遲寫,使得任一物理塊設(shè)備平均為讀請(qǐng)求提供服務(wù)將多于寫請(qǐng)求。一個(gè)臟頁(yè)可能直到最后一刻(即直到系統(tǒng)關(guān)閉時(shí))都一直逗留在主存中。然而,從延遲寫策略的局限性來(lái)看,它有兩個(gè)主要的缺點(diǎn): 一、如果發(fā)生了硬件錯(cuò)誤或者電源掉電的情況,那么就無(wú)法再獲得RAM的內(nèi)容,因此,從系統(tǒng)啟動(dòng)以來(lái)對(duì)文件進(jìn)行的很多修改就丟失了。 二、頁(yè)高速緩存的大小(由此存放它所需的RAM的大小)就可要很大——至少要與所訪問(wèn)塊設(shè)備的大小不同。因此,在下列條件下把臟頁(yè)刷新(寫入)到磁盤:
頁(yè)高速緩存變得太滿,但還需要更多的頁(yè),或者臟頁(yè)的數(shù)量已經(jīng)太多。
自從頁(yè)變成臟頁(yè)以來(lái)已過(guò)去太長(zhǎng)時(shí)間。
進(jìn)程請(qǐng)求對(duì)塊設(shè)備或者特定文件任何待定的變化都進(jìn)行刷新。通過(guò)調(diào)用sync()、fsync()或者fdatasync()系統(tǒng)調(diào)用來(lái)實(shí)現(xiàn)。 緩沖區(qū)頁(yè)的引入是問(wèn)題更加復(fù)雜。與每個(gè)緩沖區(qū)頁(yè)相關(guān)的緩沖區(qū)首部使內(nèi)核能夠了解每個(gè)獨(dú)立塊緩沖區(qū)的狀態(tài)。如果至少有一個(gè)緩沖區(qū)首部的PG_Dirty標(biāo)志被置位,就應(yīng)該設(shè)置相應(yīng)緩沖區(qū)頁(yè)的PG_dirty標(biāo)志。當(dāng)內(nèi)核選擇要刷新的緩沖區(qū)時(shí),它掃描相應(yīng)的緩沖區(qū)首部,并只把臟塊的內(nèi)容有效的寫到磁盤。一旦內(nèi)核把緩沖區(qū)的所有臟頁(yè)刷新到磁盤,就把頁(yè)的PG_dirty標(biāo)志清0。
2. pdflush內(nèi)核線程 早期版本的Linux使用bdfllush內(nèi)核線程系統(tǒng)地掃描頁(yè)高速緩存以搜索要刷新的臟頁(yè),并且使用另一個(gè)內(nèi)核線程kupdate來(lái)保證所有的頁(yè)不會(huì)“臟”太長(zhǎng)時(shí)間。Linux 2.6用一組通用內(nèi)核線程pdflush替代上述兩個(gè)線程。這些內(nèi)核線程結(jié)構(gòu)靈活,它們作用于兩個(gè)參數(shù):一個(gè)指向線程要執(zhí)行的函數(shù)的指針和一個(gè)函數(shù)要用的參數(shù)。系統(tǒng)中pdflush內(nèi)核線程的數(shù)量是要?jiǎng)討B(tài)調(diào)整的:pdflush線程太少時(shí)就創(chuàng)建,太多時(shí)就殺死。因?yàn)檫@些內(nèi)核線程所執(zhí)行的函數(shù)可以阻塞,所以創(chuàng)建多個(gè)而不是一個(gè)pdflush內(nèi)核線程可以改善系統(tǒng)性能。根據(jù)下面的原則控制pdflush線程的產(chǎn)生和消亡:
必須有至少兩個(gè),最多八個(gè)pdflush內(nèi)核線程
如果到最近的1s期間沒(méi)有空閑pdflush,就應(yīng)該創(chuàng)建新的pdflush線程
如果最近一次pdflush變?yōu)榭臻e的時(shí)間超過(guò)了1s,就應(yīng)該刪除一個(gè)pdflush線程 所有的pdflush內(nèi)核線程都有pdflush_work描述符,其數(shù)據(jù)結(jié)構(gòu)如下:
類型字段說(shuō)明
struct task_structwho指向內(nèi)核線程描述符的指針
void (*) (unsigned long)fn內(nèi)核線程所執(zhí)行的回調(diào)函數(shù)
unsigned longarg0給回調(diào)函數(shù)的參數(shù)
struct list headlistpdflush_list鏈表的鏈接
unsigned longwhen_i_went_to_sleep當(dāng)內(nèi)核線程可用時(shí)的時(shí)間(以jiffies表示)