Linux 文件 IO
參考:http://blog.csdn.net/wanxiao009/article/details/5648583
C 和 C++ 文件操作詳解:http://blog.csdn.net/freeking101/article/details/60959624
 
標準IO與文件IO 的區別:http://blog.csdn.net/big_bit/article/details/51804391
 
參考 APUE 整理。如有疑問,可以直接看 APUE 。。。? ?linux 文件IO
 
IO文件操作時最常用的也最基本的內容。linux文件系統是由兩層結構構建:第一層是虛擬文件系統(VFS),第二層是各種不同的具體文件系統。VFS是吧、把各種具體的文件系統的公共部分抽取出來,形成一個抽象層,是系統內核的一部分。它位于用戶程序和具體的文件系統中間。它對用戶程序提供了標準的文件系統的調用接口,對具體的文件系統,它通過一系列的對不同文件系統公用的函數指針來實際調用具體的文件系統函數,完成實際的各有的操作。任何使用文件系統的程序必須經過這層接口來使用它。通過這種方式,VFS就對用于屏蔽了底層文件系統的實現細節和差異。
 通過 cat /proc/filesystems命令可以查看系統支持哪些文件系統。
 
 
先來一張總結圖
 
 
 
1. 引言
 
? ? ? ? 先說明可用的文件 I/O 函數——打開文件、讀文件、寫文件等等。大多數UNIX文件 I/O只需用到5個函數: open、 read、 write、 lseek 以及 close。然后說明不同緩存器長度對 read 和 write 函數的影響 。
? ? ? ? 本文所說明的函數經常被稱之為不帶緩存的 I/O(unbuffered I/O)。標準 I/O函數,即庫函數的 I/O,是帶緩存的I/O。不帶緩沖的I/O,直接調用系統調用,速度快,如函數open(), read(), write()等。而帶緩沖的I/O,在系統調用前采用一定的策略,速度慢,比不帶緩沖的I/O安全,如fopen(), fread()、 fwrite()等。
 
? ? ? ? 術語——不帶緩存指的是每個 read 和 write都調用內核中的一個系統調用。這些不帶緩存的 I/O 函數不是 ANSI C的組成部分,但是是 POSIX .1和XPG3的組成部分。
? ? ? ? 只要涉及在多個進程間共享資源,原子操作的概念就變成非常重要。我們將通過文件 I/O 和傳送給 open 函數的參數來討論此概念。并進一步討論在多個進程間如何共享文件,并涉及內核的有關數據結構。在討論了這些特征后,將說明 dup、 fcntl 和 ioctl 函數。
 
? ? ? ? 其實,上面所說的可以分為 文件I/O(即系統調用) 和 標準 I/O(庫函數)。
- 文件I/O:文件I/O稱之為不帶緩存的IO(unbuffered I/O)。不帶緩存指的是每個read,write都調用內核中的一個系統調用。也就是一般所說的低級I/O——操作系統提供的基本IO服務,與os綁定,特定于linix或unix平臺。
 - 標準I/O:標準I/O是ANSI C建立的一個標準I/O模型,是一個標準函數包和stdio.h頭文件中的定義,具有一定的可移植性。標準I/O庫處理很多細節。例如緩存分配,以優化長度執行I/O等。標準的I/O提供了三種類型的緩存。
(1)全緩存:當填滿標準I/O緩存后才進行實際的I/O操作。
(2)行緩存:當輸入或輸出中遇到新行符時,標準I/O庫執行I/O操作。
(3)不帶緩存:stderr就是了。 
二者的區別
 
? ? ? ? 文件I/O 又稱為低級磁盤I/O,遵循POSIX相關標準。任何兼容POSIX標準的操作系統上都支持文件I/O。標準I/O被稱為高級磁盤I/O,遵循ANSI C相關標準。只要開發環境中有標準I/O庫,標準I/O就可以使用。(Linux 中使用的是GLIBC,它是標準C庫的超集。不僅包含ANSI C中定義的函數,還包括POSIX標準中定義的函數。因此,Linux 下既可以使用標準I/O,也可以使用文件I/O)。
 ? ? ? ? 通過文件I/O讀寫文件時,每次操作都會執行相關系統調用。這樣處理的好處是直接讀寫實際文件,壞處是頻繁的系統調用會增加系統開銷,標準I/O可以看成是在文件I/O的基礎上封裝了緩沖機制。先讀寫緩沖區,必要時再訪問實際文件,從而減少了系統調用的次數。
 ? ? ? ? 文件I/O中用文件描述符表現一個打開的文件,可以訪問不同類型的文件如普通文件、設備文件和管道文件等。而標準I/O中用FILE(流)表示一個打開的文件,通常只用來訪問普通文件。
 
 
使用的一些函數
 
 
 
2. 文件描述符
 
? ? ? ? 對于內核而言,所有打開的文件(Linux 中一切皆文件)都由一個文件描述符標識。文件描述符是一個非負整數。當打開一個現存文件或創建一個新文件時,內核向進程返回一個文件描述符。當讀、寫一個文件時,用?open?或?creat?返回的文件描述符標識該文件,將其作為參數傳送給read?或?write。
 ? ? ? ? 按照慣例, UNIX shell使文件描述符0與進程的標準輸入相結合,文件描述符1與標準輸出相結合,文件描述符2與標準出錯輸出相結合。這是UNIX shell以及很多應用程序使用的慣例,而與內核無關。盡管如此,如果不遵照這種慣例,那么很多UNIX應用程序就不能工作。
? ? ? ? 文件描述符的范圍是 0 ~ OPEN_MAX(見表2-7)。早期的?UNIX版本采用的上限值是19 (允許每個進程打開?20?個文件),現在很多系統則將其增加至63。
 
 
3. 文件 I/O 相關系統調用
 
APUE 中 關于文件 I/O 的幾個基礎系統調用:open()、creat()、close()、lseek()、read()、write()。
上面的函數都可以直接通過 man 幫助查看。man 查看的內容包括:
man可以查看一下內容: 1.一般命令(shell命令) 2.系統調用(open write等直接陷入內核的函數) 3.子函數(C函數庫等不直接陷入內核的函數) 4.特殊文件(/dev/zero等linux系統中有特殊用途的文件) 5.文件格式(linux系統的配置文件格式 host.conf) 6.游戲 7.宏和地方傳統定義(本地配置) 8.維護命令(tcpdump等用來觀察linux系統運行情況的命令)
 
