淺墨: 聊聊Linux IO(下)
前情回顧
淺墨: 聊聊Linux IO(上)
淺墨: 聊聊Linux IO(中)——Linux內核中的IO棧
Page Cache 的同步
廣義上Cache的同步方式有兩種,即Write Through(寫穿)
和Write back(寫回)
. 從名字上就能看出這兩種方式都是從寫操作的不同處理方式引出的概念(純讀的話就不存在Cache一致性了,不是么)。對應到Linux的Page Cache
上所謂Write Through
就是指write(2)
操作將數據拷貝到Page Cache
後立即和下層進行同步的寫操作,完成下層的更新後才返回。而Write back
正好相反,指的是寫完Page Cache
就可以返回了。Page Cache
到下層的更新操作是非同步進行的。
Linux下Buffered IO
默認使用的是Write back
機制,即文件操作的寫只寫到Page Cache
就返回,之後Page Cache
到磁碟的更新操作是非同步進行的。Page Cache
中被修改的內存頁稱之為臟頁(Dirty Page),臟頁在特定的時候被一個叫做pdflush(Page Dirty Flush)
的內核線程寫入磁碟,寫入的時機和條件如下:
當空閑內存低於一個特定的閾值時,內核必須將臟頁寫回磁碟,以便釋放內存。
當臟頁在內存中駐留時間超過一個特定的閾值時,內核必須將超時的臟頁寫回磁碟。
用戶進程調用
sync(2)
、fsync(2)
、fdatasync(2)
系統調用時,內核會執行相應的寫回操作。
刷新策略由以下幾個參數決定(數值單位均為1/100秒):
# flush每隔5秒執行一次
root@082caa3dfb1d / $ sysctl vm.dirty_writeback_centisecs
vm.dirty_writeback_centisecs = 500
# 內存中駐留30秒以上的臟數據將由flush在下一次執行時寫入磁碟
root@082caa3dfb1d / $ sysctl vm.dirty_expire_centisecs
vm.dirty_expire_centisecs = 3000
# 若臟頁佔總物理內存10%以上,則觸發flush把臟數據寫回磁碟
root@082caa3dfb1d / $ sysctl vm.dirty_background_ratio
vm.dirty_background_ratio = 10
默認是寫回方式,如果想指定某個文件是寫穿方式呢?即寫操作的可靠性壓倒效率的時候,能否做到呢?當然能,除了之前提到的fsync(2)
之類的系統調用外,在open(2)
打開文件時,傳入O_SYNC
這個flag即可實現。這裡給篇參考文章[5],不再贅述(更好的選擇是去讀TLPI相關章節)。
文件讀寫遭遇斷電時,數據還安全嗎?相信你有自己的答案了。使用O_SYNC
或者fsync(2)
刷新文件就能保證安全嗎?現代磁碟一般都內置了緩存,代碼層面上也只能講數據刷新到磁碟的緩存了。當數據已經進入到磁碟的高速緩存時斷電了會怎麼樣?這個恐怕不能一概而論了。不過可以使用hdparm -W0
命令關掉這個緩存,相應的,磁碟性能必然會降低。
文件操作與鎖
當多個進程/線程對同一個文件發生寫操作的時候會發生什麼?如果寫的是文件的同一個位置呢?這個問題討論起來有點複雜了。首先write(2)
調用不是原子操作,不要被TLPI的中文版5.2章節的第一句話誤導了(英文版也是有歧義的,作者在這裡給出了勘誤信息)。當多個write(2)
操作對一個文件的同一部分發起寫操作的時候,情況實際上和多個線程訪問共享的變數沒有什麼區別。按照不同的邏輯執行流,會有很多種可能的結果。也許大多數情況下符合預期,但是本質上這樣的代碼是不可靠的。
特別的,文件操作中有兩個操作是內核保證原子的。分別是open(2)
調用的O_CREAT
和O_APPEND
這兩個flag屬性。前者是文件不存在就創建,後者是每次寫文件時把文件游標移動到文件最後追加寫(NFS等文件系統不保證這個flag)。有意思的問題來了,以O_APPEND
方式打開的文件write(2)
操作是不是原子的?文件游標的移動和調用寫操作是原子的,那寫操作本身會不會發生改變呢?有的開源軟體比如apache寫日誌就是這樣寫的,這是可靠安全的嗎?坦白講我也不清楚,有人說Then O_APPEND is atomic and write-in-full for all reasonably-sized> writes to regular files.
但是我也沒有找到很權威的說法。這裡給出一個郵件列表上的討論,可以參考下[6]。今天先放過去,後面有時間的話專門研究下這個問題。如果你能給出很明確的說法和證明,還望不吝賜教。
Linux下的文件鎖有兩種,分別是flock(2)
的方式和fcntl(2)
的方式,前者源於BSD,後者源於System V,各有限制和應用場景。老規矩,TLPI上講的很清楚的這裡不贅述。我個人是沒有用過文件鎖的,系統設計的時候一般會避免多個執行流寫一個文件的情況,或者在代碼邏輯上以mutex加鎖,而不是直接加鎖文件本身。資料庫場景下這樣的操作可能會多一些(這個純屬臆測),這就不是我了解的範疇了。
磁碟的性能測試
在具體的機器上跑服務程序,如果涉及大量IO的話,首先要對機器本身的磁碟性能有明確的了解,包括不限於IOPS、IO Depth等等。這些數據不僅能指導系統設計,也能幫助資源規劃以及定位系統瓶頸。比如我們知道機械磁碟的連續讀寫性能一般不會超過120M/s,而普通的SSD磁碟隨意就能超過機械盤幾倍(商用SSD的連續讀寫速率達到2G+/s不是什麼新鮮事)。另外由於磁碟的工作原理不同,機械磁碟需要旋轉來尋找數據存放的磁軌,所以其隨機存取的效率受到了「尋道時間」的嚴重影響,遠遠小於連續存取的效率;而SSD磁碟讀寫任意扇區可以認為是相同的時間,隨機存取的性能遠遠超過機械盤。所以呢,在機械磁碟作為底層存儲時,如果一個線程寫文件很慢的話,多個線程分別去寫這個文件的各個部分能否加速呢?不見得吧?如果這個文件很大,各個部分的尋道時間帶來極大的時間消耗的話,效率就很低了(先不考慮Page Cache
)。SSD呢?可以明確,設計合理的話,SSD多線程讀寫文件的效率會高於單線程。當前的SSD盤很多都以高並發的讀取為賣點的,一個線程壓根就喂不飽一塊SSD盤。一般SSD的IO Depth都在32甚至更高,使用32或者64個線程才能跑滿一個SSD磁碟的帶寬(同步IO情況下)。
具體的SSD原理不在本文計劃內,這裡給出一篇詳細的參考文章[7]。有時候一些文章中所謂的STAT磁碟一般說的就是機械盤(雖然STAT本身只是一個匯流排介面)。介面會影響存儲設備的最大速率,基本上是STAT -> PCI-E -> NVMe
的發展路徑,具體請自行Google了解。
具體的設備一般使用fio
工具[8]來測試相關磁碟的讀寫性能。fio的介紹和使用教程有很多[9],不再贅述。這裡不想貼性能數據的原因是存儲介質的發展實在太快了,一方面不想貼某些很快就過時的數據以免讓初學者留下不恰當的第一印象,另一方面也希望讀寫自己實踐下fio命令。
前文提到存儲介質的原理會影響程序設計,我想稍微的解釋下。這裡說的「影響」不是說具體的讀寫能到某個速率,程序中就依賴這個數值,換個工作環境就性能大幅度降低(當然,為專門的機型做過優化的結果很可能有這個副作用)。而是說根據存儲介質的特性,程序的設計起碼要遵循某個設計套路。舉個簡單的例子,SATA機械盤的隨機存取很慢,那系統設計時,就要儘可能的避免隨機的IO出現,儘可能的轉換成連續的文件存取來加速運行。比如Google的LevelDB就是轉換隨機的Key-Value寫入為Binlog(連續文件寫入)+ 內存插入MemTable(內存隨機讀寫可以認為是O(1)的性能),之後批量dump到磁碟(連續文件寫入)。這種LSM-Tree
的設計便是合理的利用了存儲介質的特性,做到了最大化的性能利用(磁碟換成SSD也依舊能有很好的運行效率)。
寫在最後
每天抽出不到半個小時,零零散散地寫了一周,這是說是入門都有些謬讚了,只算是對Linux下的IO機制稍微深入的介紹了一點。無論如何,希望學習完Linux系統編程的同學,能繼續的往下走一走,嘗試理解系統調用背後隱含的機制和原理。探索的結果無所謂,重要的是探索的過程以及相關的學習經驗和方法。前文提出的幾個問題我並沒有刻意去解答所有的,但是讀到現在,不知道你自己能回答上幾個了?
參考文獻
[1] 圖片引自《Computer Systems: A Programmer』s Perspective》Chapter 6 The Memory Hierarchy,
另可參考
https://zh.wikipedia.org/wiki/%E5%AD%98%E5%82%A8%E5%99%A8%E5%B1%B1
[2] Locality of reference,
https://en.wikipedia.org/wiki/Locality_of_reference
[3] 圖片引自《The Linux Programming Interface》Chapter 13 FILE I/O BUFFERING
[4] Linux Storage Stack Diagram,
https://www.thomas-krenn.com/en/wiki/Linux_Storage_Stack_Diagram
[5] O_DIRECT和O_SYNC詳解, http://www.cnblogs.com/suzhou/p/5381738.html
[6] http://librelist.com/browser/usp.ruby/2013/6/5/o-append-atomicity/
[7] Coding for SSD, https://dirtysalt.github.io/coding-for-ssd.html
[8] fio作者Jens Axboe是Linux內核IO部分的maintainer,工具主頁 http://freecode.com/projects/fio/
[9] How to benchmark disk,
https://www.binarylane.com.au/support/solutions/articles/1000055889-how-to-benchmark-disk-i-o
[10] 深入Linux內核架構, (德)莫爾勒, 人民郵電出版社
(完)
查看"Linux閱碼場"精華技術文章請移步:
Linux閱碼場精華文章匯總
掃描二維碼關注
"Linux閱碼場"
推薦閱讀:
※在 Gentoo 中使用 Yubikey PGP 卡
※大牛分享:Linux常用命令都有哪些
※Linux Don』t Need No Stinkin』 ZFS: BTRFS Intro
※[DevOps]Ansible自動化運維入門到實戰(下)
※運用虛擬機搭建本地Hadoop環境
TAG:Linux |