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