open函數
open函數:調用它可以打開或者創建一個文件。(man 2 open)
 
注意:open 創建文件不在同一目錄時,如果目錄不存在則創建失敗。所以創建的文件不在同一目錄時,必須先創建目錄,然后在創建文件。
NAMEopen, openat, creat - open and possibly create a fileSYNOPSIS#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>int open(const char *pathname, int flags);int open(const char *pathname, int flags, mode_t mode);int creat(const char *pathname, mode_t mode);int openat(int dirfd, const char *pathname, int flags);int openat(int dirfd, const char *pathname, int flags, mode_t mode);返回值:成功返回新分配的文件描述符,出錯返回-1并設置errno參數解析: pathname是要打開或者創建的文件名。flags 參數有一系列常數值可供選擇,可以同時選擇多個常數用按位或運算符連接起來,所以這些常數的宏定義都以O_開頭,表示or。flags 文件打開時候的選項,有三個選項是必選的!必選項:以下三個常數中必須指定一個,且僅允許指定一個。O_RDONLY以只讀方式打開文件。O_WRONLY以只寫方式打開文件。 O_RDWR以讀、寫方式打開文件。flags 可選選項:可選項可以同時指定0個或多個,和必選項按位或起來作為flags參數。 可選項有很多,這里只介紹一部分,其它選項可參考open(2)的Man Page: O_APPEND 以追加方式打開文件,每次寫時都寫在文件末尾。 O_CREAT 如果文件不存在,則創建一個,存在則打開它。 O_EXCL 與O_CREAT一起使用時,如果文件已經存在則返回出錯。 O_TRUNC 以只寫或讀寫方式打開時,把文件截斷為0 O_DSYNC 每次write時,等待數據寫到磁盤上。 O_RSYNC 每次讀時,等待相同部分先寫到磁盤上。 O_NONBLOCK 對于設備文件,以O_NONBLOCK方式打開可以做非阻塞I/O(Nonblock I/O) O_SYNC 每次write時,等到數據寫到磁盤上并接更新文件屬性。 SYNC選項都會影響降低性能,有時候也取決于文件系統的實現。mode 只有創建文件時才使用此參數,指定文件的訪問權限。模式有:S_IRWX[UGO] 可讀 可寫 可執行S_IR[USR GRP OTH] 可讀S_IW[USR GRP OTH] 可寫S_IX[USR GRP OTH] 可執行S_ISUID 設置用戶IDS_ISGID 設置組ID U->user G->group O->others
 
由 open 返回的文件描述符一定是最小的未用描述符數字。
 
在早期的 UNIX 版本中, open 的第二個參數只能是 0、1 或 2。沒有辦法打開一個尚未存在的文件,因此需要另一個系統調用 creat 以創建新文件。現在 open 函數提供了選擇項 O_CREAT 和 O_TRUNC,于是也就不再需要 creat 函數了。
示例代碼1:
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <fcntl.h>int main(void) {int fd;char buf[64];int ret = 0;fd = open("./file.txt", O_RDONLY);if (fd == -1) {printf("open file error\n");exit(1);}printf("---open ok---\n");while((ret = read(fd, buf, sizeof(buf)))) {write(1, buf, ret);}close(fd);return 0; }示例代碼2(命令行參數實現簡單的cp命令):
/**./mycp src dst 命令行參數實現簡單的cp命令*/ #include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <stdio.h>char buf[1024];int main(int argc, char *argv[]) {int src, dst;int n;src = open(argv[1], O_RDONLY); //只讀打開源文件if(src < 0){perror("open src error");exit(1);}//只寫方式打開,覆蓋原文件內容,不存在則創建,rw-r--r--dst = open(argv[2], O_WRONLY|O_TRUNC|O_CREAT, 0644);if(src < 0){perror("open dst error");exit(1);}while((n = read(src, buf, 1024))){if(n < 0){perror("read src error");exit(1);}write(dst, buf, n); //不應寫出1024, 讀多少寫多少}close(src);close(dst);return 0; }示例代碼3:
#include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <stdlib.h>#define N 1204int main(int argc, char *argv[]) {int fd, fd_out;int n;char buf[N];fd = open("src.txt", O_RDONLY);if(fd < 0){perror("open src.txt error");exit(1);}fd_out = open("des.txt", O_WRONLY|O_CREAT|O_TRUNC, 0644);if(fd < 0){perror("open des.txt error");exit(1);}while((n = read(fd, buf, N))){if(n < 0){perror("read error");exit(1);}write(fd_out, buf, n);}close(fd);close(fd_out);return 0; }示例代碼:使用庫函數 fopen
#include <stdio.h> #include <stdlib.h>int main(void) {FILE *fp, *fp_out;int n;fp = fopen("src.txt", "r");if(fp == NULL){perror("fopen error");exit(1);}fp_out = fopen("des.cp", "w");if(fp == NULL){perror("fopen error");exit(1);}while((n = fgetc(fp)) != EOF){fputc(n, fp_out);}fclose(fp);fclose(fp_out);return 0; }示例代碼:打開錯誤
#include <unistd.h> //read write #include <fcntl.h> //open close O_WRONLY O_RDONLY O_CREAT O_RDWR #include <stdlib.h> //exit #include <errno.h> #include <stdio.h> //perror #include <string.h>int main(void) {int fd; #if 1//打開文件不存在fd = open("test", O_RDONLY | O_CREAT);if(fd < 0){printf("errno = %d\n", errno);//perror("open test error");printf("open test error: %s\n" , strerror(errno));//printf("open test error\n");exit(1);} #elif 0//打開的文件沒有對應權限(以只寫方式打開一個只有讀權限的文件)fd = open("test", O_WRONLY); //O_RDWR也是錯誤的if(fd < 0){printf("errno = %d\n", errno);perror("open test error");//printf("open test error\n");exit(1);}#endif #if 0//以寫方式打開一個目錄fd = open("testdir", O_RDWR); //O_WRONLY也是錯的 if(fd < 0){perror("open testdir error");exit(1);} #endifreturn 0; }注意open函數與C標準I/O庫的fopen函數有些細微的區別:
- 以可寫的方式fopen一個文件時,如果文件不存在會自動創建,而open一個文件時必須明確指定O_CREAT才會創建文件,否則文件不存在就出錯返回。
 - 以w或w+方式fopen一個文件時,如果文件已存在就截斷為0字節,而open一個文件時必須明確指定O_TRUNC才會截斷文件,否則直接在原來的數據上改寫。
 - 第三個參數mode指定文件權限,可以用八進制數表示,比如0644表示-rw-r-r–,也可以用S_IRUSR、S_IWUSR等宏定義按位或起來表示,詳見open(2)的Man Page。要注意的是,文件權限由open的mode參數和當前進程的umask掩碼共同決定。
 
