天天看點

csapp第十章 系統級I/O

輸入/輸出(I/O)是在主存和外部裝置之間拷貝資料的過程。

是主存,和外部裝置(磁盤、終端、網絡)之間的。

主存是主體,第一人稱,從外部到主存,是入,輸入。從主存到外部,是出,輸出。

這些外部裝置都是I/O裝置。

從磁盤輸入,是将磁盤檔案拷貝到主存的一個過程。

所有語言的運作時系統都提供執行I/O的較高基本的工具。——語言的運作時系統——程式設計語言嗎?c,c++,java,這些倒是都提供了I/O工具。

ANSI C提供了标準I/O庫——printf和scanf這樣帶緩沖區的I/O函數。——緩沖區,其實好了解,外部裝置和記憶體之間的資料拷貝是緩慢的,而且多次少量(多次在記憶體和外部裝置之間互動少量的資料)比少次多量要花多得多的事件。

緩沖區就是為了将多次少量變成少次多量而存在的。緩沖區是在主存裡面的,它可能是8192個位元組,你從外部裝置一次讀一個位元組,讀個1萬次,如果不帶緩沖區的I/O,那麼就執行一萬次的外部裝置和記憶體之間的拷貝。但是帶了緩沖區,那麼外部裝置和記憶體之間隻互動了兩次,一次8192,一次1808。第一次8192,然後,從這個8192中每次讀一個位元組,讀了8192次,然後,第二次1808,從這個1808中每次讀一個位元組,總共1萬次。

C++中有>>和<<。

c和c++的進階I/O工具都是使用Unix I/O函數來實作的。

學習系統級I/O,也就是Unix I/O的原因是:

  • 了解Unix I/O将幫助你了解其他的系統概念
  • 有時你除了使用Unix I/O以外别無選擇

10.1 Unix I/O

所有的I/O裝置都被模型化為檔案。

包括:磁盤,網絡,終端。

仔細想想:磁盤是磁盤控制器連在主機闆上的,網絡是通過網絡擴充卡連在主機闆上的,終端是什麼?這麼想,想不通,想不通就不想,當作概念好了。

  • 打開檔案——一個應用程式通過要求核心打開相應的檔案。——檔案操作總是核心做的。應用隻是告訴核心做什麼。——檔案打開後,應用程式記錄描述符,核心記錄所有和檔案相關的資訊。
    • 每個程序開始的時候都有3個打開的檔案:0,1,2,入,出,錯出。
  • 改變目前的檔案位置——對于每個打開的檔案,核心記錄其所有相關的資訊,其中一個是檔案位置,初始為0。——從檔案開頭起始的位元組偏移量。
  • 讀檔案——一個讀操作就是從檔案拷貝n個位元組到存儲器,從目前檔案位置k開始,然後将k增加到k+n。
    • 給定的一個大小為m位元組的檔案,當k>=m時,執行讀操作會觸發一個稱為EOF的條件,應用程式能檢測到這個條件。——EOF是一個條件,不是一個位元組,不是一個位,在檔案的結尾處什麼都沒有,也沒有EOF符号,EOF是一個條件。
  • 寫操作——從存儲器拷貝n>0個位元組到一個檔案,從目前檔案k開始,然後更新k。
  • 關閉檔案——應用程式通知核心,讓核心去關閉檔案。核心會釋放其記錄的關于檔案的所有。

10.2 打開和關閉檔案

程序通過open函數來打開一個已存在的檔案或者建立一個新檔案。

int open(char *filename, int flags, int mode);

flag有:O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_TRUNC, O_APPEND。

每個程序都有一個 umask,這是上下文的一部分。

open函數建立一個新檔案時,檔案的通路權限位被設定成mode & ~umask。

思考:如果mode為0,那麼檔案的通路權限是~umask,在我的linux上,終端裡輸入umask,是0022,取非,是7755?不知道。

int close(int fd);

10.3 讀和寫檔案

應用程式通過分别調用read和write函數來執行輸入和輸出。

  • ssize_t read(int fd, void *buf, size_t n);
  • ssize_t write(int fd, const void *buf, size_t n);

