學習計時:共12小時
讀書:5小時
代碼:2小時
作業:3小時
部落格:2小時
附錄A 錯誤處理

A.1 Unix系統中的錯誤處理
A.2 錯誤處理包裝函數
Unix:傳回一個錯誤,包裝函數列印一條消息然後退出。
Posix:錯誤傳回碼中不會包含有用的結果,成功時傳回void
系統級I/O
輸入/輸出是在主存和外部裝置(如磁盤驅動器、終端和網絡)之間拷貝資料的過程。輸入操作時從I/O裝置拷貝資料到主存,而輸出操作時從主存拷貝資料到I/O裝置。
10.1 Unix I/O
一個Unix檔案就是一個m個位元組的序列:B0,B1,B2,B3...Bk...Bm-1。
所有的I/O裝置,如網絡、磁盤盒終端,都被模型化為檔案,而所有的輸入和輸出都被當做對相應的檔案的讀和寫來執行。這是一種應用接口,稱為Unix I/O,這使得所有的輸入和輸出都能以一種統一且一緻的方式來執行:
1.打開檔案
一個應用程式通過要求核心打開相應的檔案,來宣告它想要通路一個I/O裝置,核心傳回一個小的非負整數,叫做描述符。unix系統建立每個程序的時候都有三個打開的檔案:标準輸入;标準輸出,标準錯誤。
2.改變目前的檔案位置
核心保持一個檔案位置k,初始為0
這個檔案位置是從檔案開頭起始的位元組偏移量
3.讀寫檔案
一個讀操作就是從檔案拷貝n>0個位元組到存儲器,從目前檔案位置k開始,然後将k增加到k+n。給定一個大小為m位元組的檔案,當k>=m時執行讀操作會觸發一個稱為end-of -file(EOF)的條件,應用程式能檢測到這個條件。在檔案結尾處并沒有明确的“EOF”符号。
4.關閉檔案
核心釋放檔案打開時建立的資料結構,并恢複描述符到可獲得描述符池中。
10.2 打開和關閉檔案
程序是通過調用open函數來打開一個已存在的檔案或是建立一個新檔案:
include <sys/types.h>
include <sys/stat.h>
include <fcntl.h>
int open(char *filename, int flags, mode_t mode);
傳回:若成功則為新檔案描述符,若出錯則為-1。
打開檔案
fd = open(char *filename, int flags, mode_t mode);
對等于
("檔案名",flag位——表示通路方式及額外提示,mode參數)
mode參數指定新檔案的通路權限位。作為上下文的一部分,每個程序都有一個umask;當程序通過帶某個帶mode參數的open函數用來建立一個新檔案的時候,檔案的通路權限位被設定為mode & ~umask。
flag參數可以是一位或者多位掩碼的或。
flags參數
O_RDONLY:隻讀
O_WRONLY:隻寫
O_RDWR:可讀可寫
O_CREAT:如果檔案不存在,就建立它的一個截斷(空)檔案
O_TRUNC:如果檔案已存在,就截斷它
O_APPEND:在每次寫操作前,設定檔案設定到檔案的結尾處
關閉檔案
10.3 讀和寫檔案
應用程式是通過分别調用read和write函數來執行輸入和輸出的。
include <unistd.h>
ssize_t read(int fd,void *buf,size_t n);//傳回值:成功為讀的位元組數,若EOF為0,出錯為-1
ssize_t write(int fd,const void *buf,size_t n);//傳回值成功為寫的位元組數,出錯為-1
read函數從描述符為fd的目前檔案位置拷貝最多n個位元組到存儲器位置buf,傳回值-1表示一個錯誤。而傳回值0表示EOF。否則,傳回值表示的是實際傳送的位元組數量。
write函數從存儲器位置buf拷貝至多n個位元組到描述符fd的目前檔案位置。
在某些情況下,read和write傳送的位元組比應用程式要求的要少,這些不足值不表示有錯誤。原因如下:
讀時遇到EOF。假設我們豬呢比讀一個檔案,該檔案從目前檔案位置開始隻含有20多個位元組,而我們以50個位元組的片進行讀取。這樣一來,下一個read傳回的不足值為20,此後的read将通過傳回不足值0來發出EOF信号。
從終端讀文本行。如果打開檔案是與終端相關聯的(如鍵盤和顯示器),那麼每個read函數将以此傳送一個文本行,傳回的不足值等于文本行的大小。
讀和寫網絡套接字。如果打開的檔案對應于網絡套接字,那麼内部緩沖限制和較長的網絡延遲會引起read和write傳回不足值。對Unix管道調用read和write時,也有可能出現不足值,這種程序間的通信機制不在我們讨論的範圍之内。
10.4 用RIO包健壯地讀寫
RIO包會自動處理不足值。RIO提供了兩類不同的函數:
無緩沖的輸入輸出函數。這些函數直接在存儲器和檔案之間傳送資料,沒有應用級緩沖,他們對将二進制資料讀寫到網絡和從網絡讀寫二進制資料尤其有用。
帶緩沖的輸入函數。這些函數允許你高效地從檔案中讀取文本行和二進制資料,這些檔案的内容緩存在應用級緩沖區内,類似于像printf這樣的标準I/O函數提供的緩沖區。是線程安全的,它在同一個描述符上可以被交錯地調用。例如,可以從一個描述符中讀一些文本行,然後讀取一些二進制資料,接着再多讀取一些文本行。
RIO的無緩沖的輸入輸出函數
rio_readn函數從描述符fd的目前檔案位置最多傳送n和位元組到存儲器位置usrbuf。類似地,rio_writen函數從位置usrbuf傳送n個位元組到描述符fd。rio_readn函數唉遇到EOF時隻能傳回一個不足值。rio_writen函數絕不會傳回不足值。對于同一個描述符,可以任意交錯地調用rio_readn和rio_writen。
RIO的帶緩沖的輸入函數
一個文本行就是一個由換行符結尾的ASCII碼字元序列。在Unix系統中,換行符(‘\n')與ASCII碼換行符(LF)相同,數字值為0x0a。另一種方法是調用一個包裝函數(rio_readlineb),它從一個内部讀緩沖區拷貝一個文本行,當緩沖區變空時,會自動地調用read重新填滿緩沖區。對于既包含文本行也包含二進制資料的檔案,我們也提供了一個rio_readn帶緩沖區的版本,叫做rio_readnb,它從和rio_readlineb一樣的讀緩沖區中傳送原始位元組。
每打開一個描述符都會調用一次該函數,它将描述符fd和位址rp處的類型為rio_t的緩沖區聯系起來。
從檔案rp中讀取一個文本行(包括結尾的換行符),将它拷貝到存儲器位置usrbuf,并用空字元來結束這個文本行。
從檔案rp中最多讀n個位元組到存儲器位置usrbuf。對同一描述符,rioreadnb和rioreadlineb的調用可以交叉進行。
while函數:如果緩沖區為空,先調用函數填滿緩沖區再讀資料
10.5 讀取檔案中繼資料
應用程式能夠通過調用stat和fstat函數,檢索到關于檔案的資訊(中繼資料)。
stat函數以檔案名作為輸入
fstat函數以檔案描述符作為輸入
stat資料結構中的重要成員:st_mode,st_size
st_size成員包含了檔案的位元組數大小
st_mode成員編碼了檔案通路許可位和檔案類型
10.6 共享檔案
表示打開檔案的三個資料結構:
描述符表。每個程序都有獨立的描述符表;它的表項是由程序打開的檔案描述符來索引的。
v-node表。所有程序共享。每個表項包含stat結構中的大多數資訊。
檔案表。表示打開檔案的集合;所有的程序共享。表項有:檔案位置、引用計數、指向v-node表中對應表項的指針。
三種打開檔案的類型:
典型:描述符各自引用不同的檔案,沒有共享
共享:多個描述符通過不同的檔案表表項引用同一個檔案。
繼承:子程序繼承父程序打開檔案。
10.7 I/0重定向
Unix外殼提供了I/O重定向操作符,允許使用者将磁盤檔案和标準輸入輸出聯系起來
重定向使用dup2函數:int dup2(int oldfd,int newfd);
dup2函數拷貝描述符表表項oldfd到描述符表表項newfd,覆寫描述表表項newfd以前的内容。
如果newfd已經打開,dup2會在拷貝oldfd之前關閉newfd
10.8 标準I/O
ANSI C定義了一組進階輸入輸出函數,稱為标準I/O庫。提供了打開和關閉檔案的函數(fopen和fclose),讀和寫位元組的函數(fread和fwrite),讀和寫字元串的函數(fgets和fputs),格式化I/O函數(scanf和printf).标準I/O庫将一個打開的檔案模型化為一個流。一個流就是一個指向FILE類型的結構的指針。每個ANSI C程式開始時都有三個打開的流stdin、stdout、stderr,分别對應标準輸入、标準輸出、标準錯誤。類型為FILE的流是對檔案描述符和流緩存區的抽象。
參考資料
《深入了解計算機系統》
www.topsage.com
https://www.shiyanlou.com/courses/413
體會
本章節内容篇幅看似不大,内容實則含有很多東西。首先,代碼量相比于前些章節多了很多,加之C語言學的沒有完全通透,是以讀代碼有一定的困難。但是花了一定的時間去了解,還是能夠了解各七八層。剩下不了解的地方主要集中在RIO處,對于繼承部分不是很了解等。