1、一個unix檔案就是一個m位元組的序列(b0b1b2...bm-1)。所有的IO裝置,如網路,磁碟,終端,都被模型化為檔案,而所有的輸入和輸出都被當作對相應檔案的讀和寫來執行。
2、所有的輸入和輸出都被當作統一的方式來處理:
1)開啟檔案。一個應用程式通過要求核心開啟相應的檔案,來宣告它想要訪問一個IO裝置。核心返回一個小的非負整數,叫做描述符,它在後續對此檔案的所有操作中標識這個檔案。核心記錄這個開啟檔案的所有資訊,而應用程式只需記住這個描述符。
Unix建立的每個進程開始都有三個開啟的檔案:標準輸入(描述符為0),標準輸出(描述符1),標準錯誤(描述符為2)。標頭檔unistd.h中定義了相關常量。
2)改變當前的位置。核心保持一個檔案位置k,對於每個開啟檔案,初始為0。這個檔案位置是從檔案開頭起始的位元組位移量。
3)核心釋放檔案開啟時建立的資料結構,並恢複描述符到可獲得描述符池中。
3、相關函數
open,close,read,write。具體定義可以參見man手冊,或相關網頁,或本書11.3。
1)某些情況下,原始的read,write傳送的位元組比應用程式要求的要少,這些不足值(short count)不一定是錯誤(意為,比如read函數參數要讀5個位元組,結果唯讀了3個位元組,即小於5),下面的情況下將可能發生不足值:
(1)讀時遇到EOF。
(2)從終端讀文本行。
(3)讀和寫網路通訊端。
為了避免不足值問題(健壯程式的需求,如Web伺服器這樣的網路應用),就需要處理不足值。本書作者11.4節開發了Rio包滿足這個情況。
4、函數是安全執行緒的:它在同一個描述符上可以被交替地調用。
5、中繼資料:關於檔案的資訊,如建立時間,修改時間,metadata。可以調用stat,fstat函數。
6、對於核心而言,檔案檔案和二進位檔案毫無區別。
7、共用檔案
核心用三種相關的資料結構來表示開啟的檔案。
1)描述符表:它的表項由進程開啟的檔案描述符來索引的。
2)檔案表:開啟檔案的集合是一張檔案表來表示的,所有的進程共用這張表。
3)v-node表:同檔案表一樣,所有的進程共用這張v-node表。每個表項包含stat結構中的大部分資訊,如檔案大小等。
多個描述符也可以通過不同的檔案表項來引用同一個檔案。
展示一個fork建立子進程的情況:子進程有一個父進程描述符的副本;父子進程共用相同的開啟檔案表集合,因此共用相同的檔案位置。注意點:在核心刪除相應檔案表表項前,父子進程必須都關閉了它們的描述符。
8、IO重新導向
unix > ls > foo.txt
實際是dup2函數(one case)。dup2函數對檔案描述符表項進行重定位。
9、標準IO
標準函數如fopen,fclose,fread,fwrite,fgets,fputs,scanf,printf。
標準IO庫將一個開啟的檔案模型化為一個流,對於程式員來說,一個流就是一個指向類型為FILE結構的一個指標。每個ANSI C程式開始都有三個開啟的流stdin,stdout,stderr,分別對應於標準輸入、輸出、錯誤。
類型為FILE的流是對檔案描述符和流緩衝區的抽象。
10、樣本圖
11、標準IO流,從某種意義上而言是全雙工系統的,因為程式能夠在同一個流上執行輸入和輸出。但是有一些限定:
1)限定1:輸入函數跟有輸出函數之後。如果中間沒有fflush,fseek,fsetpos或rewind的調用,一個輸入函數不能跟隨在一個輸出函數之後。fflush清空與流相關的緩衝區,後三個函數使用unix IO lseek函數來重設當前檔案的位置。
2)限定2:輸出函數跟有輸入函數之後。情況類同上。
這些限定給網路應用帶來一個問題:因為對通訊端使用lseek是非法的。用Rio包可以解決這個問題。用sprintf把輸出格式化一個字串,通過rio_writen輸出,傅rio_readlineb來讀一行,用scanf來從文本中提取不同的欄位。