歡迎光臨
每天分享高質量文章

聊聊Linux IO(上)

寫在前面

在開始正式的討論前,我先丟擲幾個問題:

  • 談到磁盤時,常說的HDD磁盤和SSD磁盤最大的區別是什麼?這些差異會影響我們的系統設計嗎?

  • 單執行緒寫檔案有點慢,那多開幾個執行緒一起寫是不是可以加速呢?

  • write(2)函式成功傳回了,資料就已經成功寫入磁盤了嗎?此時設備斷電會有影響嗎?會丟失資料嗎?

  • write(2)呼叫是原子的嗎?多執行緒寫檔案是否要對檔案加鎖?有沒有例外,比如O_APPEND方式?

  • 坊間傳聞,mmap(2)的方式讀檔案比傳統的方式要快,因為少一次拷貝。真是這樣嗎?為什麼少一次拷貝?

如果你覺得這些問題都很簡單,都能很明確的回答上來。那麼很遺憾這篇文章不是為你準備的,你可以關掉網頁去做其他更有意義的事情了。如果你覺得無法明確的回答這些問題,那麼就耐心地讀完這篇文章,相信不會浪費你的時間。受限於個人時間和文章篇幅,部分議題如果我不能給出更好的解釋或者已有專業和嚴謹的資料,就只會給出相關的參考文獻的鏈接,請讀者自行參閱。

言歸正傳,我們的討論從儲存器的層次結構開始。

儲存器的金字塔結構

受限於儲存介質的存取速率和成本,現代計算機的儲存結構呈現為金字塔型[1]。越往塔頂,存取效率越高、但成本也越高,所以容量也就越小。得益於程式訪問的區域性性原理[2],這種節省成本的做法也能取得不俗的運行效率。從儲存器的層次結構以及計算機對資料的處理方式來看,上層一般作為下層的Cache層來使用(廣義上的Cache)。比如暫存器快取CPU Cache的資料,CPU Cache L1~L3層視具體實現彼此快取或直接快取記憶體的資料,而記憶體往往快取來自本地磁盤的資料。

本文主要討論磁盤IO操作,故只聚焦於Local Disk的訪問特性和其與DRAM之間的資料交互。

無處不在的快取

如圖,當程式呼叫各類檔案操作函式後,用戶資料(User Data)到達磁盤(Disk)的流程如圖所示[3]。圖中描述了Linux下檔案操作函式的層級關係和記憶體快取層的存在位置。中間的黑色實線是用戶態和內核態的分界線。

從上往下分析這張圖,首先是C語言stdio庫定義的相關檔案操作函式,這些都是用戶態實現的跨平臺封裝函式。stdio中實現的檔案操作函式有自己的stdio buffer,這是在用戶態實現的快取。此處使用快取的原因很簡單——系統呼叫總是昂貴的。如果用戶代碼以較小的size不斷的讀或寫檔案的話,stdio庫將多次的讀或者寫操作通過buffer進行聚合是可以提高程式運行效率的。stdio庫同時也支持fflush(3)函式來主動的掃清buffer,主動的呼叫底層的系統呼叫立即更新buffer里的資料。特別地,setbuf(3)函式可以對stdio庫的用戶態buffer進行設置,甚至取消buffer的使用。

系統呼叫的read(2)/write(2)和真實的磁盤讀寫之間也存在一層buffer,這裡用術語Kernel buffer cache來指代這一層快取。在Linux下,檔案的快取習慣性的稱之為Page Cache,而更低一級的設備的快取稱之為Buffer Cache. 這兩個概念很容易混淆,這裡簡單的介紹下概念上的區別:Page Cache用於快取檔案的內容,和檔案系統比較相關。檔案的內容需要映射到實際的物理磁盤,這種映射關係由檔案系統來完成;Buffer Cache用於快取儲存設備塊(比如磁盤扇區)的資料,而不關心是否有檔案系統的存在(檔案系統的元資料快取在Buffer Cache中)。

綜上,既然討論Linux下的IO操作,自然是跳過stdio庫的用戶態這一堆東西,直接討論系統呼叫層面的概念了。對stdio庫的IO層有興趣的同學可以自行去瞭解。從上文的描述中也介紹了檔案的內核級快取是儲存在檔案系統的Page Cache中的。所以下篇的討論基本上是討論IO相關的系統呼叫和檔案系統Page Cache的一些機制。

(未完待續…)

赞(0)

分享創造快樂