補充說明一下Shell的umask命令。Shell進程的umask掩碼可以用umask命令查看:(反掩碼 的數字是要去掉的權限)
$ umask 0002
 
用touch命令創建一個文件時,創建權限是0666,而touch進程繼承了Shell進程的umask掩碼,所以最終的文件權限是0666&~022=0644。
 
 
同樣道理,用gcc編譯生成一個可執行文件時,創建權限是0777,而最終的文件權限是:0777 & ~022 = 0755。
 
 
我們看到的都是被umask掩碼修改之后的權限,那么如何證明touch或gcc創建文件的權限本來應該是0666和0777呢?我們可以把Shell進程的umask改成0,再重復上述實驗。
 
 
最大打開文件個數
 
查看當前系統允許打開最大文件個數:cat /proc/sys/fs/file-max
 
修改默認設置最大打開文件個數為4096:ulimit -n 4096
 
creat函數
creat 以只寫方式創建一個文件,若文件已經存在,則把它截斷為0
#include <fcntl.h> int creat(const char *pathname, mode_t mode) // 返回:若成功為只寫打開的文件描述符,若出錯為- 1參數解析: pathname 要創建的文件名稱mode 跟open的第三個參數相同,可讀,可寫,可執行 。如果失敗 ,返回值為-1creat 函數 等同于 open (pathname, O_WRONLY | O_CREAT | O_TRUNC, mode)在早期的 UNIX 版本中, open 的第二個參數只能是 0、1 或 2。沒有辦法打開一個尚未存在的文件,因此需要另一個系統調用 creat 以創建新文件。 現在 open 函數提供了選擇項 O_CREAT 和 O_TRUNC,于是也就不再需要 creat 函數了。creat 的一個不足之處是它以只寫方式打開所創建的文件。在提供 open的新版本之前,如果要創建一個臨時文件,并要先寫該文件,然后又讀該文件,則必須先調用 creat, clo se,然后再調用 open。現在則可用下列方式調用open:open(pathname, O_RDWR|O_CREAT|O_TRUNC, m o d e) ;
 
close函數
close 關閉已經打開的文件,并釋放文件描述符
#include <unistd.h> int close(int fd) // 返回值:成功返回0,出錯返回-1并設置errno 參數解析:fd 文件描述符,由 open 或者 creat 返回的非負整數。 當一個進程結束時,操作系統會自動釋放該進程打開的所有文件。但還是推薦用close來關閉文件。 lsof命令可以查看進程打開了那些文件。? ? ? ? 參數fd是要關閉的文件描述符。需要說明的是,當一個進程終止時,內核對該進程所有尚未關閉的文件描述符調用close關閉,所以即使用戶程序不調用close,在終止時內核也會自動關閉它打開的所有文件。但是對于一個長年累月運行的程序(比如網絡服務器),打開的文件描述符一定要記得關閉,否則隨著打開的文件越來越多,會占用大量文件描述符和系統資源。
 ? ? ? ? 由open返回的文件描述符一定是該進程尚未使用的最小描述符。由于程序啟動時自動打開文件描述符0、1、2,因此第一次調用open打開文件通常會返回描述符3,再調用open就會返回4。可以利用這一點在標準輸入、標準輸出或標準錯誤輸出上打開一個新文件,實現重定向的功能。例如,首先調用close關閉文件描述符1,然后調用open打開一個常規文件,則一定會返回文件描述符1,這時候標準輸出就不再是終端,而是一個常規文件了,再調用printf就不會打印到屏幕上,而是寫到這個文件中了。后面要講的dup2函數提供了另外一種辦法在指定的文件描述符上打開文件。
 
 
 
lseek函數
Linux文件空洞與稀疏文件:http://blog.csdn.net/freeking101/article/details/78190379
 
lseek 用來定位當前文件偏移量,既你對文件操作從文件的那一部分開始。
 
每個打開文件都有一個與其相關聯的“當前文件位移量”。它是一個非負整數,用以度量從文件開始處計算的字節數。通常,讀、寫操作都從當前文件位移量處開始,并使位移量增加所讀或寫的字節數。按系統默認,當打開一個文件時,除非指定 O_APPEND 選擇項,否則該位移量被設置為0。可以調用 lseek 顯式地定位一個打開文件。
#include <unistd.h> off_t lseek(int fd, off_t offset, int whence); // 如果失敗,返回值為-1,成功返回移動后的文件偏移量。參數解析:fd 文件描述符。offset 必須與whence一同解析whence為 SEEK_SET, 則offset從文件的開頭算起。whence為 SEEK_CUR, 則offset從當前位置算起,既新偏移量為當前偏移量加上offsetwhence為 SEEK_END, 則offset從文件末尾算起。 可以通過lseek、write來快速創建一個大文件。每個打開的文件都記錄著當前讀寫位置,打開文件時讀寫位置是0,表示文件開頭,通常讀寫多少個字節就會將讀寫位置往后移多少個字節。但是有一個例外,如果以O_APPEND方式打開,每次寫操作都會在文件末尾追加數據,然后將讀寫位置移到新的文件末尾。lseek和標準I/O庫的fseek函數類似,可以移動當前讀寫位置(或者叫偏移量)。
 
參數offset和whence的含義和fseek函數完全相同。只不過第一個參數換成了文件描述符。和fseek一樣,偏移量允許超過文件末尾,這種情況下對該文件的下一次寫操作將延長文件,中間空洞的部分讀出來都是0。若lseek成功執行,則返回新的偏移量,因此可用以下方法確定一個打開文件的當前偏移量:
 
