1. 程式人生 > >系統級I/O與標準I/O--“深入理解計算機系統”

系統級I/O與標準I/O--“深入理解計算機系統”

概念簡介

輸入輸出I/O是在主存和外部裝置(如磁碟,網路和終端)之間拷貝資料的過程。

輸入就是從I/O裝置拷貝資料到貯存,而輸出就是從主存拷貝資料到I/O裝置。

所有語言的執行時系統都提供執行I/O的較高級別的工具。例如,ANSI C提供標準I/O庫,包含像printf和scanf這樣執行帶緩衝區的I/O函式。C++語言用它的過載操作符<<(輸出)和>>(輸入)提供了類似的功能。在UNIX系統中,是通過使用由核心提供的系統級Unix I/O函式來實現這些比較高階的I/O函式的。

開啟和關閉檔案

程序是通過呼叫open函式來開啟一個已存在的檔案或者建立一個新檔案。

系統級I/O--摘自“深入理解計算機系統” - 鼻子很帥的豬 - 鼻子很帥的豬

flags引數表示程序打算如何訪問這個檔案,它的值包括

  • O_RDONLY
  • O_WRONLY
  • O_RDWR

flags引數也可以是一個或者更多位掩碼的或,提供一些額外的指示:

  • O_CREAT
  • O_TRUNC:如果檔案已經存在,就截斷它。
  • O_APPEND

讀和寫檔案

應用程式是通過分別呼叫系統函式 read和write函式來執行輸入和輸出的。

系統級I/O--摘自“深入理解計算機系統” - 鼻子很帥的豬 - 鼻子很帥的豬

size_t是作為usigned int,而ssize_t是作為int。

在某些情況下,read和write傳送的位元組比應用程式要求的要少。出現這種情況的可能的原因有:

  • 讀時遇到EOF?? 假設該檔案從當前檔案位置開始只含有20個位元組,而應用程式要求我們以50個位元組的片進行讀取,這樣一來,這個read的返回的值是20,在此之後的read則返回0.
  • 從終端讀文字行? 如果開啟的檔案是與終端相關聯的,那麼每個read函式將一次傳送一個文字行,返回的不足值等於文字行的大小。(具體的含義可看我以前的文章,關於緩衝區的)
  • 讀和寫socket??? 如果開啟的檔案對應於網路套接字,那麼內部緩衝約束和較長的網路延遲會導致read和write返回不足值。

RIO的無緩衝的輸入輸出函式

rio_readn函式從描述符fd的當前檔案位置最多傳送n個位元組到儲存器位置usrbuf。類似的,rio_writen函式從位置usrbuf傳送n個位元組到描述符fd。rio_readn函式在遇到EOF時只能返回一個不足值。rio_writen函式絕不會返回不足值。具體程式碼如下:

系統級I/O--摘自“深入理解計算機系統” - 鼻子很帥的豬 - 鼻子很帥的豬

注意:如果rio_readn和rio_writen函式被一個從應用訊號處理程式的返回中斷,那麼每個函式都會手動地重啟read或write。

RIO的帶緩衝的輸入輸出函式

一個文字行就是一個由 換行符 結尾的ASCII碼字元序列。在Unix系統中,換行符是‘\n’,與ASCII碼換行符LF相同,數值為0x0a。假設我們要編寫一個程式來計算文字檔案中文字行的數量應該如何來實現呢??嘿嘿這個問題,可是我在微軟面試的時候,面試官給我出的一道考題。

一種方法是用read函式來一次一個位元組地從檔案傳送到使用者儲存器,檢查每個位元組來查詢換行符。這種方法的問題就是效率不高,每次取檔案中的一個位元組都要求陷入核心。

