【UNIX環境高級編程】文件I/O
大多數文件I/O只須要5個函數:shell
open、read、write、lseek以及close編程
不帶緩衝的I/O:網絡
每一個read和write都調用內核中的一個系統調用數據結構
1.文件描述符
對於內核而言,全部打開的文件都經過文件描述符引用。當打開一個文件時,內核向進程返回一個文件描述符。當讀或寫一個文件時,使用open或create返回的文件描述符標識該文件,將其做爲參數傳給read或write。異步
UNIX系統shell使用文件描述符0與進程的標準輸入相關聯,1與標準輸出相關聯,2與標準出錯輸出相關聯。函數
2.open函數
調用open函數打開或建立一個文件測試
#include<fcntl.h>
int open(const char *pathname,int oflag,.../*mode_t mode*/);//返回值:成功返回文件描述符,出錯返回-1
#include<fcntl.h>
int open(const char *pathname,int oflag,.../*mode_t mode*/);//返回值:成功返回文件描述符,出錯返回-1
pathname是要打開或建立文件的名字。oflag參數,可用下列一個或多個常量進行「或」運算。3d
在這三個常量中必須指定一個且只能指定一個指針
O_RDONLY 只讀打開blog
O_WRONLY 只寫打開
O_RDWR 讀、寫打開
可選常量:
O_APPEND 每次寫時都加到文件的尾端
O_CREAT 若文件不存在,則建立它
O_EXCL 若是同時指定了O_CREAT,而文件已經存在,則會出錯,用此可測試一個文件是否存在,若是不存在,則建立文件,這使測試和建立二者成爲一個原子操做。
O_TRUNC 若是此文件存在,並且爲只寫或讀寫成功打開,則將其長度截短爲0
3.creat函數
調用creat函數建立一個新文件
#include<fcntl.h>
int creat(const char *pathname,mode_t mode);//返回值:成功返回爲只寫打開的文件描述符,出錯返回-1
#include<fcntl.h>
int creat(const char *pathname,mode_t mode);//返回值:成功返回爲只寫打開的文件描述符,出錯返回-1
此函數等效於:open(pathname,O_WRONLY|O_CREAT|O_TRUNC,mode);
4.close函數
調用close函數關閉一個打開的文件
#include<unistd.h>
int close(int file);//成功返回0,出錯-1
int close(int file);//成功返回0,出錯-1
關閉一個文件時還會釋放該進程加在該文件上的全部記錄鎖。
當一個進程終止時,內核自動關閉它全部打開的文件。不少程序都利用這一功能不顯式的關閉打開文件。
5.lseek函數
偏移量
每一個打開的文件都有一個與其相關聯的「當前文件偏移量」。它一般是一個非負整數,用以度量從文件開始處計算的字節數。一般,讀、寫操做都從當前文件偏移量處開始,並使偏移量增長所讀寫的字節數。系統默認下,打開文件,未指定O_APPEND選項時,該偏移量爲0
調用lseek函數能顯式地爲一個打開地文件設置其偏移量
#include<unistd.h>
off_t lseek(int file,off_t offset,int whence);//成功返回新的文件偏移量,出錯-1
off_t lseek(int file,off_t offset,int whence);//成功返回新的文件偏移量,出錯-1
對參數offset地解釋與參數whence有關
若whence是SEEK_SET,則將該文件地偏移量設置爲距文件開始處offset個字節
若whence是SEEK_CUR,則將該文件地偏移量設置爲其當前值加offset,offset可正可負
若whence是SEEK_END,則將該文件地偏移量設置爲文件長度加offset,offset可正可負
例:
off_t currpo;
currpo = lseek(fd,0,SEEK_CUR);//打開文件地當前偏移量
off_t currpo;
currpo = lseek(fd,0,SEEK_CUR);//打開文件地當前偏移量
6.read函數
調用read函數從打開文件中讀數據
#include<unistd.h>
int read(int file,char *buf,unsigned nbytes);//成功返回讀到地字節數,若已到文件結尾返回0,出錯-1
#include<unistd.h>
int read(int file,char *buf,unsigned nbytes);//成功返回讀到地字節數,若已到文件結尾返回0,出錯-1
實際讀到字節數少於要求讀地字節數:
讀文件時,要求字節數大於文件的字節數。如,到達文件尾端以前還有30個字節,而要求讀100字節,則read返回30
當從終端設備讀時,一般一次最多讀一行
當從網絡讀時,網絡中的緩衝機構可能形成返回值小於所要求讀的字節數
當從管道或FIFO讀時,若管道包含的字節少於所需的數量,那麼read將只返回實際可用的字節數
當從某些面向記錄的設備(如磁盤)讀時,一次最多返回一個記錄
當某一信號形成中斷,而已經讀了部分數據量時。
7.write函數
調用write函數向打開的文件寫數據
#include<unistd.h>
ssize_t write(int file,const void *buf,size_t nbytes);//成功返回已寫字節數,出錯返回-1
ssize_t write(int file,const void *buf,size_t nbytes);//成功返回已寫字節數,出錯返回-1
write出錯的常見緣由:磁盤已寫滿,或超過一個給定進程的文件長度限制。
普通文件,寫操做從文件的當前偏移量處開始,若是在打開文件時,指定了O_APPEND選項,則每次寫操做以前,將文件偏移量設置在文件的當前結尾處,成功寫以後,該文件偏移量增長實際寫的字節數。
8.I/O的效率
例程:標準輸入複製到標準輸出
#include"apue.h"
#define BUFFSIZE 4096
int main()
{
int n;
char buf[BUFFSIZE];
while((n=read(STDIN_FILENO,buf,BUFFSIZE))>0)
if(write(STDOUT_FILENO,buf,n)!=n)
err_sys("write error");
if(n<0)
err_sys("read error");
exit(0);
}
#define BUFFSIZE 4096
int main()
{
int n;
char buf[BUFFSIZE];
while((n=read(STDIN_FILENO,buf,BUFFSIZE))>0)
if(write(STDOUT_FILENO,buf,n)!=n)
err_sys("write error");
if(n<0)
err_sys("read error");
exit(0);
}
注意點:
它從標準輸入讀,寫至標準輸出,這就假定在執行本程序前,這些標準輸入,輸出已由shell安排好。使得程序沒必要自行打開輸入和輸出文件。
不少應用程序假定標準輸入是文件描述符0,標準輸出是文件描述符1。本例程中使用<unsitd.h>中定義的倆個名字:STDIN_FILENO,STDOUT_FILENO
進程終止時,UNIX系統內核會關閉該進程的全部打開的文件描述符,故程序中未關閉輸入和輸出文件
對UNIX系統內核而言,文本文件和二進制代碼文件無區別
9.文件共享
UNIX系統支持在不一樣進程間共享打開的文件。
內核使用三種數據結構表示打開的文件,他們以前的關係決定了在文件共享方面一個進程對另外一個進程可能產生的影響。
(1)每一個進程在進程表中都有一個記錄項,記錄項中包含有一張打開文件描述符表,可將其視爲一個矢量,每一個描述符佔用一項,與每一個文件描述符相關聯的是:
文件描述符標誌
指向一個文件表項的指針
(2)內核爲全部打開文件維持一張文件表,每一個文件表項包含:
文件狀態標誌(讀、寫、添加。。)
當前文件偏移量
指向該文件v節點表項的指針
(3)每一個打開文件(或設備)都有一個v節點結構,v節點包含了文件類型和對比文件進行各類操做的函數的指針。
兩個獨立進程各自打開同一文件
如圖一進程用文件描述符3打開該文件,二進程用文件描述符4打開該文件,每一個進程都獲得一個文件表項,但一個文件只有一個v節點表項,兩個進程的v節點指針均指向v節點表項
進一步說明:
在完成每一個write後,在文件表項中的當前文件偏移量即增長所寫的字節數。
若是用O_APPEND標誌打開一個文件,則相應標誌也被設置到文件表項的文件狀態標誌中。
10.原子操做
多步組成的操做,若是該操做原子地執行,則要麼執行完全部步驟,要麼一步也不執行,不可能只執行全部步驟地一個子集。
10.1添加至一個文件
兩個獨立進程A和B都對同一文件進行添加操做,UNIX系統提供一種方法使這種操做成爲原子操做,該方法是在打開文件時設置O_APPEND標誌。這使內核每次對這種文件進行寫以前,都將進程的當前偏移量設置到該文件的尾端處,因而每次寫以前就再也不調用lseek。
10.2 pread和pwrite函數
pread和pwrite是XSI擴展,該擴展容許原子性地定位搜索和執行I/O
#include<unistd.h>
ssize_t praed(int file,void *buf,size_t nbytes,off_t offset);//返回值:讀到的字節數,若已到文件結尾則返回0,出錯-1
ssize_t pwrite(int file,const void *buf,size_t nbytes,off_t offset;//返回值:成功返回已寫的字節數,出錯-1
#include<unistd.h>
ssize_t praed(int file,void *buf,size_t nbytes,off_t offset);//返回值:讀到的字節數,若已到文件結尾則返回0,出錯-1
ssize_t pwrite(int file,const void *buf,size_t nbytes,off_t offset;//返回值:成功返回已寫的字節數,出錯-1
調用pread至關於順序調用lseek和read,但也有區別:
調用pread,沒法中斷其定位和讀操做
不更新文件指針
調用pwrite至關於順序調用lseek和write,不過也有相同的區別
10.3 建立一個文件
同時選擇open函數地O_CREAT和O_EXCL選項,該文件已經存在則open失敗。檢查該文件是否存在以及建立該文件這兩個操做做爲一個一個原子操做執行。
11.dup和dup2函數
這兩個函數用於複製一個現存地文件描述符
#include<unistd.h>
int dup(int file);
int dup2(int file,int file2);//兩個函數返回值:成功返回新的文件描述符,出錯-1
int dup(int file);
int dup2(int file,int file2);//兩個函數返回值:成功返回新的文件描述符,出錯-1
dup返回的新文件描述符是當前可用文件描述符中的最小數值。dup2可用file2參數指定新的文件描述符數值。若果file2已經打開,則先將其關閉,若file等於file2,則dup2返回file2,而不關閉它。
這些函數返回的新文件描述符於參數file共享同一個文件表項。
假定進程執行:
newfd=dup(1);
當此函數開始執行,新的文件描述符頗有可能爲3(0,1,2已經被shell打開),兩個文件描述符指向同一文件表項,共享同一文件狀態標誌(讀、寫等)和同一當前文件偏移量
複製一個描述符的另外一種方法是使用ftcntl函數
調用dup(file)等效於fcntl(file,F_DUPFD,0)
調用dup2(file,file2)等效於close(file2);fcntl(file,F_DUPFD,file2);
12.fcntl函數
fcntl函數能夠改變已打開的文件的性質
#include<fcntl.h>
int fcntl(int file,int cmd,.../*int arg*/);//返回值:成功則依賴cmd,出錯-1
#include<fcntl.h>
int fcntl(int file,int cmd,.../*int arg*/);//返回值:成功則依賴cmd,出錯-1
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)