off_t currpos;
 currpos = lseek(fd, 0, SEEK_CUR);
這種方法也可用來確定文件或設備是否可以設置偏移量,常規文件都可以設置偏移量,而設備一般是不可以設置偏移量的。如果設備不支持lseek,則lseek返回-1,并將errno設置為ESPIPE。注意fseek和lseek在返回值上有細微的差別,fseek成功時返回0失敗時返回-1,要返回當前偏移量需調用ftell,而lseek成功時返回當前偏移量失敗時返回-1。
lseek 僅將當前的文件位移量記錄在內核內,它并不引起任何 I/O操作。然后,該位移量用于下一個讀或寫操作。文件位移量可以大于文件的當前長度,在這種情況下,對該文件的下一次寫將延長該文件,并在文件中構成一個空調,這一點是允許的。位于文件中但沒有寫過的字節都被讀為 0。
 
示例代碼:
新建一個文件 lseek.txt ,文件內容:It's a test for lseek。然后新建一個 lseek.c 文件,內容如下:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <fcntl.h>int main(void) {int fd, n;char msg[] = "It's a test for lseek\n";char ch;fd = open("lseek.txt", O_RDWR|O_CREAT|O_TRUNC, 0644);if(fd < 0){perror("open lseek.txt error");exit(1);}write(fd, msg, strlen(msg));lseek(fd, 0, SEEK_SET);while((n = read(fd, &ch, 1))){if(n < 0){perror("read error");exit(1);}write(STDOUT_FILENO, &ch, n);//putchar(ch);//printf("%c", ch);}close(fd);return 0; }使用 lseek 得出文件大小
#include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <stdlib.h>int main(void) {int fd;fd = open("lseek.txt", O_RDWR | O_CREAT, 0644);if (fd < 0) {perror("open error");exit(1);} #if 0int ret = lseek(fd, 99, SEEK_SET);if (ret < 0) {perror("lseek error");exit(1);}write(fd, "a", 1); #endifint ret = lseek(fd, 0, SEEK_END);if (ret < 0) {perror("lseek error");exit(1);}printf("the lenth of lseek.txt is %d\n", ret);close(fd);return 0; }示例代碼:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <fcntl.h>int main(void) {int fd;fd = open("lseek.txt", O_RDONLY);if(fd < 0){perror("open lseek.txt error");exit(1);}int len = lseek(fd, 0, SEEK_END);if(len == -1){perror("lseek error");exit(1);}printf("len of msg = %d\n", len);off_t cur = lseek(fd, -10, SEEK_END);if(cur == -1){perror("lseek error");exit(1);}printf("--------| %ld\n", cur);close(fd);return 0; }
 
read函數
read 從打開的設備或文件偏移量處讀入指定大小的數據。
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count);返回值:成功返回讀取的字節數,出錯返回-1并設置errno,如果在調read之前已到達文件末尾,則這次read返回0參數解析 fd 文件描述符 ,有open返回。buf 讀入文件內容存放的內存首地址。count 要讀取的字節數。 實際讀入的字節數可能會小于要求讀入的字節數。比如文件只有所剩的字節數小于你要讀入的字節數,讀取fifo文件和網絡套接字時都可能出現這種情況? ? ? ? 參數count是請求讀取的字節數,讀上來的數據保存在緩沖區buf中,同時文件的當前讀寫位置向后移。注意這個讀寫位置和使用C標準I/O庫時的讀寫位置有可能不同,這個讀寫位置是記在內核中的,而使用C標準I/O庫時的讀寫位置是用戶空間I/O緩沖區中的位置。比如用fgetc讀一個字節,fgetc有可能從內核中預讀1024個字節到I/O緩沖區中,再返回第一個字節,這時該文件在內核中記錄的讀寫位置是1024,而在FILE結構體中記錄的讀寫位置是1。注意返回值類型是ssize_t,表示有符號的size_t,這樣既可以返回正的字節數、0(表示到達文件末尾)也可以返回負值-1(表示出錯)。read函數返回時,返回值說明了buf中前多少個字節是剛讀上來的。有些情況下,實際讀到的字節數(返回值)會小于請求讀的字節數count,
? ? ? ? 例如:讀常規文件時,在讀到count個字節之前已到達文件末尾。例如,距文件末尾還有30個字節而請求讀100個字節,則read返回30,下次read將返回0。從終端設備讀,通常以行為單位,讀到換行符就返回了。從網絡讀,根據不同的傳輸層協議和內核緩存機制,返回值可能小于請求的字節數,后面socket編程部分會詳細講解。
 
write函數
write向打開的設備或文件中寫入一定字節的數據。
#include <unistd.h> ssize_t write(int fd, const void *buf, size_t count); // 返回值:成功返回寫入的字節數,出錯返回-1并設置errno失敗返回-1,成功返回實際寫入的字節數。當磁盤滿或者文件到達上限時可能寫入失敗。 一般從當前文件偏移量出寫入,但如果打開時使用了O_APPEND,那么無論當前文件偏移量在哪里,都會移動到文件末尾寫入。寫常規文件時,write的返回值通常等于請求寫的字節數count,而向終端設備或網絡寫則不一定
 
阻塞和非阻塞
? ? ? ? 讀常規文件是不會阻塞的,不管讀多少字節,read一定會在有限的時間內返回。從終端設備或網絡讀則不一定,如果從終端輸入的數據沒有換行符,調用read讀終端設備就會阻塞,如果網絡上沒有接收到數據包,調用read從網絡讀就會阻塞,至于會阻塞多長時間也是不確定的,如果一直沒有數據到達就一直阻塞在那里。同樣,寫常規文件是不會阻塞的,而向終端設備或網絡寫則不一定。
 ? ? ? ? 現在明確一下阻塞(Block)這個概念。當進程調用一個阻塞的系統函數時,該進程被置于睡眠(Sleep)狀態,這時內核調度其它進程運行,直到該進程等待的事件發生了(比如網絡上接收到數據包,或者調用sleep指定的睡眠時間到了)它才有可能繼續運行。與睡眠狀態相對的是運行(Running)狀態,在Linux內核中,處于運行狀態的進程分為兩種情況:
 ? ? ? ? 正在被調度執行。CPU處于該進程的上下文環境中,程序計數器(eip)里保存著該進程的指令地址,通用寄存器里保存著該進程運算過程的中間結果,正在執行該進程的指令,正在讀寫該進程的地址空間。
 ? ? ? ? 就緒狀態。該進程不需要等待什么事件發生,隨時都可以執行,但CPU暫時還在執行另一個進程,所以該進程在一個就緒隊列中等待被內核調度。系統中可能同時有多個就緒的進程,那么該調度誰執行呢?內核的調度算法是基于優先級和時間片的,而且會根據每個進程的運行情況動態調整它的優先級和時間片,讓每個進程都能比較公平地得到機會執行,同時要兼顧用戶體驗,不能讓和用戶交互的進程響應太慢。
 
 
