歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

Linux探秘之I/O效率

一、文章來由

  最近看了《UNIX環境高級編程》,對以前比較模糊的一些知識結構又做了進一步的加強,特別是前兩章講到不帶緩沖的文件I/O和帶緩沖的標准I/O,對read、write、fread、fwrite、printf等等這些函數又有了新的認識。一個很大的感受是我們很多時候編程開發都只注重上層邏輯,雖然一個項目接一下項目,看上去做了不少事,但是夜深人靜時仔細一想,究竟我們是否真正掌握了這些知識點,對於每一個知識點實現的機制我們是否能完整地說出來。這些東西最能體現一個人的基礎知識是否扎實,我發現互聯網公司的面試中最喜歡問這些基礎知識,由一個很基本的函數都會層層遞進引申出很多的問題。很多時候我們內心可能會很排斥,甚至不屑於這些基礎知識,想著等用到的時候,我再來查,我就專注上層邏輯就好了,這樣有助於提升我的開發效率。這樣的想法貌似也沒什麼錯,但是往往這就是瓶頸的來源,程序員最可怕的就是遇到瓶頸了。因為瓶頸這個東西是很難意識到的,一味追求實踐而放棄理論學習,很容易就遇到瓶頸。(個人見解,不喜勿噴)

  本文算是自己看完《UNIX環境高級編程》文件I/O和標准I/O兩章的讀書筆記,文件I/O一章說不帶緩沖,但後面又出現可帶緩沖,搞得我有點暈,特意記下自己對此的理解。如果有什麼不對的,歡迎指出,如果你覺得本文對你有幫助,就動動手指推薦下,或者是粉我下,你的關注是我寫作的最大動力。^_^

UNIX環境高級編程中文第二版PDF高清版 下載地址  http://www.linuxidc.net/thread-2063-1-1.html

二、緩沖機制

  眾所周知,CPU和內存的數據交換要遠大於磁盤操作,通過緩存機制,可以減少磁盤讀寫的次數,提高並發處理程序的效率,因此,緩存是一種提高任務存儲和處理效率的有效方法。我們很多時候可以看到,緩存不單單在操作系統方面被采用,更是在Web技術、服務器端、分布式系統等領域發揮著及其重要的作用。

  從宏觀上看,Linux操作系統分為用戶態和內核態,在處理I/O操作的時候,兩者都提供了緩存。用戶態的稱為標准I/O緩存,也稱為用戶空間緩存,而內核態的稱為緩沖區高速緩存,也叫頁面高速緩存。既然都提供了緩存,那為什麼這本書上卻分不帶I/O的緩存和帶I/O的緩存,原因其實是“不帶I/O緩存”指的是用戶空間中不為這些I/O操作設有緩沖,而內核是帶緩沖的,這樣來看,就不會糊塗了。

三、系統I/O和標准I/O

  系統I/O,又稱文件I/O,或是內核態I/O,引用文件的方式是通過文件描述符,一個文件對應一個文件描述符。一個文件描述符用一個非負整數表示,0、1、2系統默認表示標准輸入、標准輸出、標准錯誤,某些UNIX系統規定了描述符的上限值OPEN_MAX,這些常量都定義在頭文件<unistd.h>中。當讀或寫一個文件時,使用open或create系統調用返回的文件描述符標識該文件,並將其作為參數傳遞給read或write系統調用。

#include <unistd.h>
ssize_t read(int filedes, void *buf, size_t nbytes);
ssize_t write(int filedes, const void *buf, size_t nbytes);

   標准I/O,又叫用戶態I/O,引用文件的方式則是通過文件流(stream),一般用fopen和freopen函數打開一個流,返回一個指向FILE對象的指針,其他函數如果要引用這個流,則將FILE指針作為參數傳遞。一個進程預定義了三個流,並且這三個流自動被進程使用,它們是標准輸入流、標准輸出流和標准出錯流,這三個流和系統I/O所規定的三個文件描述符所引用的文件相同。當讀或寫一個文件時,不像系統I/O,僅定義了read和write兩個系統調用函數,標准I/O定義了多個函數,程序員可以根據自己的需求靈活使用。這些函數可以分為每次一個字符的I/O,每次一行的I/O和直接I/O(或者二進制I/O、一次一個對象I/O、面向記錄的I/O、面向結構的I/O)。

