天天看點

Linux IO 之 系統緩存(pdflush & dirty page) 及 擴充知識

[原文]

http://www.phpfans.net/article/htmls/201010/mzewnzax.html

延伸閱讀:

cgroup限制使用者iops,共用檔案系統,引發的思考:

http://blog.163.com/digoal@126/blog/static/163877040201571403648184/

系統緩存相關的幾個核心參數 (還有2個是指定bytes的,含義和ratio差不多):

1.         /proc/sys/vm/dirty_background_ratio

該檔案表示髒資料到達系統整體記憶體的百分比,此時觸發pdflush程序把髒資料寫回磁盤。

預設設定:10

當使用者調用write時,如果發現系統中的髒資料大于這門檻值(或dirty_background_bytes ),會觸發pdflush程序去寫髒資料,但是使用者的write調用會立即傳回,無需等待。pdflush刷髒頁的标準是讓髒頁降低到該門檻值以下。

即使cgroup限制了使用者程序的iops,也無所謂。

2.         /proc/sys/vm/dirty_expire_centisecs

該檔案表示如果髒資料在記憶體中駐留時間超過該值,pdflush程序在下一次将把這些資料寫回磁盤。

預設設定:3000(1/100秒)

3.         /proc/sys/vm/dirty_ratio

該檔案表示如果程序産生的髒資料到達系統整體記憶體的百分比,此時使用者程序自行把髒資料寫回磁盤。

預設設定:40

當使用者調用write時,如果發現系統中的髒資料大于這門檻值(或dirty_bytes ),需要自己把髒資料刷回磁盤,降低到這個門檻值以下才傳回。

注意,此時如果cgroup限制了使用者程序的iops,那就悲劇了。

4.         /proc/sys/vm/dirty_writeback_centisecs

該檔案表示pdflush程序的喚醒間隔,周期性把超過dirty_expire_centisecs時間的髒資料寫回磁盤。

預設設定:500(1/100秒)

系統一般在下面三種情況下回寫dirty頁:

1.      定時方式: 定時回寫是基于這樣的原則:/proc/sys/vm/dirty_writeback_centisecs的值表示多長時間會啟動回寫線程,由這個定時器啟動的回寫線程隻回寫在記憶體中為dirty時間超過(/proc/sys/vm/dirty_expire_centisecs / 100)秒的頁(這個值預設是3000,也就是30秒),一般情況下dirty_writeback_centisecs的值是500,也就是5秒,是以預設情況下系統會5秒鐘啟動一次回寫線程,把dirty時間超過30秒的頁回寫,要注意的是,這種方式啟動的回寫線程隻回寫逾時的dirty頁,不會回寫沒逾時的dirty頁,可以通過修改/proc中的這兩個值,細節檢視核心函數wb_kupdate。

2.      記憶體不足的時候: 這時并不将所有的dirty頁寫到磁盤,而是每次寫大概1024個頁面,直到空閑頁面滿足需求為止

3.      寫操作時發現髒頁超過一定比例: 

        當髒頁占系統記憶體的比例超過/proc/sys/vm/dirty_background_ratio 的時候,write系統調用會喚醒pdflush回寫dirty page,直到髒頁比例低于/proc/sys/vm/dirty_background_ratio,但write系統調用不會被阻塞,立即傳回.

         當髒頁占系統記憶體的比例超/proc/sys/vm/dirty_ratio的時候, write系統調用會被被阻塞,主動回寫dirty page,直到髒頁比例低于/proc/sys/vm/dirty_ratio

大資料量項目中的感觸:

1  如果寫入量巨大,不能期待系統緩存的自動回刷機制,最好采用應用層調用fsync或者sync。如果寫入量大,甚至超過了系統緩存自動刷回的速度,就有可能導緻系統的髒頁率超過/proc/sys/vm/dirty_ratio, 這個時候,系統就會阻塞後續的寫操作,這個阻塞有可能有5分鐘之久,是我們應用無法承受的。是以,一種建議的方式是在應用層,在合适的時機調用fsync。

2  對于關鍵性能,最好不要依賴于系統cache的作用,如果對性能的要求比較高,最好在應用層自己實作cache,因為系統cache受外界影響太大,說不定什麼時候,系統cache就被沖走了。