下面這個小程序從終端讀數據再寫回終端。
 
阻塞讀終端
#include <unistd.h> #include <stdlib.h> int main(void) {char buf[10];int n;n = read(STDIN_FILENO, buf, 10);if (n < 0) {perror("read STDIN_FILENO\n");exit(1);}write(STDOUT_FILENO, buf, n);return 0; }
 
第一次執行a.out的結果很正常,而第二次執行的過程有點特殊。
現在分析一下:Shell進程創建a.out進程,a.out進程開始執行,而Shell進程睡眠等待a.out進程退出。a.out調用read時睡眠等待,直到終端設備輸入了換行符才從read返回,read只讀走10個字符,剩下的字符仍然保存在內核的終端設備輸入緩沖區中。a.out進程打印并退出,這時Shell進程恢復運行,Shell繼續從終端讀取用戶輸入的命令,于是讀走了終端設備輸入緩沖區中剩下的字符d和換行符,把它當成一條命令解釋執行,結果發現執行不了,沒有 abcd 這個命令。如果在open一個設備時指定了O_NONBLOCK標志,read/write就不會阻塞。以read為例,如果設備暫時沒有數據可讀就返回-1,同時置errno為EWOULDBLOCK(或者EAGAIN,這兩個宏定義的值相同),表示本來應該阻塞在這里(would block,虛擬語氣),事實上并沒有阻塞而是直接返回錯誤,調用者應該試著再讀一次(again)。這種行為方式稱為輪詢(Poll),調用者只是查詢一下,而不是阻塞在這里死等,這樣可以同時監視多個設備:
while(1) {非阻塞read(設備1);if(設備1有數據到達)處理數據;非阻塞read(設備2);if(設備2有數據到達)處理數據; ... }如果read(設備1)是阻塞的,那么只要設備1沒有數據到達就會一直阻塞在設備1的read調用上,即使設備2有數據到達也不能處理,使用非阻塞I/O就可以避免設備2得不到及時處
 理。非阻塞I/O有一個缺點,如果所有設備都一直沒有數據到達,調用者需要反復查詢做無用功,如果阻塞在那里,操作系統可以調度別的進程執行,就不會做無用功了。在使用非阻塞I/O時,通常不會在一個while循環中一直不停地查詢(這稱為Tight Loop),而是每延遲等待一會兒來查詢一下,以免做太多無用功,在延遲等待的時候可以調度其它進程執行。
這樣做的問題是,設備1有數據到達時可能不能及時處理,最長需延遲n秒才能處理,而且反復查詢還是做了很多無用功。以后要學習的select(2)函數可以阻塞地同時監視多個設備,還可以設定阻塞等待的超時時間,從而圓滿地解決了這個問題。
 
以下是一個非阻塞I/O的例子。目前我們學過的可能引起阻塞的設備只有終端,所以我們用終端來做這個實驗。程序開始執行時在0、1、2文件描述符上自動打開的文件就是終端,但是沒有O_NONBLOCK標志。所以就像例 28.2 “阻塞讀終端”一樣,讀標準輸入是阻塞的。我們可以重新打開一遍設備文件/dev/tty(表示當前終端),在打開時指定O_NONBLOCK標志。
 
非阻塞讀終端示例代碼:
#include <unistd.h> #include <fcntl.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h>#define MSG_TRY "try again\n"int main(void) {char buf[10];int fd, n;fd = open("/dev/tty", O_RDONLY|O_NONBLOCK); //使用O_NONBLOCK標志設置非阻塞讀終端if(fd < 0){perror("open /dev/tty");exit(1);} tryagain:n = read(fd, buf, 10);if(n < 0){//由于open時指定了O_NONBLOCK標志,read讀設備,沒有數據到達返回-1,同時將errno設置為EAGAIN或EWOULDBLOCKif(errno != EAGAIN){ //也可以是 if(error != EWOULDBLOCK)兩個宏值相同perror("read /dev/tty");exit(1);}sleep(3);write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));goto tryagain;}write(STDOUT_FILENO, buf, n);close(fd);return 0; }非阻塞I/O實現等待超時的例子。既保證了超時退出的邏輯又保證了有數據到達時處理延遲較小。
#include <unistd.h> #include <fcntl.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h>#define MSG_TRY "try again\n" #define MSG_TIMEOUT "time out\n"int main(void) {char buf[10];int fd, n, i;fd = open("/dev/tty", O_RDONLY|O_NONBLOCK);if(fd < 0){perror("open /dev/tty");exit(1);}printf("open /dev/tty ok... %d\n", fd);for(i = 0; i < 5; i++){n = read(fd, buf, 10);if(n > 0){ //說明讀到了東西break;}if(errno != EAGAIN){ //EWOULDBLK perror("read /dev/tty");exit(1);}sleep(3);write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));}if(i == 5){write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TIMEOUT));}else{write(STDOUT_FILENO, buf, n);}close(fd);return 0; }
 
內核用于所有 I/O 的數據結構
內核使用了三種數據結構,來實現I/O?
(a) 文件描述符標志。
(b) 指向一個文件表項的指針。
(a) 文件狀態標志(讀、寫、增寫、同步等)。
(b) 當前文件位移量。
(c) 指向該文件v節點表項的指針。
如圖顯示了進程的三張表之間的關系。該進程有兩個不同的打開文件:一個文件打開為標準輸入(文件描述符0),另一個打開為標準輸出(文件描述符為 1)。
 
打開文件的內核數據結構
 
 
兩個獨立進程各自打開了同一文件,它們擁有各自的文件表項,但共享v節點表。
 