read函數調用傳回有三種情況:

  • -1——錯誤。
  • 0——調用的時候,觸發了k>=m的EOF條件,應該這麼說,調用的時候,目前位置k已經在檔案結尾了。
  • 實際傳送的位元組數——這裡又有兩種情況,一種是n,也就是實際傳送了n個;另一種是小于n的,也就是實際沒有傳送到n那麼多,導緻這種情況的原因可能就是從目前位置到檔案尾部之間的位元組數沒有n那麼多了(在這種情況下,下一次再read,直接就傳回0了)。

如果打開檔案是與終端相關聯的,那麼每個read函數将一次傳送一個文本行,傳回的不足值對于文本行的大小。

10.4 用RIO包健壯地讀寫

Robust I/O,RIO提供了兩類不同的函數:

  • 無緩沖的輸入輸出函數——這些函數直接在存儲器和檔案之間傳送資料,沒有應用級緩沖。當檔案是網絡時,尤其有用。
  • 帶緩沖的輸入函數——檔案的内容緩存在應用級緩沖區内。

無緩沖的函數:

  • ssize_t rio_readn(int fd, void  *usrbuf, size_t n);
  • ssize_t rio_writen(int fd, void *usrbuf, size_t n);

rio_readn比read好的地方在于:信号中斷會重新開機。

帶緩沖的輸入函數(沒有輸出函數)

結構rio_t。

  • void rio_readinitb(rio_t *rp, int fd);
  • static ssize_t rio_read(rio_t *rp, char *usrbuf, size_t n);
  • ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen);
  • ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n);

10.5 讀取檔案中繼資料

檔案的中繼資料,就是檔案的相關資訊。

  • int stat(const char *filename, struct stat *buf);
  • int fstat(int fd, struct stat *buf);

結構stat中,我們關心的,是st_mode和st_size。st_mode編碼了檔案通路許可位和檔案類型。

Unix識别大量不同的檔案類型。

  • 普通檔案:文本檔案和二進制檔案。
  • 目錄檔案
  • 套接字

10.6 共享檔案

核心用三個相關的資料結構來表示打開的檔案:描述符表,檔案表,v-node表。

描述符表是每個程序都有的。檔案表和v-node表是所有程序共享的。

描述符表的每個條目是由檔案描述符索引的,條目值指向檔案表中的一項。

檔案表的條目包含一個檔案的目前位置,引用計數,以及一個指向v-node表中對應表項的指針。

也就是說,如果兩個程序的描述符各自有一個條目指向檔案表中的一個,那麼這兩個程序就共享了檔案的目前位置這一屬性。一個read了,目前位置變了,另一個讀的時候,這種變化會展現出來。

v-node表,包含了檔案的中繼資料,也就是,stat結構的内容。

一次open,将在檔案表中建立一個新的條目,将在描述符表中建立一個新的描述符指向這個檔案表中的條目。

如果已open之後,程序fork了,那麼父子程序都擁有相同的描述符内容,指向的也會是檔案表中相同的條目,也就是共享了該檔案的目前位置。

兩次open同一個檔案,雖然是同一個檔案,但是有兩個描述符條目,指向的也是檔案表中的兩個條目,雖然檔案表中的兩個條目指向v-node表中的一個條目,但是,檔案表兩個條目是關鍵。

10.7 I/O重定向

I/O重定向的一個方式是使用dup2函數。

int dup2(int oldfd, int newfd);

oldfd将覆寫newfd。也就是說newfd将指向和oldfd相同的檔案表的某一項,oldfd保持不變。再簡單點——後者變成和前者一樣的了。

10.8 标準I/O

ANSI C定義了一組進階輸入輸出函數,稱為标準I/O庫。

标準I/O庫将一個打開的檔案模型化為一個流。

對于程式員而言,一個流就是一個指向FILE類型的結構的指針。

類型為FILE的流是對檔案描述符和流緩沖區的抽象。——這裡,和前面的rio_t有點類似了,FILE将檔案描述符和緩沖區合在了一起,而rio_t将檔案描述符和緩沖區合在了一起,還是很類似的。

10.9 綜合:我該使用那些I/O函數

已知三種:Unix I/O,RIO,标準I/O。

我們建議:在網絡套接字上不要使用标準I/O函數來繼續輸入輸出。而使用RIO。

這裡的原因,暫時看不懂。

10.10 小結

(over)

轉載于:https://www.cnblogs.com/rayhill/archive/2012/06/01/2529063.html