1、文件描述符node
對於Linux而言,全部對設備或文件的操做都是經過文件描述符進行的。當打開或者建立一個文件的時候,內核向進程返回一個文件描述符(非負整數)。後續對文件的操做只需經過該文件描述符,內核記錄有關這個打開文件的信息(file結構體)。
一個進程啓動時,默認打開了3個文件,標準輸入、標準輸出、標準錯誤,對應文件描述符是0(STDIN_FILENO)、1(STDOUT_FILENO)、2(STDERR_FILENO),這些常量定義在unistd.h頭文件中。ubuntu
2、open系統調用緩存
(1)函數原型 int open(const char *path, int flags);網絡
參數
path :文件的名稱,能夠包含(絕對和相對)路徑
flags:文件打開模式
返回值:
打開成功,返回文件描述符;打開失敗,返回-1
數據結構
(2)函數原型 int open(const char *path, int flags,mode_t mode);async
參數
path :文件的名稱,能夠包含(絕對和相對)路徑
flags:文件打開模式
mode: 用來規定對該文件的全部者,文件的用戶組及系 統中其餘用戶的訪問權限
返回值:
打開成功,返回文件描述符;打開失敗,返回-1
函數
打開文件的方式:測試
O_RDONLY 只讀打開
O_WRONLY 只寫打開
O_RDWR 讀寫打開spa
O_APPEND 寫入的全部數據將被追加到文件的末尾
O_CREAT 打開文件,若是文件不存在則創建文件。須要第三個參數mode,用來指定該文件的訪問權限。
O_EXCL 若是已經置O_CREAT且文件存在,則會出錯。用此用於檢測文件是否存在,若是不存在建立此文件。
O_TRUNC 若是此文件存在,並且爲只寫或讀寫成功打開,則將其長度截短爲0。unix
3、creat系統調用
建立一個新文件
函數原型 int creat(const char *pathname, mode_t mode);
注意,此函數等效於: open(pathname,O_WRONLY | O_CREAT | O_TRUNC,mode);
creat的一個不足之處是它以只寫方式打開所建立的文件。
4、close()系統調用
函數原型: int close(int fd);
函數參數:
fd :要關閉的文件的文件描述符
返回值:
若是出現錯誤,返回-1;調用成功返回0
5、read系統調用
一旦有了與一個打開文件描述相關連的文件描述符,只要該文件是用O_RDONLY或O_RDWR標誌打開的,就能夠用read()系統調用從該文件中讀取字節 。
讀操做從文件的當前偏移量處開始,在成功返回以前,該偏移量將增長實際讀到的字節數。
函數原型: ssize_t read(int fd, void *buf, size_t count);
參數:
fd :想要讀的文件的文件描述符
buf : 指向內存塊的指針,從文件中讀取來的字節放到這個內存塊中
count : 從該文件複製到buf中的字節個數
返回值:
若是出現錯誤,返回-1;讀文件結束,返回0;不然返回從該文件複製到規定的緩衝區中的字節數。
6、write系統調用
用write()系統調用將數據寫到一個文件中
其返回值一般與參數count的值相同,不然出錯。write出錯的一個常見緣由是:磁盤已寫滿,或者超過了一個給定進程的文件長度限制。
對於普通文件,寫操做從文件的當前偏移量處開始。在一次成功寫以後,該文件偏移量增長實際寫的字節數。
函數原型: ssize_t write(int fd, const void *buf, size_t count);
函數參數:
fd:要寫入的文件的文件描述符
buf: 指向內存塊的指針,從這個內存塊中讀取數據寫入 到文件中
count: 要寫入文件的字節個數
返回值: 若是出現錯誤,返回-1;若是寫入成功,則返回寫入到文件中的字節個數
7、文件的隨機讀寫
到目前爲止的全部文件訪問都是順序訪問。這是由於全部的讀和寫都從當前文件的偏移位置開始,而後文件偏移值自動地增長到恰好超出讀或寫結束時的位置,使它爲下一次訪問做好準備。
有個文件偏移這樣的機制,在Linux系統中,隨機訪問就變得很簡單,你所需作的只是將當前文件移值改變到有關的位置,它將迫使一次read()或write()發生在這一位置。(除非文件被O_APPEND打開,在這種狀況下,任何write調用仍將發生在文件結束處)
lseek系統調用:
功能說明:經過指定相對於開始位置、當前位置或末尾位置的字節數來重定位,這取決於 lseek() 函數中指定的位置
函數原型:off_t lseek (int fd, off_t offset, int base);
函數參數:
fd:須要設置的文件描述符
offset:偏移量
base:偏移基位置
返回值:返回新的文件偏移值。文件的當前位置是容許爲負數的,因此判斷是否成功,不要測試是否小於0,而要測試她是否等於-1.
base 表示搜索的起始位置,有如下幾個值:(這些值定義在<unistd.h>)
SEEK_SET 從文件開始處計算偏移
SEEK_CUR 從當前文件的偏移值計算偏移
SEEK_END 從文件的結束處計算偏移
注意:若是文件描述符引用的是一個管道、FIFO或網絡套接字,則lseek返回-1,並將errno設置爲ESPIPE。lseek只對常規文件有效。
文件偏移量能夠大於文件的當前長度,在這種狀況下,對該文件的下一次寫將加長該文件,並在文件中構成一個空洞,這一點是容許的。位於文件中但沒有寫過的字節都被讀爲0.文件中的空洞並不要求磁盤上佔用存儲區。
#include "apue.h" #include <fcntl.h> char buf1[] = "abcdefghij"; char buf2[] = "ABCDEFGHIJ"; int main(void) { int fd; if ((fd = creat("file.hole", FILE_MODE)) < 0) err_sys("creat error"); if (write(fd, buf1, 10) != 10) err_sys("buf1 write error"); /* offset now = 10 */ if (lseek(fd, 16384, SEEK_SET) == -1) err_sys("lseek error"); /* offset now = 16384 */ if (write(fd, buf2, 10) != 10) err_sys("buf2 write error"); /* offset now = 16394 */ exit(0); }運行該程序獲得:
huangcheng@ubuntu:~$ ./a.out huangcheng@ubuntu:~$ ll file.hole 檢查其大小 -rwxr-xr-x 1 huangcheng huangcheng 16394 2013-07-04 14:33 file.hole* huangcheng@ubuntu:~$ od -c file.hole 觀察其內容 0000000 a b c d e f g h i j \0 \0 \0 \0 \0 \0 0000020 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 * 0040000 A B C D E F G H I J 0040012
8、sync、fsync和fdatasync函數
傳統的unix實如今內核中設有緩衝區高速緩存或頁面高速緩存,大多數磁盤I/O都經過緩衝進行。當將數據寫入文件時,內核一般先將該數據複製到其中一個緩衝區中,若是該緩衝區還沒有寫滿,則並不將其排入輸出隊列,而是等待其寫滿或者當內核須要重用該緩衝區以便存放其餘磁盤塊數據時,再將該緩衝排入輸出隊列,而後待其到達隊首時,才進行實際的I/O操做。這種輸出方式被稱爲延遲寫。
爲了保證磁盤上實際文件系統與緩衝區高速緩存中內容的一致性,UNIX系統提供了sync、fsync和fdatasync三個函數。
int fsync(int fd); int fdatasync(int fd); void sync(void);
sync函數只是將全部修改過的塊緩衝區排入寫隊列,而後就返回,它並不等於實際寫磁盤操做結束。
fsync函數只對由文件描述符fd指定的單一文件起做用,而且等待寫磁盤操做結束,而後返回。
fdatasync函數相似於fsync,但它隻影響文件的數據部分。而除數據外,fsync還會同步更新文件的屬性。
10、打開文件內核數據結構
一、一個進程打開兩個文件
文件狀態標誌:讀、寫、追加、同步、非阻塞等
二、一個進程兩次打開同一文件
三、兩個進程打開同一文件
示例程序:
#include<sys/types.h> #include<sys/stat.h> #include<unistd.h> #include<fcntl.h> #include<stdio.h> #include<stdlib.h> #include<errno.h> #include<string.h> #define ERR_EXIT(m) \ do { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) int main(int argc, char *argv[]) { int fd1, fd2; char buf1[1024] = {0}; char buf2[1024] = {0}; /* 進程控制塊PCB * struct task { * ... * struct files_struct *files; * } * 同一個進程兩次打開同一個文件,一個進程擁有的一個文件描述符表其中一個fd索引對應的指針指向一個 * 文件表(包括文件狀態(讀寫追加同步非阻塞等),當前文件偏移量, * 文件引用次數(當有兩個fd指向同個文件表時引用計數爲2,見dup,也可用於重定向), * 文件操做指針, V節點指針等)不共享, * V節點表(包括V節點信息(struct stat), i節點信息等)共享 */ /* 兩個進程打開同一個文件的狀況與上類同*/ fd1 = open("test.txt", O_RDONLY); if (fd1 == -1) ERR_EXIT("open error"); read(fd1, buf1, 5); printf("buf1=%s\n", buf1); fd2 = open("test.txt", O_RDWR); if (fd2 == -1) ERR_EXIT("open error"); read(fd2, buf2, 5); printf("buf2=%s\n", buf2); write(fd2, "AAAAA", 5); memset(buf1, 0, sizeof(buf1)); read(fd1, buf1, 5); printf("buf1=%s\n", buf1); close(fd1); close(fd2); return 0; }假設test.txt文件的內容是 ABCDEhello
測試以下:
huangcheng@ubuntu:~$ ./a.out buf1=ABCDE buf2=ABCDE buf1=AAAAA huangcheng@ubuntu:~$ cat test.txt ABCDEAAAAA分析:由上圖分析可知,一個進程兩次打開同一文件,文件表是不共享的,即各有本身的文件偏移量和打開文件標誌,因此兩次read不一樣的fd都是從頭開始讀取,但V節點表是共享的,在fd2寫入(同個文件表的read和write是共享偏移的)更改了inode指向的硬盤數據塊,再次read fd1獲得的也是更改後的值。
11、I/O重定向
當咱們執行了dup(3)以後,系統選擇一個空閒的文件描述符即4,這樣就有兩個文件描述符指向同個文件表,因此引用計數爲2。利用dup等函數能夠進行重定向的步驟是先close輸入輸出文件描述符,而後執行dup(fd), 這樣輸入輸出文件描述符也指向fd指向的文件,這樣就實現了重定向。此外dup2, fcntl 函數也能夠實現,其實不使用這些函數,而直接close(0/1/2)完再open也能夠實現。以下使用cat命令實現複製文件的功能:
#include<sys/types.h> #include<sys/stat.h> #include<unistd.h> #include<fcntl.h> #include<stdio.h> #include<stdlib.h> #include<errno.h> #include<string.h> #include<signal.h> #define ERR_EXIT(m) \ do { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) int main(int argc, char *argv[]) { close(0); open("Makefile", O_RDONLY); close(1); open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664); execlp("cat", "cat", NULL); return 0; }
dup/fcntl 函數示例程序以下:
#include<sys/types.h> #include<sys/stat.h> #include<unistd.h> #include<fcntl.h> #include<stdio.h> #include<stdlib.h> #include<errno.h> #include<string.h> #define ERR_EXIT(m) \ do { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) /* dup dup2 fcntl */ int main(int argc, char *argv[]) { int fd; fd = open("test2.txt", O_WRONLY); if (fd == -1) ERR_EXIT("open error"); /* close(1); dup(fd); */ // dup2(fd, 1); close(1); if (fcntl(fd, F_DUPFD, 0) < 0) //從0開始搜索可用的fd ERR_EXIT("fcntl error"); printf("hello\n"); // 輸出重定向到test2.txt return 0; }