一種更好的方法是呼叫一個包裝函式(rio_readlineb),它從一個內部緩衝區拷貝一個文字行,當緩衝區變空時,會自動的呼叫read系統呼叫來重新填滿緩衝區。

    在帶緩衝區的版本中,每開啟一個描述符都會呼叫一次rio_readinitb函式,它將描述符fd和地址rp處的一個型別為rio_t的讀緩衝區聯絡起來。

    rio_readinitb函式從檔案rp讀取一個文字行(包括結尾的換行符),將它拷貝到儲存器位置usrbuf,並且用空字元來結束這個文字行。

系統級I/O--摘自“深入理解計算機系統” - 鼻子很帥的豬 - 鼻子很帥的豬

?

RIO讀程式的核心是rio_read函式,rio_read函式可以看成是Unix read函式的帶緩衝區的版本。當呼叫rio_read要求讀取n個位元組的時候,讀緩衝區內有rp->rio_cnt個未讀的位元組。如果緩衝區為空的時候,就會呼叫read系統函式去填滿緩衝區。這個read呼叫收到一個不足值的話並不是一個錯誤,只不過讀緩衝區的是填充了一部分。

一旦緩衝區非空,rio_read就從讀緩衝區拷貝n和rp->rio_cnt中較小值個位元組到使用者緩衝區,並返回拷貝位元組的數目。

系統級I/O--摘自“深入理解計算機系統” - 鼻子很帥的豬 - 鼻子很帥的豬

對於應用程式來說,rio_read和系統呼叫read有著相同的語義。出錯時返回-1;在EOF時,返回0;如果要求的位元組超過了讀緩衝區內未讀的位元組的數目,它會返回一個不足值。rio_readlineb函式多次呼叫rio_read函式。每次呼叫都從讀緩衝區返回一個位元組,然後檢查這個位元組是否是結尾的換行符。rio_readlineb函式如下所示:

系統級I/O--摘自“深入理解計算機系統” - 鼻子很帥的豬 - 鼻子很帥的豬

rio_readlineb函式最多讀取(maxlen-1)個位元組,餘下的一個位元組留給結尾的空字元。超過maxlen-1位元組的文字行被截斷,並用一個空字元結束。


標準I/O     ANSI C定義了一組高階輸入輸出函式,成為標準I/O庫,為程式設計師提供了Unix I/O的較高級別的替代。這個庫(libc)提供了開啟和關閉檔案的函式(fopen和fclose)、讀和寫位元組的函式(fread和fwrite)、讀和寫字串的函式(fgets和fputs)、以及複雜的格式化I/O函式(printf和scanf)。     標準I/O庫將一個開啟的檔案模型化為一個流。對於程式設計師而言一個流就是一個指向FILE型別的結構的指標。型別為FILE的流是對檔案描述符和緩衝區的抽象。流的緩衝區的目的和RIO讀緩衝區的目的是一樣的:就是使開銷較高的Unix I/O系統呼叫的次數儘可能的減少。例如,假如我們有一個程式,反覆呼叫標準I/O的getc函式,每次呼叫返回檔案的下一個字元。當第一次呼叫getc函式時,庫函式通過呼叫一次read系統呼叫來填充流緩衝區,然後將緩衝區中的第一個位元組返回給應用程式。只要緩衝區中還有未讀的位元組,接下來對getc函式的呼叫就能直接從流緩衝區中得到服務,而不必去呼叫開銷較高的Unix I/O系統呼叫。     文字流是由一系列行組成的,每一行的結尾是一個換行符。如果系統沒有遵循這種模式,則標準庫將通過一些措施使得該系統適應這種模式。例如,標準庫可以在輸入端將回車符和換頁符都轉換成換行符,而在輸出端進行反向轉換。     最簡單的輸入機制是使用getchar()函式從標準輸入中(一般為鍵盤)一次讀取一個字元,getchar函式在每次被呼叫的時候返回下一個輸入字元。若遇到檔案結尾,則返回EOF。符號常量EOF在標頭檔案stdio.h中定義為-1,但程式中應該使用EOF來測試檔案是否結束,這樣才能保證程式同EOF的特定值無關。