如圖所示,假定第一個進程使該文件在文件描述符 3上打開,而另一個進程則使此文件在文件描述符 4上打開。打開此文件的每個進程都得到一個文件表項,但對一個給定的文件只有一個 v節點表項。每個進程都有自己的文件表項的一個理由是:這種安排使每個進程都有它自己的對該文件的當前位移量
 
兩個獨立進程各自打開同一個文件
 
 
 
原子操作
假定 A、B 兩個進程以 O_APPEND 方式打開同一個文件。A 進程去寫該文件,假設此時文件偏移量為1000,B進程同時去寫該文件,此時由于A進程未寫完,則B進程得到的文件偏移量仍為1000。最后B進程的內容可能會覆蓋掉A進程寫的內容。pread , pwrite是原子讀寫操作。相當于先把文件偏移量定位到offset,然后在進行讀寫。這都是一步完成,不存在競爭問題。
#include <unistd.h> ssize_t pread(int filedes, void *buf, size_t nbytes, off_t offset) ssize_t pwrite(int filedes, const void *buf, size_t nbytes, off_t offset) 返回值跟read和write一樣。offset為文件偏移量。調用 pread 相當于 順序調用 lseek 和 read ,但是 pread 又與這種順序調用有下列重要區別:
- 調用 pread 時,無法中斷其定位和讀操作
 - 不能更新文件指針
 
調用 pwrite 相當于 順序調用 lseek 和 write ,但但也與他們有上述類似的區別。
 
dup 和 dup2 函數
dup/dup2用來復制一個已經存在的文件描述符
#include <unistd.h> int dup(int filedes) ; int dup2(int filedes, int filedes2) ; 失敗返回-1,成功返回新文件描述符。filedes2是新文件描述符,如果已經打開則先關閉它。 ssize_t pread(int filedes, void *buf, size_t nbytes, off_t offset); 共享文件表項。dup 和 dup2 的使用
- dup 返回的新文件描述符一定是當前可用文件描述符中的最小數值。
 - dup2 則可以用 filedes2 參數指定新描述符的數值。如果 filedes2 已經打開,則先將其關閉。如若filedes 等于 filedes2,則 dup2 返回 filedes2,而不關閉它。
 
這些函數返回的新文件描述符與參數 filedes 共享同一個文件表項。如圖所示:
 
 
? ? ? ? 在此圖中,我們假定進程執行了:newfd = dup(1) 。當此函數開始執行時,假定下一個可用的描述符是 3 (這是非常有可能的,因為 0, 1和2由 shell 打開)。因為兩個描述符指向同一文件表項,所以它們共享同一文件狀態標志 (讀、寫、添寫等)以及同一當前文件位移量。
 ? ? ? ? 每個文件描述符都有它自己的一套文件描述符標志。正如我們將在下一節中說明的那樣,新描述符的執行時關閉( close-on-exec )文件描述符標志總是由 dup 函數清除。
 ? ? ? ? 復制一個描述符的另一種方法是使用 fcntl 函數,下一節將對該函數進行說明。
