你們知道當程序需要讀取或者寫入數據的時候,CPU是如何操作我們的磁盤的嗎?首先CPU肯定是要把讀寫數據的命令告訴給磁盤,這個命令可以通過IO總線傳給磁盤,那這里有個細節,其實我們常說的磁盤不僅僅是只包含存儲數據的媒介,還有接口,接口相信大家都熟悉,接口的意義不僅僅是為了連接到IO總線上的,其實這個接口里還有個叫做控制器的東西,控制器才是真正控制磁盤讀寫的東西,當CPU發出讀寫指令的時候,這個指令其實是告訴磁盤控制器的。以讀為例,當控制器收到讀的請求時,它告訴磁盤:“你把xx數據給我吧”,當機械硬盤經過轉動、尋道找到目標扇區后,把目標數據給磁盤控制器:“哥,這是你要的數據”,控制器收到數據之后,其實不會立馬通知CPU,因為需要讀的數據可能涉及到多個扇區,如果每讀一個扇區的數據就通知,會導致效率低下。
CPU:“控制器老弟,你這是搞事啊,我很忙的,每次搞這么點數據就通知我,能不能把我需要的數據都準備好,再通知我”。
“控制器”:“好的,CPU老哥”。
于是控制器內部就搞了個緩沖區,把讀到的數據先緩存起來,然后通知CPU來取數據,但是問題又發生了...
CPU:“控制器老弟,數據你是準備好了,但是你給我的數據已經是損壞的,玩我呢!”
“控制器”:“CPU老哥,俺錯了,下次一定不會”。
于是控制器為了判斷讀到的數據是否發生了損壞,會先計算下校驗和,如果校驗和不通過,那么就不會通知CPU來取壞的數據了。
當緩沖區快要滿了或者需要讀的數據已經讀完了并且校驗數據也是OK的,這時控制器就會發出個中斷:“CPU老哥,你要的數據好了,過來取吧”,于是CPU屁顛屁顛的過來拿數據,當然它也是分批拿的,每次從控制器的緩沖區中一個字節一個字節的拿,直至取完。整個過程看起來還不錯,但是有個很嚴重的效率問題:CPU每次取數據的單位有點小(一個字節),這樣勢必造成CPU多次往返,那有什么辦法解決這個問題呢?我們接著往下看。
緩沖
在講緩沖之前,我們先了解一下當我們的程序發出read的時候,數據是怎么返回的,首先和設備打交道的時候,需要發起系統調用,系統調用會導致進入內核態,然后CPU去讀數據,讀到數據后,在把數據返給用戶程序,這時又回到用戶態。
這里我們先著重看下數據從內核態到到用戶態的過程,通過上文我們知道CPU是一個字節一個字節的讀取數據的,當CPU拿到數據之后,可以有這樣幾個選擇:
每次讀到一個字節后立馬發出中斷,然后由中斷程序把每個字節交給用戶進程,用戶進程收到數據之后,再發起下個字節的讀取,就這樣不停的循環...,直至把數據讀完。這種模式的問題在于每個字節都要喚起進程,然后用戶進程繼續阻塞等待下個字節的到來,很傻很低效。
用戶程序可以每次多讀點數據,比如每次告訴CPU:“我要讀n個字節”,CPU收到指令后去磁盤把數據讀到,當然這里肯定不是一個字節一個字節的發起中斷,不然和1無區別,由于一開始已經告訴CPU要讀n個字節,所以要等讀滿n個字節后才能發起中斷,那如何知道讀滿n個字節了呢?這就需要緩沖了,可以在用戶空間開辟一個n個字節的緩沖區,當緩沖區滿了,再發起中斷,相比第一種n次中斷,這里只需要一次中斷,是不是效率提高了許多。
第二種方法解決了用戶程序低效的問題,但是不要忘記了還有CPU,CPU還是一個字節一個字節的把數據搬運到用戶的緩沖區中,這樣看CPU還是挺辛苦的,不僅要讀取數據,還要低效的把數據從內核空間搬運到用戶空間,注意這個在內核空間和用戶空間之間的切換還是挺耗費時間的,于是為了減少切換開銷,內核空間干脆也搞個緩沖區,等緩沖區有足夠多的數據之后,一次性的給到用戶程序,這樣是不是就高效多了。
可以發現最后一種肯定是效率最高的,這也是現代操作系統普遍使用的方式,然而這種模式也不是百分百的完美,我們來看下相關的時序圖。
時序圖中我們先重點看下CPU這塊,可以發現當控制器的緩沖區滿了之后需要CPU把數據copy到內核緩沖區,然后CPU再把內核緩沖區的數據copy到用戶緩沖區,CPU不僅要負責數據的讀寫還要負責數據的搬運。