3  在logic設計中,發現一種需求使用系統cache實作非常合适,對于logic中的高樓貼,在應用層cache實作非常複雜,而其數量又非常少,這部分請求,可以依賴于系統cache發揮作用,但需要和應用層cache相配合,應用層cache可以cache住絕大部分的非高樓貼的請求,做到這一點後,整個程式對系統的io就主要在高樓貼這部分了。這種情況下,系統cache可以做到很好的效果。

磁盤預讀:

關于預讀摘錄如下兩段:

預讀算法概要

1.順序性檢測

為了保證預讀命中率,linux隻對順序讀(sequential read)進行預讀。核心通過驗證如下兩個條件來判定一個read()是否順序讀:

◆這是檔案被打開後的第一次讀,并且讀的是檔案首部;

◆目前的讀請求與前一(記錄的)讀請求在檔案内的位置是連續的。

如果不滿足上述順序性條件,就判定為随機讀。任何一個随機讀都将終止目前的順序序列,進而終止預讀行為(而不是縮減預讀大小)。注意這裡的空間順序性說的是檔案内的偏移量,而不是指實體磁盤扇區的連續性。在這裡linux作了一種簡化,它行之有效的基本前提是檔案在磁盤上是基本連續存儲的,沒有嚴重的碎片化。

2.流水線預讀

當程式在處理一批資料時,我們希望核心能在背景把下一批資料事先準備好,以便cpu和硬碟能流水線作業。linux用兩個預讀視窗來跟蹤目前順序流的預讀狀态:current視窗和ahead視窗。其中的ahead視窗便是為流水線準備的:當應用程式工作在current視窗時,核心可能正在ahead視窗進行異步預讀;一旦程式進入目前的ahead視窗,核心就會立即往前推進兩個視窗,并在新的ahead視窗中啟動預讀i/o。

3.預讀的大小

當确定了要進行順序預讀(sequential readahead)時,就需要決定合适的預讀大小。預讀粒度太小的話,達不到應有的性能提升效果;預讀太多,又有可能載入太多程式不需要的頁面,造成資源浪費。為此,linux采用了一個快速的視窗擴張過程:

◆首次預讀: readahead_size = read_size * 2; // or *4

預讀視窗的初始值是讀大小的二到四倍。這意味着在您的程式中使用較大的讀粒度(比如32kb)可以稍稍提升i/o效率。

◆後續預讀: readahead_size *= 2;

後續的預讀視窗将逐次倍增,直到達到系統設定的最大預讀大小,其預設值是128kb。這個預設值已經沿用至少五年了,在目前更快的硬碟和大容量記憶體面前,顯得太過保守。

# blockdev –setra 2048 /dev/sda

當然預讀大小不是越大越好,在很多情況下,也需要同時考慮i/o延遲問題。

其他細節:

1.      pread 和pwrite

在多線程io操作中,對io的操作盡量使用pread和pwrite,否則,如果使用seek+write/read的方式的話,就需要在操作時加鎖。這種加鎖會直接造成多線程對同一個檔案的操作在應用層就串行了。進而,多線程帶來的好處就被消除了。

使用pread方式,多線程也比單線程要快很多,可見pread系統調用并沒有因為同一個檔案描述符而互相阻塞。pread和pwrite系統調用在底層實作中是如何做到相同的檔案描述符而彼此之間不影響的?多線程比單線程的iops增高的主要因素在于排程算法。多線程做pread時互相未嚴重競争是次要因素。

      核心在執行pread的系統調用時并沒有使用inode的信号量,避免了一個線程讀檔案時阻塞了其他線程;但是pwrite的系統調用會使用inode的信号量,多個線程會在inode信号量處産生競争。pwrite僅将資料寫入cache就傳回,時間非常短,是以競争不會很強烈。

2.       檔案描述符需要多套嗎?

在使用pread/pwrite的前提下,如果各個讀寫線程使用各自的一套檔案描述符,是否還能進一步提升io性能?

每個檔案描述符對應核心中一個叫file的對象,而每個檔案對應一個叫inode的對象。假設某個程序兩次打開同一個檔案,得到了兩個檔案描述符,那麼在核心中對應的是兩個file對象,但隻有一個inode對象。檔案的讀寫操作最終由inode對象完成。是以,如果讀寫線程打開同一個檔案的話,即使采用各自獨占的檔案描述符,但最終都會作用到同一個inode對象上。是以不會提升io性能。

繼續閱讀