核心態與使用者態、系統呼叫與庫函式、檔案IO與標準IO、緩衝區等概念介紹
概述
Linux提供了兩套可以用於檔案的IO介面:
- 檔案IO: open、create、close、lseek、read、write、fcntl、ioctl等
- 標準IO: FILE、fopen、fwrite、fread、等
為了理解檔案IO和標準IO的區別,可能要先理解下使用者態與核心態,系統呼叫與庫函式的概念。
使用者態和核心態
什麼是使用者態和核心態:
- 核心態: CPU可以訪問記憶體所有資料, 包括外圍裝置, 例如硬碟, 網絡卡. CPU也可以將自己從一個程式切換到另一個程式
- 使用者態: 只能受限的訪問記憶體, 且不允許訪問外圍裝置. 佔用CPU的能力被剝奪, CPU資源可以被其他程式獲取
區分使用者態和核心態的作用:
之所以區分使用者態和核心態,是為了限制不同程式的訪問能力,防止它們獲取隨意獲取其他程式或外圍裝置的資料。
(限制使用者態的訪問能力,還可以防止外圍裝置的訪問衝突。)
我們知道,執行緒
使用者態和核心態的切換:
程式都是工作在使用者態,但是有時候程式需要訪問一些受限的資料(如從硬碟讀資料,從鍵盤獲取輸入等),這是就需要切換到核心態。切換的方式一般時呼叫系統呼叫。使用者態通過系統呼叫通知核心態,需要訪問哪些受限的資料,或操作哪些受限的裝置。核心態就幫助使用者態完成。
小結:
簡單的說,使用者程式只能訪問記憶體,而作業系統能訪問所有資料。使用者程式工作的模式即是使用者態,當用戶程式想要訪問受限的資料時,就需要向作業系統發請求,讓作業系統幫忙完成。 作業系統工作的模式即核心態。
庫函式和系統呼叫
庫函式工作在使用者態,系統呼叫工作在核心態。
所有的作業系統都會提供一些服務用以訪問/操作裝置等,作業系統會為這些服務提供介面。這些介面就是系統呼叫 。使用者態程式通過呼叫系統呼叫可以切換到核心態,訪問記憶體意外的資料,操作外圍裝置等。
不同的作業系統提供的系統呼叫會有所差異。
Unix為每個系統呼叫在標準C庫中設定一個具有同樣名字的函式 。 一般我們是稱這些函式為系統呼叫。(如標準C庫中有函式write(), write()函式直接使用write系統呼叫相應的核心服務)。 所以一般說write(),指的不是write()這個函式,而是系統呼叫write。
標準C庫,或是其他庫會定義一些函式,這些函式稱之為庫函式。很多庫函式是跨平臺的。雖然有些庫函式最終會呼叫系統呼叫,但不少庫函式會根據具體的作業系統選用響應的系統呼叫介面。
有些庫函式僅僅是操作記憶體,不訪問硬碟等外圍裝置的資料,因此最終並不會呼叫系統帶哦用。即:並不是所有的庫函式最終都要使用系統呼叫的, 如:strcpy、atoi等。
I/O的概念
I/O:輸入輸出。
既然是輸入輸出,就要有輸入輸出裝置(一般記憶體不算輸入輸出裝置)。如硬碟、鍵盤、終端等都可以作為輸入輸出裝置。
使用者態是不能訪問硬碟、鍵盤、終端這些外圍(虛擬)裝置的,因此需要切換到核心態。使用者態和核心態的切換是會產生開銷的。
舉個例子,程式要從硬碟上的檔案讀取資料,其過程如下:
- 程式通過系統呼叫(如read)告訴作業系統想要讀取某個硬碟檔案的內容。
- 切換到核心態(這裡需要從使用者態向核心態傳遞,要讀取的是哪個檔案等資訊)
- 系統呼叫(工作在核心態)讀取檔案內容。
- 切換到使用者態(這個過程包含了把系統呼叫讀取到的資料拷貝到使用者態的過程)
- 程式得到資料。
檔案I/O——不帶緩衝的I/O
檔案IO相關的函式open、close、read、write等等
這裡我們主要以write()函式為例,介紹一些檔案IO的特性。
檔案IO是不帶緩衝的,所謂不帶緩衝,即每呼叫一次檔案IO(如write),就進行一次使用者態與核心態的切換。
呼叫一次write,就需要立即把資料從使用者態拷貝到核心態。
標準I/O——帶緩衝的I/O
標準IO相關的函式主要有:fopen、fclose、fread、fwrite、fgetc等等
標準IO是帶緩衝的。
標準IO提供緩衝的目的是儘可能減少使用read和write呼叫的次數(也就是使用者態和核心態切換的次數)。
所謂帶緩衝,其實就是在呼叫fwrite的時候,先把資料放在緩衝區。不直接使用系統呼叫,切換到核心態。而是等到緩衝區滿或是呼叫fflush等條件滿足時,再一次性呼叫write,把資料拷貝到核心空間。
緩衝
緩衝是標準IO庫提供的,緩衝區存在於使用者空間(不管是fread還是fwrite)。 對於fwrite,根據上面的例子很好理解。對於fread而言,就是在呼叫fread的時候,切換到核心態,核心態讀取緩衝去能夠接受的大小的資料(一般會多餘此次fread期望的資料), 然後返回給fread函式。下次再呼叫fread的時候,可能只需要從緩衝區讀取,而不需要再到核心空間拷貝了。
標準IO有三種緩衝型別:
全緩衝:
行緩衝
不帶緩衝
檔案IO與標準IO的一些對比
效率對比
檔案IO不帶緩衝,因此每呼叫write,就需要做使用者態和核心態的切換,把資料從使用者態切換到核心態。
標準IO帶緩衝,呼叫fwrite的時候,先把資料儲存在緩衝區,等待緩衝區滿或呼叫fflush等條件滿足時,再呼叫write,一次性把多次fwrite的資料
因此,從效率上看,帶緩衝的IO作使用者態與核心態切換的次數較少,效率比較高。沒有特殊要求的話,一般應該選用fopen、fwrite系列帶緩充的IO。
檔案IO與標準IO的應用場景——多執行緒日誌系統為例
在多執行緒日誌系統中,每個執行緒開啟一個日誌檔案的描述符。
如果使用帶緩衝的IO,最終可能導致日誌的順序發生錯亂,可能影響閱讀。因此如果對多執行緒的日誌順序有要求的話,可能需要使用不帶緩衝的IO。
另一方面,在測試中發現,使用標準IO時,每次呼叫write的資料並不一定都是多次完整的fwrite的資料。可能會有一些fwrite的資料被分為兩次write。舉個例子,每次呼叫fwrite寫100個位元組的資料,而緩衝區為550個位元組,那麼第6次fwrite的資料可能就被截斷分成兩次write。(這是我在centOS7.2上的測試結論,目前不清楚是否可以配置在fwrite寫緩衝之前先判斷緩衝區剩餘空間是否充足,不足時先呼叫write,再把完整的fwrite資料寫入緩衝)。(就剛剛那個例子來說,就是不知道是否可以配置,在第6次fwrite寫緩衝之前先write前面的500位元組)。