1)每次一個字符的I/O

#include<sdio.h>

/* 輸入函數 */
int getc(FILE *fp) -> 宏
int fgetc(FILE *fp) -> 函數
int getchar(void) 等價於getc(stdin)

/* 輸出函數 */
int putc(int c, FILE *fp)
int fputc(int c, FILE *fp)
int putchar(int c) 等效於putc(c, stdout)

2)每次一行I/O

#include <stdio.h>

/* 輸入函數 */
char *fgets(char *restrict buf, int n, FILE *restrict fp)
char *gets(char *buf)

/* 輸出函數 */
int fputs(cont char *restrict str, FILE *restrict fp)
int puts(const char *str) 

3)直接I/O

#include <stdio.h>

size_t fread(void *restrict ptr, size_t size, size_t nobj, FILE *restrict fp)

size_t fwrite(const void *restrict ptr, size_t size, size_t nobj, FILE *restrict fp)

  到此,我們大概了解了系統I/O和標准I/O引用文件的方法,以及一些常用的I/O函數。下面通過一個圖來詳細看下當用戶調用一個I/O函數時,用戶態和內核態的一個執行流程是什麼樣的,進一步了解緩存在I/O操作中的作用,以及用戶態I/O和內核態I/O在執行效率上的區別。

四、I/O操作的流程

  如上圖所示,用戶進程空間和內核進程空間讀寫磁盤的操作都要經過緩沖區緩存,緩存的作用前面也提到過,是為了減少磁盤讀寫的次數,提高I/O的效率。當讀寫一個文件時,首先看系統I/O的操作流程。

1、系統I/O: 屬於內核系統調用,沒有涉及用戶態的參與。以圖中標號為例:

(3) 調用write函數向文件中寫數據,buf中存放的就是要寫入的數據,如write(fd, 'abc', 3)。調用前需要先設置BUFFSIZE。不同的BUFFSIZE會影響I/O效率,下面再來說這個問題。

(5) 延遲寫:當緩存區高速緩存滿或者內核要重寫緩沖區的時候,才將數據寫入輸出隊列,等數據到隊列首部的時候,才真正觸發磁盤的寫操作。

(6) 預讀:當檢測到正進行順序讀取時,內核就試圖讀入比應用程序所要求更多的數據,並假想應用程序很快就會讀到這些數據。這樣,當緩沖區沒有數據時,能夠快速填充下次要讀取的數據。

(4) 調用read從緩沖區高速緩存讀取所需數據到邏輯單元中進行處理。

以上,就是系統I/O所涉及到的四步操作。

2、標准I/O:屬於ISO C實現的標准庫函數,調用的是底層的系統調用。

(1) 將邏輯單元中的數據寫入文件,根據需求,有三種函數類型可以調用,以fputc、fputs、fwrite為例,這些函數不用人為去控制緩沖區的大小,而是系統自動申請的,當用戶定義了相應的I/O函數之後,根據不同的緩存類型(是全緩沖、行緩沖還是無緩沖),系統自動調用malloc等函數申請緩沖區,即標准I/O緩存。

(3)(5) 當用戶緩沖區滿了之後,如系統I/O操作一般,此時調用write從標准I/O緩存中復制數據到內核緩沖區,再寫入磁盤。

(4)(6) 同系統I/O操作,從內核緩沖區調用read讀入到用戶緩沖區。

(2) 同樣有三種函數類型可以調用,以fgetc、fgets、fread為例,讀入邏輯單元進行後續的處理。