在上面的第二種情況下,dup2并不完全等同于close加上fcntl。它們之間的區別是:
示例代碼:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <string.h>int main(void) {int fd, save_fd;char msg[] = "It's a test!\n";fd = open("file1", O_RDWR|O_CREAT, 0644); if(fd < 0){perror("open error");exit(1);}printf("------>fd = %d\n", fd); //新打開的文件描述符是3,里面保存指向feil1文件的指針save_fd = dup(STDOUT_FILENO); //把文件描述符1所保存的stdout指針復制給文件描述符save_fdprintf("save_fd = %d\n", save_fd); //save_fd是文件描述符4,里面保存指向stdout的文件指針write(save_fd, msg, strlen(msg)); //向save_fd寫,既是向stdout寫,會寫到屏幕//將fd(3)保存的指向file1的文件指針復制給STDOUT_FILENO(1),并覆蓋1原來保存的文件指針int ret = dup2(fd, STDOUT_FILENO); //結果是fd指向file1文件,STDOUT_FILENO(1)也指向file1文件printf(" -------> m = %d\n", ret); //printf默認對應文件描述符1,但是現在1指向file1文件close(fd); //fd(3)被關閉puts(msg); return 0; }dup2 示例代碼:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <string.h>int main(void) {int fd, save_fd;char msg[] = "It's just a test for dup2!\n";fd = open("test", O_RDWR|O_CREAT|O_TRUNC, 0644); //<fcntl.h>if(fd < 0){perror("open error");exit(1);}save_fd = dup(STDOUT_FILENO); //STDOUT_FILENO <unistd.h>printf("save_fd = %d\n", save_fd); #if 0dup2(STDOUT_FILENO, fd);write(fd, msg, strlen(msg)); #elsedup2(fd, STDOUT_FILENO);puts(msg); write(fd, msg, strlen(msg));//??? #endifclose(fd);return 0; }示例代碼:
/**如果使用dup2給一個文件制定了兩個描述符的時候*一個文件描述符關閉,依然能使用dup2的新文件描述符對該文件讀寫*/ #include <unistd.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> #include <stdio.h>int main(void) {int fd, fd2;char *str = "use fd write in\n";char *str2 = "use ====fd2==== write\n";fd = open("test", O_WRONLY|O_TRUNC|O_CREAT, 0644);if(fd < 0){perror("open test error");exit(1);}fd2 = open("test", O_WRONLY);dup2(fd, fd2);write(fd, str, strlen(str));close(fd);printf("----------------done close(fd)--------------\n");int ret = write(fd2, str2, strlen(str2));if(ret == -1){perror("write fd2 error");exit(1);}close(fd2);return 0; }示例代碼:
#include <unistd.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> #include <stdio.h>int main(void) {int fd;char *str = "hello dup2\n";//write(STDOUT_FILENO, str, strlen(str));fd = open("test", O_WRONLY|O_TRUNC|O_CREAT, 0644);if(fd < 0){perror("open test1 error");exit(1);}//dup2(STDOUT_FILENO, fd); dup2(fd, STDOUT_FILENO);close(fd);//做文件關閉之前同樣的事。//int n = write(STDOUT_FILENO, str, strlen(str)); int n = write(fd, str, strlen(str)); printf("--------|%d\n", n);return 0; }代碼:
/*編程程序,要求程序執行效果等同于命令 cat file1 - file2 > out 執行效果。 注:Linux 系統下, Ctrl+d 可輸出一個文件結束標記 EOF。 */#include <unistd.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> #include <stdio.h>void sys_err(int fd, char *err_name) {if(fd < 0){perror(err_name);exit(1);} }int main(void) {int fd_f1, fd_f2, fd_out;int ret;char buf[1024];fd_f1 = open("file1", O_RDONLY);sys_err(fd_f1, "open file1 error");fd_f2 = open("file2", O_RDONLY);sys_err(fd_f2, "open file2 error");fd_out = open("out", O_WRONLY|O_TRUNC|O_CREAT, 0644);sys_err(fd_out, "open out error");dup2(fd_out, STDOUT_FILENO);while ((ret = read(fd_f1, buf, sizeof(buf)))) {write(fd_out, buf, ret);}while ((ret = read(STDIN_FILENO, buf, sizeof(buf)))) {write(fd_out, buf, ret);}while ((ret = read(fd_f2, buf, sizeof(buf)))) {write(fd_out, buf, ret);}close(fd_f1);close(fd_f2);close(fd_out);return 0; }
 
fcntl 函數
fcntl 可以改變一個已打開的文件的屬性,可以重新設置讀、寫、追加、非阻塞等標志(這些標志稱為File Status Flag),而不必重新open文件
#include <unistd.h> #include <fcntl.h> int fcntl(int fd, int cmd, ... /* arg */ );int fcntl(int fd, int cmd); int fcntl(int fd, int cmd, long arg); int fcntl(int fd, int cmd, struct flock *lock); 獲取和設置文件的訪問控制屬性 參數解析:第一個為已經打開的文件描述符第二個為要對文件描述采取的動作 F_DUPFD 復制一個文件描述,返回值為新描述符。F_GETFD/F_SETFD 目前只有FD_CLOEXEC一個,set時候會用到第三個參數。 F_GETFL / F_SETFL 得到或者設置目前的文件描述符屬性,返回值為當前屬性。設置時使用第三個參數。在本節的各實例中,第三個參數總是一個整數,與上面所示函數原型中的注釋部分相對應。 但在說明記錄鎖時,第三個參數則是指向一個結構的指針。 fcntl函數有五種功能:?復制一個現存的描述符(cmd=F_DUPFD)。?獲得/設置文件描述符標記(cmd=F_GETFD或F_SETFD)。?獲得/設置文件狀態標志(cmd=F_GETFL或F_SETFL)。?獲得/設置異步I/O有權(cmd=F_GETOWN或F_SETOWN)。?獲得/設置記錄鎖(cmd=F_GETLK,F_SETLK或F_SETLKW)。 我們先說明這十種命令值中的前七種(后三種都與記錄鎖有關,當講解記錄鎖時說明) 將涉及與進程表項中各文件描述符相關聯的文件描述符標志,以及每個文件表項中的文件狀態標志,這個函數和open一樣,也是用可變參數實現的,可變參數的類型和個數取決于前面的cmd參數。下面的例子使用F_GETFL和F_SETFL這兩種fcntl命令改變STDIN_FILENO的屬性,加上O_NONBLOCK選項,實現和 “非阻塞讀終端” 同樣的功能。
 
簡單使用:
1、獲取文件的flags,即open函數的第二個參數:flags = fcntl(fd,F_GETFL,0);2、設置文件的flags:fcntl(fd,F_SETFL,flags);3、增加文件的某個flags,比如文件是阻塞的,想設置成非阻塞:flags = fcntl(fd,F_GETFL,0);flags |= O_NONBLOCK;fcntl(fd,F_SETFL,flags);4、取消文件的某個flags,比如文件是非阻塞的,想設置成為阻塞:flags = fcntl(fd,F_GETFL,0);flags &= ~O_NONBLOCK;fcntl(fd,F_SETFL,flags);用fcntl改變File Status Flag
#include <unistd.h> #include <fcntl.h> #include <errno.h> #include <stdio.h> #include <stdlib.h>#define N 1024int main(void) {int flags, n;char buf[N];flags = fcntl(STDIN_FILENO, F_GETFL);flags |= O_NONBLOCK;fcntl(STDIN_FILENO, F_SETFL, flags);again:n = read(STDIN_FILENO, buf, N);if(n == -1){if(errno == EWOULDBLOCK) // errno == EAGAIN(非阻塞讀終端){ printf("no data...\n");sleep(3);goto again;}else {perror("read error");exit(1);}}write(STDOUT_FILENO, buf, n);return 0; }或者?
#include <unistd.h> #include <fcntl.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h>#define MSG_TRY "try again\n"int main(void) {char buf[10];int flags, n;flags = fcntl(STDIN_FILENO, F_GETFL);if(flags == -1){perror("fcntl error");exit(1);}flags |= O_NONBLOCK;int ret = fcntl(STDIN_FILENO, F_SETFL, flags);if(ret == -1){perror("fcntl error");exit(1);}tryagain:n = read(STDIN_FILENO, buf, 10);if(n < 0){if(errno != EAGAIN){ perror("read /dev/tty");exit(1);}sleep(3);write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));goto tryagain;}write(STDOUT_FILENO, buf, n);return 0; }示例程序2:
//獲取和設置文件flags舉例#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <error.h>char buf[500000];int main(int argc,char *argv[]) {int ntowrite,nwrite;const char *ptr ;int flags;ntowrite = read(STDIN_FILENO,buf,sizeof(buf));if(ntowrite <0) { perror("read STDIN_FILENO fail:");exit(1);} fprintf(stderr, "read %d bytes\n", ntowrite);if((flags = fcntl(STDOUT_FILENO,F_GETFL,0))==-1){ perror("fcntl F_GETFL fail:");exit(1);} flags |= O_NONBLOCK;if(fcntl(STDOUT_FILENO,F_SETFL,flags)==-1){ perror("fcntl F_SETFL fail:");exit(1);} ptr = buf;while(ntowrite > 0){ nwrite = write(STDOUT_FILENO,ptr,ntowrite);if(nwrite == -1) { perror("write file fail:");} if(nwrite > 0){ ptr += nwrite;ntowrite -= nwrite;} } flags &= ~O_NONBLOCK;if(fcntl(STDOUT_FILENO,F_SETFL,flags)==-1){ perror("fcntl F_SETFL fail2:");} return 0; }
 
sync函數
 
 
 
 
ioctl 函數
ioctl 函數是 I/O 操作的雜物箱。不能用本章中其他函數表示的 I/O 操作通常都能用 ioctl 表示。終端 I/O是 ioctl 的最大使用方面
 
ioctl用于向設備發控制和配置命令,有些命令也需要讀寫一些數據,但這些數據是不能用read/write讀寫的,稱為Out-of-band數據。也就是說,read/write讀寫的數據是in-band數據,是I/O操作的主體,而ioctl命令傳送的是控制信息,其中的數據是輔助的數據。例如,在串口線上收發數據通過read/write操作,而串口的波特率、校驗位、停止位通過ioctl設置,A/D轉換的結果通過read讀取,而A/D轉換的精度和工作頻率通過ioctl設置。
以下程序使用TIOCGWINSZ命令獲得終端設備的窗口大小。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/ioctl.h>int main(void) {struct winsize size;if (isatty(STDOUT_FILENO) == 0)exit(1);if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &size)<0) {perror("ioctl TIOCGWINSZ error");exit(1);}printf("%d rows, %d columns\n", size.ws_row, size.ws_col);return 0; }
 
