第5章:node
主要介紹了文件I/O更深刻的一些內容。編程
原子操做,將一個系統調用所要完成的全部動做做爲一個不可中斷的操做,一次性執行;這樣能夠避免競爭狀態(兩個或多個共享資源的進程或線程的運行結果是一個沒法預期的順序)。app
以獨佔方式建立一個文件:對文件是否存在的檢查和建立文件屬於同一個原子操做。防止新建文件的時候由於檢查文件是否存在和新建文件之間發生中斷(而其餘進程也在新建相同文件名的文件),致使兩個進程都認爲本身是文件的建立者。ide
向文件尾部追加數據:將文件的偏移量的移動與數據的寫操做屬於同一個原子操做。防止多個進程同時往同一個文件尾部添加數據致使數據混亂。post
fcntl(),對一個打開的文件描述符執行一系列的操做。測試
1 #include <fcntl.h> 2 3 int fcntl(int fd, int cmd, ...);
fd爲文件描述符,cmd是決定具體操做,第三個參數(可選)用來設置爲不一樣的類型。atom
cmd參數(部分),具體查看man手冊:spa
F_DUPFD | 複製文件描述符 |
F_GETFD | 獲取文件描述符 |
F_SET_FD | 設置文件描述符 |
F_GETFL | 獲取文件訪問模式和狀態標誌 |
F_SETFL | 設置文件訪問模式和狀態標誌 |
文件描述符與打開文件之間的關係:多個文件描述符能夠指向同一個打開文件。他們的關係以下命令行
文件描述符表、打開文件表和i-node表。打開文件表的條目成爲打開文件句柄(open file handle)。
PS:若是兩個不一樣的文件描述符指向同一個打開文件句柄,這兩個文件描述符將共享相同的文件偏移量。(打開文件句柄裏包含文件偏移量file offset)。
dup(),複製一個打開的文件描述符oldfd,並返回新的描述符。
dup2(),複製oldfd指定的文件描述符,返回newfd參數指定的描述符。
dup3(),參數與dup2()相同,添加了flags,用於修改系統調用行爲。
1 #include <unistd.h> 2 3 int dup(int oldfd); 4 5 int dup2(int oldfd, int newfd); 6 7 int dup3(int oldfd, int newfd, int flags);
成功調用返回新的文件描述符,失敗返回-1。
pread()和pwrite(),在指定參數所指定的位置進行文件I/O操做,但不改變文件的偏移量。
1 #include <unistd.h> 2 3 ssize_t pread(int fd, void *buf, size_t count, off_t offset); 4 5 ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);
fd爲文件描述符,buf爲緩衝區, count爲緩衝區字節數, offset爲偏移量。
pread()成功調用返回讀取的字節數,失敗返回-1
pwrite()成功調用返回寫入的字節數,失敗返回-1
------------------------省略分散輸入和集中輸出,截斷文件,非阻塞I/O和大文件I/O等一些知識點---------------
練習:
5-1,請使用標準文件I/O系統調用(open()和lseek())和off_t數據類型修改程序清單5-3中的程序。將宏_FILE_OFFSET_BITS的值設置爲64進行編譯,並測試該程序是否可以成功建立一個大文件。
1 /* 2 * ===================================================================================== 3 * 4 * Filename: large_file.c 5 * 6 * Description: 7 * 8 * Version: 1.0 9 * Created: 2014年03月17日 22時05分50秒 10 * Revision: none 11 * Compiler: gcc 12 * 13 * Author: alan (), alan19920626@gmail.com 14 * Organization: 15 * 16 * ===================================================================================== 17 */ 18 19 #define _FILE_OFFSET_BITS 64 20 #include <sys/stat.h> 21 #include <fcntl.h> 22 #include "tlpi_hdr.h" 23 24 int main(int argc, char *argv[]){ 25 int fd; 26 off_t off; 27 if(argc != 3 || strcmp(argv[1], "--help") == 0) 28 usageErr("%s pathname offset\n", argv[0]); 29 30 fd = open(argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); 31 32 if(fd == -1) 33 errExit("open"); 34 35 off = atoll(argv[2]); 36 if(lseek(fd, off, SEEK_SET) == -1) 37 errExit("lseek"); 38 39 if(write(fd, "test", 4) == -1) 40 errExit("write"); 41 exit(EXIT_SUCCESS); 42 43 }
測試結果:
1 lancelot@debian:~/Code/tlpi$ ./large_file largefile 10111222333 2 lancelot@debian:~/Code/tlpi$ ls -l largefile 3 -rw------- 1 lancelot lancelot 10111222337 4月 9 23:22 largefile
5-2,編寫一個程序,使用O_APPEND標誌並以寫方式打開一個已存在的文件,且將文件偏移量置於起始位置,再寫入數據。數據會顯示在文件中的哪一個位置?爲何?
1 /* 2 * ===================================================================================== 3 * 4 * Filename: 5-2.c 5 * 6 * Description: 7 * 8 * Version: 1.0 9 * Created: 2014年03月17日 22時26分51秒 10 * Revision: none 11 * Compiler: gcc 12 * 13 * Author: alan (), alan19920626@gmail.com 14 * Organization: 15 * 16 * ===================================================================================== 17 */ 18 19 #include <sys/stat.h> 20 #include <ctype.h> 21 #include <fcntl.h> 22 #include "tlpi_hdr.h" 23 24 int main(int argc, char *argv[]){ 25 int fd; 26 off_t off; 27 ssize_t numWritten; 28 29 if(argc != 2 || strcmp(argv[1], "--help") == 0) 30 usageErr("%s file", argv[0]); 31 32 fd = open(argv[1], O_RDWR | O_APPEND, S_IRUSR | S_IWUSR); 33 if(fd == -1) 34 errExit("open"); 35 36 off = lseek(fd, 0, SEEK_SET); 37 if(off == -1) 38 errExit("lseek"); 39 40 numWritten = write(fd, "Kevin Durant\n", 13); 41 if(numWritten == -1) 42 errExit("write"); 43 44 close(fd); 45 exit(EXIT_SUCCESS); 46 }
測試結果:
1 lancelot@debian:~/Code/tlpi$ cat t1 2 This is the second line. 3 This is the third line. 4 This is the append line. 5 6 lancelot@debian:~/Code/tlpi$ ./write_append t1 7 lancelot@debian:~/Code/tlpi$ cat t1 8 This is the second line. 9 This is the third line. 10 This is the append line. 11 12 Kevin Durant
5-3,本習題的設計目標在於展現爲什麼以O_APPEND標誌打開文件來保障操做的原子性是必要的。請編寫一程序,可接收多達3個命令行參數:
$ automic_append filename num-bytes [x]
該程序應打開制定的文件,而後以每次調用write()寫入一個字節的方式,向文件尾部追加num-bytes個字節。缺省狀況下,程序使用O_APPEND標誌打開文件,但若存在第三個命令行參數(x),那麼打開文件時將再也不使用O_APPEND標誌,代之以調用write()前調用lseek(fd, 0, SEEK_END)。同時運行該程序的兩個實例,不帶x參數,將100萬字節寫入同一個文件:
$ automic_append f1 1000000 & automic_append f1 1000000
重複上述操做,將數據寫入另外一個文件,但運行時加入x參數
$ automic_append f2 1000000 x & automic_append f2 1000000 x
使用ls -l命令檢查文件f1和f2的大小, 並解釋兩文件大小不一樣的緣由。
1 /* 2 * ===================================================================================== 3 * 4 * Filename: atomic_append.c 5 * 6 * Description: 7 * 8 * Version: 1.0 9 * Created: 2014年03月17日 22時46分49秒 10 * Revision: none 11 * Compiler: gcc 12 * 13 * Author: alan (), alan19920626@gmail.com 14 * Organization: 15 * 16 * ===================================================================================== 17 */ 18 19 #include <sys/stat.h> 20 #include <fcntl.h> 21 #include "tlpi_hdr.h" 22 23 int main(int argc, char *argv[]){ 24 int i, fd, flags, numBytes; 25 off_t off; 26 ssize_t numWritten; 27 28 29 flags = O_RDWR | O_CREAT; 30 if(argc < 3 || strcmp(argv[1], "--help") == 0 ) 31 usageErr("%s filename num-bytes [x]"); 32 33 if(argc != 4) 34 flags = flags | O_APPEND; 35 36 numBytes = getInt(argv[2], 0, "num-bytes"); 37 38 fd = open(argv[1], flags, S_IRUSR | S_IWUSR); 39 if(fd == -1) 40 errExit("open"); 41 42 /*if(argc == 4) 43 if(lseek(fd, 0, SEEK_END) == -1) 44 errExit("lseek"); 45 46 */ 47 48 for(i = 0; i < numBytes; ++i){ 49 if(argc > 3 && argv[3] == "x") 50 if(lseek(fd, 0, SEEK_END) == -1) 51 errExit("lseek"); 52 53 if(write(fd, "A", 1) != 1) 54 fatal("write() failed"); 55 } 56 57 exit(EXIT_SUCCESS); 58 }
測試結果:
1 lancelot@debian:~/Code/tlpi$ ls -l f1 f2 2 -rw------- 1 lancelot lancelot 2000000 4月 9 23:45 f1 3 -rw------- 1 lancelot lancelot 1000000 4月 9 23:45 f2
5-4,使用fcntl()和close()來實現dup()和dup2()。務必牢記dup2()須要處理的一種特殊狀況,即oldfd與newfd相等。這時,應檢查oldfd是否有效,測試fcntl(oldfd, F_GETFL)是否成功就能達到這一目標。若oldfd無效,則dup2()將返回-1,並將errno置爲EBADF。
1 /* 2 * ===================================================================================== 3 * 4 * Filename: 5-4.c 5 * 6 * Description: 7 * 8 * Version: 1.0 9 * Created: 2014年03月19日 08時48分46秒 10 * Revision: none 11 * Compiler: gcc 12 * 13 * Author: alan (), alan19920626@gmail.com 14 * Organization: 15 * 16 * ===================================================================================== 17 */ 18 19 #include <sys/stat.h> 20 #include <fcntl.h> 21 #include "tlpi_hdr.h" 22 23 int Dup(int oldfd); 24 25 int Dup2(int oldfd, int newfd); 26 27 int main(){ 28 int fd; 29 int newfd; 30 if((fd = open("t1", O_RDONLY)) == -1) 31 errExit("open"); 32 33 //newfd = fcntl(fd, F_DUPFD, fd+1); 34 newfd = Dup(fd); 35 if(newfd == -1) 36 errExit("Dup"); 37 38 printf("old fd is %d, new fd is %d\n", fd, newfd); 39 40 printf("Input the new fd:"); 41 scanf("%d", &newfd); 42 43 /*if(fcntl(oldfd, F_GETFL) == -1) 44 errExit("fd is ") 45 */ 46 47 newfd = Dup2(fd, newfd); 48 if(newfd == -1) 49 errExit("Dup2"); 50 printf("old fd is %d, new fd is %d\n", fd, newfd); 51 52 exit(EXIT_SUCCESS); 53 } 54 55 int Dup(int oldfd){ 56 int newfd = fcntl(oldfd, F_DUPFD, oldfd+1); 57 return newfd; 58 } 59 60 int Dup2(int oldfd, int newfd){ 61 int fd = newfd; 62 if(fcntl(oldfd, F_GETFL) == -1){ 63 errno = EBADF; 64 return -1; 65 } 66 if(oldfd == newfd) 67 return newfd; 68 else{ 69 if(fcntl(newfd, F_GETFL) != -1) 70 close(newfd); 71 newfd = fcntl(oldfd, F_DUPFD, fd); 72 if(newfd != fd) 73 newfd = fcntl(newfd, F_SETFD, fd); 74 75 return newfd; 76 } 77 }
測試結果:
1 lancelot@debian:~/Code/tlpi$ ./my_dup 2 old fd is 3, new fd is 4 3 Input the new fd:13 4 old fd is 3, new fd is 13