版權聲明:本文由高劍林原創文章,轉載請註明出處:
文章原文連接:https://www.qcloud.com/community/article/106node
來源:騰雲閣 https://www.qcloud.com/communitylinux
對linux內核來講,讀寫要通過層層路徑,才能真正讀寫硬盤。從io路徑來講,io要通過page cache,io調度隊列,dispatch隊列,ncq隊列和硬盤cache,才能真正到達硬盤。算法
Page cache:page cache是linux內核提供的緩存接口,page cache的名字就說明內核是經過page單元(一般4K大小)來管理cache。讀操做首先在page cache查找,若是找到,就複製page cache的內容返回,找不到,才真正調用下層處理。寫操做,buffer io 寫到page cache就返回,真正的磁盤寫,是由內核的pdflush內核線程負責緩存
IO調度隊列:服務器
Linux內核提供了四種io調度算法,as,deadline,cfq,noop。每種調度算法都實現了一個調度隊列,io首先在隊列中排序(noop最簡單,不排序),而後根據條件,決定是否到dispatch隊列。從調度隊列下發,涉及一個unplug的概念。也就是說,調度隊列一般處於阻塞(plug)狀態,當執行unplug操做時,io離開調度隊列,開始下發。unplug是個循環動做,將調度隊列的全部io都嘗試下發,直到不能下發爲止。
總結一下,執行unplug有下列條件:併發
第一個io啓動了三毫秒的定時器,定時器到了,會unplug,開始下發async
io請求超過設定的限制(缺省是4),執行unplug,開始下發oop
Sync標誌的io,當即unplug,開始下發。性能
Barrier標誌的io,清空調度隊列後,執行unplug,開始下發spa
一個io執行完畢,也要unplug隊列。
dispatch隊列:dispatch隊列對應用關係不大。可是內核層對日誌文件系統的joural數據,提供了一種barrier io,這個主要在dispatch隊列實現。
Ncq隊列:
NCQ是sata硬盤自身的隊列。(sas硬盤的隊列叫TCQ)。NCQ隊列是由操做系統建立的,可是加入到NCQ隊列的io,是由硬盤來決定執行順序。爲了實現這個,NCQ隊列建立在內核的DMA內存中,而後通知硬盤,至於硬盤選擇那個io執行,是硬盤自身選擇的結果。
硬盤cache:
硬盤cache是硬盤內部的cache。若是打開硬盤cache的話,寫硬盤的io,首先是到硬盤cache,而非直接落到硬盤。
Pdflush提供了四個參數來控制回寫。在內核實現中,pdflush的回寫策略控制還比較複雜。
可是簡單一點說,內核缺省狀況下,每5秒鐘掃描髒頁,若是髒頁生存時間超過30秒(缺省數值),就刷髒頁到磁盤。
詳細的可參考本人寫的《linux內核回寫機制和調整》一文。
從上文的分析,一般的io寫,到page cache層就結束返回了,並沒真正寫到硬盤。這樣機器掉電或者故障的時候,就有丟失數據的風險。爲了儘快下io,系統又提供了一些措施解決這個問題。
O_SYNC:打開文件的時候,能夠設置O_SYNC標誌,在page cache的寫完成後,若是文件有O_SYNC標誌,當即開始將io下發,進入調度隊列。隨後將文件系統的meta data數據也下發,而後開始循環執行unplug操做,直到全部的寫io完成。和回寫機制比較,O_SYNC沒有等髒頁生存30秒,就嘗試當即下發到硬盤。
O_SYNC本質就是io下發,而後執行unplug操做。O_SYNC的幾個問題是:
寫page cache時候要把io拆成4k的單元。回寫也是每次寫4K的頁面,若是是大io,就須要內核的調度層把4k的io從新再合併起來。這是冗餘的過程
每一個io都要當即unplug,這樣就不能實現io的排序和合並。O_SYNC的性能至關低。
若是多個進程併發寫,不能保證寫操做的順序。Ncq隊列根據硬盤磁頭的位置和磁盤旋轉位置肯定執行的順序。通常是meta data數據一塊兒寫,這樣存在不一樣步的風險。
若是硬盤cache打開了,那麼寫只到硬盤cache就返回了。存在丟數據的風險。一般存儲廠商都要求硬盤cache關閉。不過騰訊的服務器都是打開硬盤cache的。
O_DIRECT:打開文件的時候,可設置O_DIRECT標誌。O_DIRECT不使用內核提供的page cache。這樣讀操做,就不會到page cache中檢查是否有須要數據存在。而寫操做,也不會將數據寫入page cache,而是送入調度隊列。
O_DIRECT執行寫io的時候,會置WRITE_SYNC標誌。這個標誌在io進入調度隊列後,會執行一次unplug操做。而不是像O_SYNC那樣,循環執行unplug操做。
爲了不O_SYNC每一個寫io都要阻塞等待的問題,系統提供了fsync和fdatasync系統調用,可讓應用層本身控制同步的時機。
Fsync:fsync將文件範圍內,全部的髒頁面都下發到硬盤。而後也要將髒的元數據寫到 硬盤。若是文件的inode自己有變化,一樣須要寫到硬盤。
Fdatasync:fdatasync和fsync的區別其實很輕微。好比ext2文件系統,若是文件的inode只有輕微的變化,fdatasync此時不更新inode。典型的輕微變化是文件atime的變化。而在ext3文件系統,fsync和fdatasync是徹底同樣的。不論是否輕微變化,都要回寫inode。
Fsync和fdatasync都是對整個文件的操做,若是應用只想刷新文件的指定位置,這兩個系統調用就失效了。因此新的內核還提供了sync_file_range來指定範圍寫。不過要注意,sync_file_range是不回寫文件的meta data。必須應用層保證meta data沒有更新。
Pdflush提供了四個參數來控制回寫。在內核實現中,pdflush的回寫策略控制還比較複雜。
可是簡單一點說,內核缺省狀況下,每5秒鐘掃描髒頁,若是髒頁生存時間超過30秒(缺省數值),就刷髒頁到磁盤。
詳細的可參考本人寫的<<linux內核回寫機制和調整》一文。
從上文的分析看出,內核沒有爲用戶態提供保證順序的,肯定寫到硬盤的系統調用。可是對於內核文件系統來講,必須提供這樣的接口。好比日誌文件系統,必需要數據落到硬盤後,才能修改元數據的日誌。不然,出錯狀況下就可能形成文件系統崩潰。爲此,內核專門提供了一個barrier方式實現日誌的準確寫到硬盤。
文件系統的barrier io,意味着,這個barrier io以前的寫io必須完成。同時,在barrier io完成以前(是真正寫到硬盤,不是寫到cache就返回),也不能有別的寫io再執行。爲此,上文分析的dispatch 隊列完成了這個功能。
當寫io從調度隊列進入dispatch隊列的時候,要檢查是不是一個barrier io。若是是barrier io,dispatch首先在隊列中插入一個SCSI命令SYNCHRONIZE_CACHE,這個命令指示硬盤cache刷全部的寫io到硬盤。而後再下發barrier io,以後再插入一個SYNCHRONIZE_CACHE命令,指示硬盤將剛纔的barrier io真正寫到硬盤(還有一種方式,是經過命令攜帶FUA標誌實現不通過cache直接下盤)。
對於單一的存儲系統來講,數據一致性,性能和可靠性是幾個矛盾的指標。標準的linux內核在這方面也有些左右爲難。好比內核在io失敗的狀況下,通常會重試(內核設置了5次的重試)。這樣重試時間可能超過1分鐘,這對互聯網系統的業務來講是不可承受的。如何找到合適點,平衡幾個指標的關係,從操做系統最底層提高產品的可靠性和性能,是一項長期的任務。