/dev/fd
? ? ? ? 比較新的系統都提供名為 /dev/fd 的目錄,其目錄項是名為 0、1、2等的文件。打開文件 /dev/fd/n 等效于復制描述符n (假定描述符n是打開的)。
在函數中調用:
? ? ? ? fd = open("/dev/fd/0", mode);
 大多數系統忽略所指定的 mode,而另外一些則要求 mode 是所涉及的文件 (在這里則是標準輸入)原先打開時所使用的 mode 的子集。因為上面的打開等效于:
 ? ? ? ? fd = dup(0);
 描述符 0 和 fd 共享同一文件表項(見圖3 - 3 )。例如,若描述符0被只讀打開,那么我們也只對 fd 進行讀操作。即使系統忽略打開方式,并且下列調用成功:
 ? ? ? ? fd = open("/dev/fd/0", O_RDWR);
 我們仍然不能對 fd 進行寫操作。
 
? ? ? ? 我們也可以用/dev/fd作為路徑名參數調用creat,或調用open,并同時指定O_CREAT。這就允許調用creat的程序,如果路徑名參數是/dev/fd/1等仍能工作。
 某些系統提供路徑名/dev/stdin,/dev/stdout和/dev/stderr。這些等效于/dev/fd/0,/dev/fd/1和/dev/fd/2。
 ? ? ? ? /dev/fd文件主要由shell使用,這允許程序以對待其他路徑名一樣的方式使用路徑名參數來處理標準輸入和標準輸出。例如,cat(1)程序將命令行中的一個單獨的-特別解釋為一個輸入文件名,該文件指的是標準輸入。例如:
 ? ? ? ? filterfile2|catfile1-file3|lpr
 首先cat讀file1,接著讀其標準輸入(也就是filterfile2命令的輸出),然后讀file3,如若支持/dev/fd,則可以刪除cat對-的特殊處理,于是我們就可鍵入下列命令行:
 ? ? ? ? filterfile2|catfile1/dev/fd/0file3|lpr
 在命令行中用-作為一個參數特指標準輸入或標準輸出已由很多程序采用。但是這會帶來一些問題,例如若用-指定第一個文件,那么它看來就像開始了另一個命令行的選擇項。/dev/fd則提高了文件名參數的一致性,也更加清晰。
 
 
最后些一個測試程序,希望可以用到里面大多數函數,用于測試其功能。這個程序功能是打開一個文件,在里面寫入hello world,然后調用dup函數復制一個文件描述符,隨后調用lseek將偏移量設置到hello之后,最后讀出文件內容world打印到終端顯示。代碼如下所示
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <string.h> int main(void) { int fd, fdd, ret; char str[]="hello world!"; char buf[10]; fd = open("file", O_RDWR|O_CREAT|O_TRUNC, 755); if(fd < 0){ perror("open error"); exit(1); } ret = write(fd, str, sizeof(str)); if(ret != sizeof(str)){ perror("write error"); exit(1); } fdd = dup(fd); if(ret == -1){ perror("dup error"); exit(1); } lseek(fdd, 6, SEEK_SET); memset(buf,0,sizeof(buf)); ret = read(fdd, buf, sizeof(buf)); if(ret < 0){ perror("read error"); exit(1); } printf("%s/n",buf); return 0; }
 
統計一個目錄下普通文件個數:
/*編程統計指定目錄下普通文件個數。 包括其子目錄下的普通文件.將文件總數打印至屏幕。 */#include <unistd.h> #include <string.h> #include <dirent.h> #include <stdio.h> #include <stdlib.h> #include <sys/stat.h>int count(char *root) {DIR *dp;struct dirent *item;int n = 0; char path[1024];dp = opendir(root); //打開目錄if (dp == NULL) {perror("----opendir error");exit(1); }//遍歷每一個目錄項,NULL表示讀完while ((item = readdir(dp))) { struct stat statbuf;//排除.目錄和..目錄if (strcmp(item->d_name, ".") == 0 || strcmp(item->d_name, "..") == 0)continue;//將子目錄和當前工作目錄拼接成一個完整文件訪問路徑sprintf(path, "%s/%s", root, item->d_name);/*取文件的屬性, lstat防止穿透*/if (lstat(path, &statbuf) == -1) {perror("lstat error");exit(1);}if (S_ISREG(statbuf.st_mode)) {n++;} else if (S_ISDIR(statbuf.st_mode)) {n += count(path);//遞歸調用該函數}}closedir(dp);return n; }int main(int argc, char *argv[]) {int total = 0;if (argc == 1) {total = count(".");printf("There are %d files in ./\n", total);} else if (argv[1]) {total = count(argv[1]);printf("There are %d files in %s\n", total, argv[1]);}return 0; }
 
 
 
 
 
總結
以上是生活随笔為你收集整理的Linux 文件 IO的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 一篇文章带你搞懂 DEX 文件的结构
 - 下一篇: MFC 最详细入门教程