3.2文件描述符
- 對於內核而言,所有打開的文件都是通過文件描述符引用。
- 文件描述符是一個非負整數
- 常量STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO分別代表標準輸入、標準輸出、標準出錯輸出,定義在頭文件<unistd.h>中。
3.3open函數(P48)
對於open函數而言,僅當創建新文件是才使用第三個參數。其他情況第三個參數直接省去,相當於只有兩個參數。#include <fcntl.h> int open(const char *pathname, int oflag, .../* mode_t mode */);
- open函數返回的文件描述符一定是最小的未用描述符數值。
3.4creat函數(P49)
#include <fcntl.h> int creat(const char *pathname, mode_t mode);
- creat的一個不足之處是它以只寫方式打開所創建的文件。
3.5close函數(P50)
#include <unistd.h> int close(int filedes);
- 關閉一個文件時還會釋放該進程加在該文件上的所有記錄鎖。
- If fd is the last file descriptor referring to the underlying open
file description (see open(2)), the resources associated with the
open file description are freed; if the descriptor was the last ref‐
erence to a file which has been removed using unlink(2) the file is
deleted.
3.5lseek函數
#include <unistd.h> off_t lseek(int filedes, off_t offset, int whence); 若成功則返回新的文件偏移量,若出錯則返回-1
- 如果文件描述符引用的是一個管道、FIFO或網絡套接字,則lseek返回-1,並將errno設置爲ESPIPE
- 某些設備可能允許負的偏移量,所以在比較lseek的返回值時應當謹慎,不要測試它是否小於0,二要測試它是否等於-1.
- 文件空洞是由所設置的偏移量超過文件尾端,並寫了某些數據後所造成的。位於文件中但沒有寫過的字節都被讀爲0.文件中的空洞並不要求在磁盤上佔用存儲區。
3.6read函數
#include <unistd.h> ssize_t read(int filedes, void *buf, size_t nbytes); 若成功則返回讀到的字節數,若已到文件結尾則返回0,若出錯則返回-1.
3.7write函數
#include <unistd.h> ssize_t write(int filedes, const void *buf, size_t nbytes); 若成功返回已寫的字節數,若出錯則返回-1.
- 其返回值通常與參數nbytes的值相同,否則表示出錯。
3.8文件共享
- 內核使用三種數據結構表示打開的文件,他們之間的關係決定了在文件共享方面一個進程對另一個進程可能產生的影響(如圖3-1)
- 每個進程在進程表中都有一個記錄項,記錄項中包含有一張打開文件描述符表,
- 內核爲所有打開文件位置一張文件表。
- 每個打開文件(或設備)都有一個v節點(v-node)結構。v節點包含了文件類型和對比文件進行各種操作的函數的指針。對於大多數文件,v節點還包含了該文件的i節點(i-node,索引節點)。這些信息是在打開文件時從磁盤上讀入內存的,所以所有關於文件的信息都是快速可供使用的。
- 如果兩個獨立進程各自打開了同一個文件按,則有圖3-2中所示的安排。打開該文件的每個進程都得到一個文件表項,但對一個給定的文件只有一個v節點表項。每個進程都有自己的文件表項的一個理由是:這種安排是每個進程都有它自己的對該文件的當前偏移量。
- 給出了這些數據結構後,現在對前面所述的操作作進一步說明。
- 在完成每個write後,在文件表項中的當前文件偏移量即增加所寫的字節數。如果這使當前文件偏移量超過了當前文件長度,則在i節點表項中的當前文件長度被設置爲當前文件偏移量(也就是該文件加長了)。
- 如果用O_APPEND標誌打開一個文件,則相應標誌也被設置到文件表項的文件狀態標誌中。每次對這種具有添寫標誌的文件執行操作時,在文件表項中的當前文件偏移量首先被設置爲i節點表項中的文件長度。這就使每次寫的數據都添加到文件的當前尾端處。
- 若一個文件用lseek定位到文件當前尾端,則文件表項中的當前文件偏移量被設置爲i節點表項中的當前長度。
- lseek函數值修改文件表項中的當前文件偏移量,沒有進行任何I/O操作。
- 可能多個文件描述符項指向同一個文件表項。(如dup函數,fork後,此時父子進程對於每一個打開文件描述符共享同一個文件表項。
- 文件描述符標誌和文件狀態標誌在作用域方面的區別,前者值用於一個進程的一個描述符,而後者則適用於指向該給定文件表項的任何進程中的所有描述符。
3.11
- pread和pwrite允許原子行的定位搜索和執行I/O。
#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);
- 原子操作指的是由多步組成的操作。如果該操作原子的執行,則要麼執行完所有步驟,要麼一步也不執行,不可能只執行所有步驟中的一個子集。
3.12dup和dup2函數
- 下面兩個函數用來複制一個現存的文件描述符。
#include <unistd.h> int dup(int filedes); int dup2(int filedes, int filedes2); 若成功則返回新的文件描述符,若出錯則返回-1
- 由dup返回的新文件描述符一定是當前可用文件描述符中的最小數值。用dup2則可以用filedes2參數指定新文件描述符的數值。如果filedes2已經打開,則先將其關閉。如果filedes等於filedes2,則dup2返回filedes2,而不關閉它。
- 這些函數返回的新聞鍵描述符與參數filedes共享同一個文件表項。圖3-3顯示了這種情況。
3.13sync、fsync和fdatasync函數
- 當將數據寫入文件是,內核通常先將該數據複製到其中一個緩衝區中如果該緩衝區尚未寫滿,則並不將其排入輸出隊列,而是等待其寫滿或者當內核需要重用該緩衝區以便存放其他磁盤塊數據時,再將該緩衝區排入輸出隊列,然後待其到達隊首時,才進行實際的I/O操作,這種輸出方式被成爲延遲寫。
- 爲了保證磁盤上的實際文件系統與緩衝區高速緩存中內容的一致性,UNIX系統提供了sync、fsync和fdatasync三個函數
sync函數只是將所有修改過的塊緩衝取區排入寫隊列,然後就返回,它並不等待實際寫磁盤操作結束。#include <unistd.h> int fsync(int filedes); int fdatasync(int filedes); 若成功返回0,若出錯返回-1 void sync(void);
fsync函數只對由文件描述符filedes指定的單一文件起作用,並且等待寫磁盤操作結束,然後返回
fdatasync函數類似於fsync,但它隻影響文件的數據部分。而除數據外,fsync還會同步更新文件的屬性。
3.14fcntl函數
- fcntl函數可以改變已打開文件的性質。
#include <fcntl.h> int fcntl(int filedes, int cmd, .../* int arg */); 若成功則依賴於cmd,若出錯則返回-1.
在本節的各實例中,第三個參數總是一個整數。但是在14.3節說明記錄鎖時,第三個參數則是指向一個結構的指針。 - fcntl函數有5種功能:
- 複製一個現有的描述符(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)
- 在修改文件描述符標誌或文件狀態標誌時必須謹慎,先要取得現有的標誌值,然後根據需要修改它,最後設置新標誌值。不能只是執行F_SETFD或F_SETFL命令,這樣會關閉以前設置的標誌位。
- 將fsync和fdatasync函數與O_SYNC標誌相比較,fsync和fdatasync在我們需要時更新文件內容,O_SYNC標誌則在我們每次寫至文件時更新文件內容。
3.15ioctl函數
#include <sys/ioctl.h> int ioctl(int filedes, int request, ...); 若出錯返回-1,若成功則返回其他值。
習題
1.當讀/寫磁盤文件時,本章中描述的函數是否有緩衝機制?請說明原因。
所有的磁盤I/O都要經過內核的塊緩衝區(也成爲內核的緩衝區高速緩存),唯一例外的是對原始磁盤設備的I/O。既然read或write的數據都要被內核緩衝,那麼術語“不帶緩衝的I/O“指的是在用戶的進程中對這兩個函數不會自動緩衝,每次read或write就要進行一次系統調用。
2.編寫一個與3.12節中dup2功能相同的函數,要求不調用fcntl函數,並且要有正確的出錯處理。
轉自:http://blog.csdn.net/lgg201/article/details/6685241
int ud_dup2(const int ofd, const int nfd) { //新描述符等於舊描述符,不關閉直接返回 if(ofd == nfd) return ofd; int pid = getpid(); char *pathname = malloc(sizeof(char) * 128); sprintf(pathname, "/proc/%d/fd/%d", getpid(), nfd); //如果新描述符已經被打開,關閉它 if(!access(pathname, F_OK)) close(nfd); int tmp; int max = sysconf(_SC_OPEN_MAX); int fds[max], i = 0; //如果新描述符值大於最大描述符數, 返回錯誤 if(max < nfd) return -1; do { tmp = dup(ofd); //dup出錯 if(tmp < 0) break; fds[i ++] = tmp; } while(tmp < nfd); //如果拷貝出錯,則i不自減,也關閉最後一次複製的描述符, 否則,最後的爲新描述符, 不關閉 if(tmp == nfd) i --; //關閉複製的描述符 while(i-- >= 0) close(fds[i]); if(tmp != nfd) return -1; return nfd; }
3.6如果使用添加標誌打開一個文件以便讀、寫,能否仍用lseek在任一位置開始讀?能否用lseek更新文件中任一部分的數據?請編寫一個程序以驗證之。
這種情況下,仍然可以用lseek和read函數讀文件中任意一處的內容。但是write函數在寫數據之前會自動將文件偏移量設置爲文件尾,所以寫文件時只能從文件尾開始。
#include <stdio.h> #include <fcntl.h> #include <unistd.h> int main() { int fd; int n; char buf[4]; if ((fd = open("tmp", O_RDWR | O_APPEND)) == -1) perror("open error"); if (lseek(fd, 0, SEEK_SET) == -1) perror("lseek error"); if ((n = read(fd, buf, 3)) == -1) perror("read error"); printf("%c %c %c\n", buf[0], buf[1], buf[2]); if (write(fd, buf, n) != 3) perror("write error"); return 0; }