系統級I/O
- 輸入/輸出(I/O)是在主存和外部裝置之間拷貝資料的過程。
- 輸入操作是從I/O裝置拷貝資料到主存。
- I/O→主存
- 輸出操作是從主存拷貝資料到I/O裝置。
- 主存→I/O
Unix I/O
- 所有的I/O裝置都被模型化為檔案。
- 所有的輸入和輸出都被當作對相應檔案的讀和寫來執行。
- 打開檔案
- 應用程式通過要求核心打開相應的檔案,來宣告它想要通路一個I/O裝置。
- 核心傳回一個小的非負整數,稱為描述符。
- 核心記錄有關這個打開檔案的所有資訊。
- 應用程式隻需要記住操作符即可。
- 建立每個程序開始時三個打開的檔案
- 标準輸入(描述符為0)|STDIN_FILENO
- 标準輸出(描述符為1)|STDOUT_FILENO
- 标準錯誤(描述符為2)|STDERR_FILENO
- 改變目前的檔案位置
- 對于打開的檔案,核心儲存着該檔案的位置k,初始值為0
- k為從檔案開頭起始的位元組偏移量
- 應用程式通過執行seek操作,顯式地設定檔案的目前位置為k
- 讀寫檔案
- 讀操作:
- 從檔案拷貝n>0個位元組到存儲器
- 從目前檔案位置k開始
- 将k增加到k+n
- 給定一個大小為m位元組的檔案,當k≥m時(即目前檔案位置已到檔案尾),此時執行讀操作會觸發end-of-file(EOF)條件。
- 在檔案的結尾并未明确的EOF符号
- 寫操作:
- 從存儲器拷貝n>0個位元組到一個檔案
- 更新k
- 讀操作:
- 關閉檔案
- 應用完成對檔案的通路後,會通知核心關閉該檔案。
- 核心釋放檔案打開時建立的資料結構,并恢複描述池。
- 無論一個程序因為何種原因終止,核心都會關閉所有打開的檔案并釋放它們的存儲器資源。
打開和關閉檔案
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(char *filename,int flags,mode_t mode)
- sys/types.h 基本系統資料類型,其中包括核心位址、系統時間、裝置号、檔案描述集、檔案位置等類型。
- sys/stat.h 用于擷取一個檔案的所有資訊,包括檔案對應的模式、裝置号碼、檔案所有者、最後被通路的時間、最後被修改的時間等。ls -l指令就用到了該頭檔案中所包含的類型。
- fcntl.h 檔案資訊控制類型(file control),定義了很多宏和open.fcntl函數原型。
open函數
- char *filename 參數filename指向欲打開的檔案路徑字元串。
- int flags 旗标
- O_RDONLY 以隻讀方式打開檔案
-
define O_RDONLY 00
-
- O_WRONLY 以隻寫方式打開檔案
-
define O_WRONLY 01
-
- O_RDWR 以可讀寫方式打開檔案。
-
define O_RDWR 02
-
- 上述三種旗标是互斥的,也就是不可同時使用,但可與下列的旗标利用OR(|)運算符組合。
- O_CREAT 若欲打開的檔案不存在則自動建立該檔案。
-
define O_CREAT 00000100
-
- O_TRUNC 若檔案存在并且以可寫的方式打開時,此旗标會令檔案長度清為0,而原來存于該檔案的資料也會消失。
-
define O_TRUNC 00001000
-
- O_APPEND 當讀寫檔案時會從檔案尾開始移動,也就是所寫入的資料會以附加的方式加入到檔案後面。
-
define O_APPEND 00002000
-
- mode_t mode 檔案權限标志
1. S_IRUSR 所有者擁有讀權限 2. S_IWUSR 所有者擁有寫權限 3. S_IXUSR 所有者擁有執行權限 4. S_IRGRP 群組擁有讀權限 5. S_IWGRP 群組擁有寫權限 6. S_IXGRP 群組擁有執行權限 7. S_IROTH 其他使用者擁有讀權限 8. S_IWOTH 其他使用者擁有寫權限 9. S_IXOTH 其他使用者擁有執行權限
- 檔案權限标志也可以用權重數字表示,這組數字被成為umask變量,它的類型是mode_t,是一個無符号八進制數。
- umask變量由3位數字組成,數字的每一位代表一類權限,使用者所獲得的權限是權重數值的總和。
- 例如764表示所有者擁有讀、寫和執行權限,群組擁有讀和寫權限,其他使用者擁有讀權限。
- 0表示沒有任何權限。
- 每個程式都有一個umask的值,并且預設為022。可以通過umask()函數調用設定。
- umask()會将系統umask值設成參數mask&0777後的值,然後将先前的umask值傳回。在使用open()建立新檔案時,該參數 mode并非真正建立檔案的權限,而是(mode& ~umask)的權限值。
- 例如,在建立檔案時指定檔案權限為0666,通常umask值預設為 022,則該檔案的真正權限則為0666&~022=0644
- 書上例子解析:
#define DEF_MODE S_IRUSER|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH //擁用者、群組、其他人都擁有讀寫權限,相當于666 #define DEF_MASK S_IWGRP|S_IWOTH //使用者組、其他人擁有寫權限,相當于022 umask(DEF_UMASK); //将目前的umask值設定成022 fd = open("foo.txt",O_CREAT|O_TRUNC|O_WRONLY,DEF_MODE); //DEF_MODE此時的值應該為DEF_MODE &~DEF_UMASK,即666&~022=644,則檔案的擁有者有讀寫權利,所有其他的使用者隻有讀權利。
- 傳回值 若成功則為新檔案描述符,若出錯為-1。傳回的描述符總是在程序中目前沒有打開的最小描述符。
- 示例:fd = open("foo.txt",O_WRONLY|O_APPEND,0)
- 表示打開一個已存在的檔案,并在後面添加一些資料。
- O_RDONLY 以隻讀方式打開檔案
close函數
#include <unistd.h>
int close(int fd);
- unistd.h 提供了close()函數。
- 傳回值:成功傳回0,出錯傳回-1并設定errno
- 參數fd是要關閉的檔案描述符。
- 需要說明的是,當一個程序終止時,核心對該程序所有尚未關閉的檔案描述符調用close關閉,是以即使使用者程式不調用close,在終止時核心也會自動關閉它打開的所有檔案。
- 若省略fd,則将關閉Open語句打開的所有活動檔案。
讀和寫檔案
讀檔案
#include <unist.h>
ssize_t read(int fd,void *buf,size_t n);
- fd 檔案描述符
- *buf 緩沖區指針,用于儲存讀出來的資料。
- n 所需要讀取的位元組數
- 傳回值類型為ssize_t 表示有符号的size_t。
- size_t(size type),表示一種整型類型,包含int、long等。
- 傳回所讀取的位元組數;0(讀到EOF);-1(出錯)。
- 以下幾種情況會導緻讀取到的位元組數小于 n :
- A. 讀取普通檔案時,讀到檔案末尾還不夠 n 位元組。例如:如果檔案隻有 30 位元組,而我們想讀取 100 位元組,那麼實際讀到的隻有 30 位元組,read 函數傳回 30 。此時再使用 read 函數作用于這個檔案會導緻 read 傳回 0 。
- B. 從終端裝置(terminal device)讀取時,一般情況下每次隻能讀取一行。
- C. 從網絡讀取時,網絡緩存可能導緻讀取的位元組數小于 n位元組。
- D. 讀取 pipe 或者 FIFO 時,pipe 或 FIFO 裡的位元組數可能小于 n 。
- E. 從面向記錄(record-oriented)的裝置讀取時,某些面向記錄的裝置(如錄音帶)每次最多隻能傳回一個記錄。
- F. 在讀取了部分資料時被信号中斷。
寫檔案
#include <unist.h>
sszize_t write(int fd,const void *buf,size_t n);
- const void *buf 資料來源buf,const所描述的對象具有不變性,不可更新。
- n 從存儲器位置buf拷貝至多n個位元組到描述符fd的目前檔案位置。
- 傳回值 一般等于n,否則就是出錯。
- 常見出錯的原因:
- 磁盤空間滿了
- 超過檔案大小限制
- 常見出錯的原因:
書上cpstdin.c代碼測試
書上的源代碼僅為理論上講述的程式,說明了一次一個位元組地從标準輸入拷貝到标準輸出的功能,但是并未有明顯的表示。是以我将代碼修改了,通過兩個檔案foo.txt與foo1.txt來展現該功能。
- foo.txt文檔中存放着123456的文本
- foo1.txt為空文檔。
- 代碼功能将foo.txt中的文本一次一個地寫入foo1.txt文檔中。
- 代碼如下:
/* $begin cpstdin */ #include "csapp.h" #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main(void) { char c = 1; int fd1,fd2; fd1 = open("foo.txt",O_RDWR);//打開foo.txt,并将檔案描述符儲存至fd1 fd2 = open("foo1.txt",O_RDWR);//打開 foo1.txt,并将檔案描述符儲存至fd2 while(read(fd1,&c, 1) != 0)//當未讀到EOF,則循環進行 write(fd2, &c, 1);//将一個字元寫入foo1.txt中 exit(0); } /* $end cpstdin */
- 運作結果如下:
用RIO包健壯地讀寫
- RIO(Robust I/O,健壯地I/O)包:它會自動為你處理不足值。
- 不足值:read和write傳送的位元組比應用程式要求地要少。
- RIO提供了兩類不同的函數:
- 無緩沖的輸入輸出函數。 沒有應用級緩沖,它們對将二進制資料讀寫到網絡和從網絡讀寫二進制資料尤其有用。
- 帶緩沖的輸入函數。 高效地從檔案中讀取檔案行和二進制資料,這些檔案的内容緩存在應用級緩存區内。
RIO的無緩沖的輸入輸出函數
- 通過調用rio_readn和rio_writen函數,應用程式可以在存儲器和檔案之間直接傳送資料。
#include "csapp.h" ssize_t rio_readn(int fd,void *usrbuf,size_t n);
- void *usrbuf 存儲器位置
- size_t n 傳送的位元組數
- 傳回值 若成功則為傳送的位元組數,若EOF則為0,若出錯則為-1
- 功能 從描述符fd的目前檔案位置最多傳送n個位元組到存儲器位置usrbuf。
#include "csapp.h" ssize_t rio_writen(int fd,viod *usrbuf,size_t n);
- 傳回值 若成功則為傳送的位元組數,若出錯則為-1
- 功能 從位置usrbuf傳送n個位元組道描述符fd。
RIO帶緩沖地輸入函數
- 一個文本行就是一個由換行符結尾的ASCII碼字元序列。
- 在Unix系統中,換行符的數值未0x0a。
#include "csapp.h" void rio_readinitb(rio_t *rp,int fd);
- 每打開一個描述符都會調用一次rio_readinitb,它将描述符fd和位址rp處的一個類型為rio_t的讀緩沖區練習起來。
ssize_t rio_readlineb(rio_t *rp,void *usrbuf,size_t maxlen);
- rio_t 設定一個位址
- void *usrbuf 緩存區位址
- size_t maxlen 讀取的最大長度
- 功能 從一個内部讀緩沖區拷貝一個文本行,當緩沖區變空時,會自動地調用read重新填滿緩沖區。
- rio_readlineb函數最多讀maxlen-1個位元組,餘下的一個字元留給結尾的空字元。
- 超過maxlen-1位元組的文本行被截斷,并用一個空字元結束。
- ssize_t rio_readnb(rio_t *rp,void *usrbuf,size_t n);
- 功能 對于既包含文本行也包含二進制資料的文本,進行拷貝文本行操作。
- 從檔案rp最多讀n個位元組道存儲器位置usrbuf。
對同一描述符,對rio_readlineb和rio_readnb的調用可以任意交叉進行,但不應該和無緩沖的rio_readn函數交叉使用。
讀取檔案中繼資料
- 應用程式能夠通過調用stat和fstat函數,檢索到關于檔案的資訊(也稱為檔案的中繼資料)。
#include <unistd.h> #include <sys/stat.h> int stat(const char *filename,struct stat *buf);
- filename 檔案名
- struct stat *buf 結構體stat中各個成員,如下:
struct stat { mode_t st_mode; //檔案對應的模式,檔案,目錄等 ino_t st_ino; //inode節點号 dev_t st_dev; //裝置号碼 dev_t st_rdev; //特殊裝置号碼 nlink_t st_nlink; //檔案的連接配接數 uid_t st_uid; //檔案所有者 gid_t st_gid; //檔案所有者對應的組 off_t st_size; //普通檔案,對應的檔案位元組數 time_t st_atime; //檔案最後被通路的時間 time_t st_mtime; //檔案内容最後被修改的時間 time_t st_ctime; //檔案狀态改變時間 blksize_t st_blksize; //檔案内容對應的塊大小 blkcnt_t st_blocks; //偉建内容對應的塊數量 }; int fstat(int fd,struct stat *buf);
- int fd 檔案描述符
- 不同的檔案類型
- 普通檔案
- 目錄檔案
- 網絡套接字
- 宏指令根據st_mode成員來确定檔案的類型
- S_ISREG() —— 這是一個普通檔案嗎?
- S_ISDIR() —— 這是一個目錄檔案嗎?
- S_ISSOCK() —— 這是一個網絡套接字嗎?
共享檔案
- 核心用三個資料結構來表示打開的檔案:
- 描述符表
- 每個程序都有它獨立的描述符表
- 檔案表
- 打開檔案的集合是由一張檔案表來表示的,所有的程序共享這張表。
- 每個檔案表的表項組成:
- 目前的檔案位置
- 引用計數
- 指向v-node表中對應表項的指針
- v-node表
- 所有的程序共享這張v-node表。
- 描述符表
I/O重定向
- 利用dup2函數進行I/O重定向工作
#include <unistd.h> int dup2(int oldfd,int newfd);
- dup2函數拷貝描述符表表項oldfd到描述符表表項newfd,覆寫描述符表表項newfd以前的内容。
- 如果newfd已經打開了,dup2會在拷貝oldfd之前關閉newfd。
标準I/O
- ANSI C定義了一組進階輸入輸出函數,成為标準I/O庫。
- 提供了打開和關閉檔案的函數(fopen/fclose)
- 讀和寫位元組的函數(fread/fwrite)
- 讀和寫字元串的函數(fgets/fputs)
- 複雜的格式化的I/O函數(scanf/printf)
- 标準I/O庫将一個打開的檔案模型化為一個流。
- 每個ANSI C程式開始時都有三個打開的流stdin、stdout和stderr。
- 類型為FILE的流是對檔案描述符和流緩沖區的抽象。
實驗代碼
-
cp1.c
該代碼的功能為生成一個與其一樣内容的檔案。
運作測試(拷貝cp1.c至cp2.c)
2.echostate.c
該代碼的功能為檢視此時echo值為多少,配合setecho使用。
3.setecho.c
該代碼的功能是輸入y時,即可顯示輸入。輸入除y外的内容,即可隐藏輸入
運作結果:
4.fileinfo.c
該代碼的功能是顯示檔案的資訊
5.filesize.c
該代碼的功能是顯示檔案的位元組數
6.who1.c
該代碼的功能是将系統使用者操作顯示出來。
7.ls1.c
該代碼的功能是顯示目前目錄的檔案
8.ls2.c
該代碼的功能同ls1.c相同,但多了檔案的權限資訊。
9.spwd.c
該代碼的功能是顯示目前檔案夾路徑,同pwd。
10.testioctl.c
該代碼的功能是顯示檔案内容的行列。
參考資料
1.《Computer.Systems.A.Programmer's.Perspective.2nd.CN》教材
2.《打開檔案、建立檔案和關閉檔案操作》http://book.51cto.com/art/200912/169537.htm
3.《linux下的檔案操作函數》http://jingyan.baidu.com/article/6dad5075c33056a123e36ecf.html
4.《linux下的umask()函數》http://www.360doc.com/content/12/0605/16/9305922_216186419.shtml