可見,標准I/O實現的機制就是基於系統I/O,這樣看來,標准I/O在效率上肯定不如系統I/O,但事實是標准I/O與系統I/O相比並不慢很多,而且還有很多其他的優點,下面一一述說(本篇文章最重要的就是下一小節)。

五、I/O效率

  系統I/O效率受限於read、write系統調用的次數,而系統調用次數則又受限於內核緩沖區的大小,即BUFFSIZE,通過設置不同的BUFFSIZE,系統CPU時間是不同的,其最小值出現在BUFFSIZE=4096處,原因是該測試所采用的是Linux ext2文件系統,其塊長為4096字節,也即緩沖區所能申請到的最大緩沖區大小,��們把4096字節看做是本次最佳I/O長度。如果繼續擴大緩沖區大小,對此時間幾乎沒有影響。所以,對於系統I/O操作,一個最大的問題就是:需要人為控制緩存的大小及最佳I/O長度的選擇,另外就是系統調用與普通函數調用相比通常需要花費更多的時間,因為系統調用具體內核要執行這樣的操作:1)內核捕獲調用,2)檢查系統調用參數的有效性,3)在用戶空間和內核空間之間傳輸數據。

  因此,引入標准I/O的目的就是為了通過標准I/O緩存來避免BUFFSIZE選擇不當而帶來的頻繁的系統調用。根據用戶不同的需求,選擇不同的I/O函數,然後根據不同的緩存類型,自動調用malloc等緩存分配函數分配合適的緩存,等分配的緩存滿之後,再調用系統I/O從標准I/O緩存向內核緩存拷貝數據,這樣就進一步減少了系統調用的次數。

  但是不同的標准I/O函數,不同的緩存類型也會帶來不同的效率。如上圖,當選擇系統最佳I/O長度,即BUFFSIZE的大小和文件系統的塊長一致,可以得到最佳的時間。當選用標准I/O函數時,每次一個字符函數fgetc、fputc和每次一行函數fgets、fputs函數相比要花費較多的CPU時間,而每次單個字節調用系統I/O則花費更多的時間,如果是一個100M的文件,則要執行大概2億次函數調用,也就引起2億次系統調用(從用戶緩沖區到內核緩沖區,再到磁盤),而fgetc版本也執行了2億次函數調用,但只引起了大約25222次系統調用,所以,時間就大大減少了。

  綜合以上,標准I/O函數雖然基於系統I/O實現,但很大程度上減少了系統調用的次數,而且不用人為關心緩沖區大小的選擇,整體上提高了I/O的效率。另外,標准I/O提供了多種緩存類型,方便程序員根據不同的應用需求選擇不同的緩存要求,提高了編程的靈活性,當選擇無緩存時,就相當於直接調用系統I/O。

  OK,大概的內容就以上這些,當然關於I/O操作這塊還有很多需要注意的點,而且還有很多更加高級的I/O函數,這些在後面遇到再來做總結。最後,如果您覺得這篇文章對您有幫助就粉我吧,還是那句話,你的關注是我寫作的最大動力。

UNIX環境高級編程中文第二版PDF高清版 下載地址  http://www.linuxidc.net/thread-2063-1-1.html

Unix環境高級編程 源代碼地址 http://www.linuxidc.com/Linux/2011-04/34826.htm

Unix環境高級編程源碼編譯 http://www.linuxidc.com/Linux/2011-09/42503.htm

apue.h頭文件(Unix環境高級編程) http://www.linuxidc.com/Linux/2012-01/51729.htm

《Unix環境高級編程》(第二版)apue.h的錯誤 http://www.linuxidc.com/Linux/2011-04/34662.htm

Unix環境高級編程第二版讀書筆記 http://www.linuxidc.com/Linux/2011-04/34235.htm

《Unix環境高級編程》中apue.h的問題 http://www.linuxidc.com/Linux/2013-01/77686.htm

Copyright © Linux教程網 All Rights Reserved