1. 程式人生 > >每天3分鐘作業系統修煉祕籍(13):兩個緩衝空間Kernel Buffer和IO Buffer

每天3分鐘作業系統修煉祕籍(13):兩個緩衝空間Kernel Buffer和IO Buffer

點我檢視祕籍連載

兩個緩衝空間:kernel buffer和io buffer

先看一張圖,稍後將圍繞這張圖展開描述。圖中的fd table、open file table以及兩個inode table都可以不用理解,只需要知道它們體現出來的檔案描述符和磁碟檔案之間的對應關係:檔案描述符fd(例如圖中的fd=3)是對應磁碟上檔案的。

在Linux下,我們經常會在IO操作時不可避免的涉及到檔案描述符,因為Linux下的所有IO操作都是通過檔案描述符來完成的。但是,檔案描述符是一個非常底層的概念,通過它操作的資料,都是二進位制資料,所以通過檔案描述符完成IO的模式通常也稱為裸IO(Raw IO)。而且,直接通過底層的檔案描述符進行程式設計會比較麻煩,因為是二進位制資料,它缺少很多功能,比如無法指定編碼,無法指定換行符(換行符有多種:\n、\n\r、\r)等等。注意fd是使用者空間的,它僅僅是一個數值而已,並不是想象中感覺比較底層就在核心空間。

所以,現代高階語言(比如C、Python、Java、Golang)都提供了比檔案描述符更高一層次的標準IO庫,比如C的標準IO庫是stdio,Python的標準IO庫是IO模組,等等。使用這些標準IO庫中的函式進行IO操作時,都會使用比檔案描述符更高一層次的物件,例如C中稱為IO流(io stream),其它面向物件的語言中一般稱為IO物件,為了方便說明,這裡統稱為IO物件。上圖中的F就是檔案物件。

標準IO庫可以看作是檔案描述符的更高層次的封裝,提供了比檔案描述符操作IO更多的功能。例如,可以在IO物件上指定編碼、指定換行符,此外還在使用者空間提供了一個標準IO庫的緩衝空間,通常可稱為stdio buffer或IO buffer,而這些功能在檔案描述符上都是沒有的。另外,標準IO庫既然是高層封裝,當然也會提供使用者不使用這些功能(比如不使用IO Buffer),而是直接使用檔案描述符,那麼這時候的檔案物件就相當於是檔案描述符了,這時候的IO操作模式也就是裸IO模式。

所有從硬體讀取或寫入到硬體的資料,預設都會經過作業系統維護的這個Kernel Buffer。正如上圖中描述的是讀資料過程。

例如,cat程序想要讀取a.log檔案,cat程序是使用者空間程序,它自身沒有許可權開啟檔案以及讀檔案資料,它只能通過系統呼叫的方式陷入核心,請求作業系統幫助讀取資料,作業系統讀取資料後會將資料放入到page cache(剛才已說明,對於普通檔案維護的Kernel buffer稱為page cache或buffer cache)。然後還要將核心空間page cache中的資料拷貝到使用者空間的IO Buffer緩衝空間(因為cat程式的原始碼中使用了標準IO庫stdio),然後cat程序從自己的IO Buffer中讀取資料。這就是整個讀資料的過程。

需要注意的是,雖然這兩段緩衝空間都在記憶體中,但仍然有拷貝操作,因為核心的記憶體空間和使用者程序的虛擬記憶體空間是隔離的,使用者空間程序沒有許可權訪問到核心空間的記憶體,但是核心具有最高許可權,允許訪問任何記憶體地址。換句話說,在將Kernel Buffer的資料拷貝到IO Buffer空間的過程中,需要陷入到核心,OS需要掌控CPU。

此外,Linux也提供了所謂的直接IO模式,只需使用一個稱為O_DIRECT的標記即可,這時會繞過Kernel Buffer,直接將硬體資料拷貝到使用者空間。雖然看上去直接IO少了一個層次的參與感覺效能會更優秀,但實際上並非如此,作業系統為核心緩衝空間做了非常多的優化,使得並不會因此而降低效能。最典型且常見的一個優化是預讀功能,它表示在讀資料時,會比所請求要讀取的資料量多讀一點放入到Kernel Buffer,這樣在下次讀取接下來的一段資料時可以直接從Kernel Buffer中取資料,而無需再和硬體IO互動。所以,使用直接IO模式的場景是非常少的,一般只會在自帶了完整緩衝模型的大型軟體(比如資料庫系統)上可能會使用直接IO模式。

上面所描述的都是讀操作,關於寫操作,這裡不再多花篇幅去描述,整體過程和讀是類似的,都會經過IO Buffer和Kernel Buffer,只是其中一些細節有所不同,如果感興趣,可以閱讀《Linux/Unix系統程式設計手冊》的第13章