文章簡介講解一下關於linux檔案I/O的用法與注意事項,有需要的朋友參考。
Linux系統中的大多數檔案I/O只用到5個函數:open,read,write,lseek及close。本專題所涉及的函數都被稱為不帶緩衝的I/O,不帶緩衝指的是read或read都是通過核心的一個系統調用實現的,它們是POSIX.1和Single UNIX Specification的組成部分。我們將進一步討論多個進程間的檔案分享權限設定及所涉及的核心資料結構。
檔案描述符
檔案描述符是一個非負整數,當使用open或create時,會返回一個檔案描述符來標識該檔案,可將其作為參數傳遞給read或write使用。檔案描述符0(符號常量:STDIN_FILENO)與進程的標準輸入相關聯,檔案描述符1(符號常量:STDOUT_FILENO)與進程的標準輸出相關聯,檔案描述符2(符號常量:STDERR_FILENO)與進程的標準輸出相關聯,這些常量都定義在<unistd.h>中。檔案描述符的範圍是0~OPEN_MAX,OPEN_MAX的值可以通過函數調用sysconf( _SC_OPEN_MAX );取得,下面是一個樣本:
#include<stdio.h>
#include<unistd.h>
int
main( void )
{
long open_max = sysconf( _SC_OPEN_MAX );
printf( "%dn", open_max );
return 0;
}
輸出:1024
open函數
#include<fcntl.h>
int open( const char* pathname, int oflag, .../* mode_t mode */ );
用以下一個或多個常量進行“或”運算構成oflag參數(這些常量在<fcntl.h>中定義):
O_RDONLY,O_WRONLY,O_RDWR 這三個常量必須且只能指定一個。
以下常量可選擇一個或多個:
O_APPEND 在檔案尾端追加
O_CREAT 若此檔案不存在,則建立。使用此選項時,要用到mode參數以設定新檔案的存取權限。
O_EXCL 測試一個檔案是否存在。
O_TRUNC 如果檔案成功開啟,則將其長度截短為0。
O_NOCTTY 如果pathname參數指的是終端裝置,則不將該裝置作為此進程的控制終端。
O_NONBLOCK 如果pathname指的是一個FIFO,塊特殊檔案或字元特殊檔案,則該選項為檔案的本次開啟操作和後續的I/O操作設定非阻塞模式。
註:*非阻塞模式:在I/O操作不能完成時,調用立即出錯並返回,而不是永遠等待。
O_DSYNC 使該檔案所有的write操作等待,直到對該檔案所有的I/O操作都完成之後,但是如果write操作不影響讀取剛寫入的資料時,則不用等待檔案屬性被更新。
O_RSYNC 使該檔案所有的read操作等待,直到對該檔案所有的write操作都完成之後。
O_SYNC 使該檔案所有的write操作等待,直到對該檔案所有的I/O操作都完成之後。
註:*Linux2.4.22將O_DSYNC和O_RSYNC處理成與O_SYNC相同,即檔案的資料和屬性總是同步更新。
close函數
#include<unistd.h>
int close( int file_des );
關閉一個檔案時,會釋放該檔案上的所有記錄鎖。進程終止時,核心會自動關閉它所開啟的所有檔案,但是顯式調用close函數更安全。
lseek函數
#include<unistd.h>
off_t lseek( int file_des, off_t offset, int whence );
whence取值:
SEEK_SET 將檔案的讀寫位移量設定為距離檔案首段offset個位元組處。
SEEK_CUR 將檔案的讀寫位移量設定為距離當前值加offset個位元組處,offset可為正負。
SEEK_END 將檔案的讀寫位移量設定為距離檔案尾端offset個位元組處,offset可為正負。
成功時返回新的檔案位移量,失敗時返回-1。利用傳回值可以測試檔案描述符是否能夠設定讀寫位移量(管道,FIFO和網路通訊端不能設定位移量)。當讀寫位移量大於檔案長度時,寫操作將會在檔案中構成一個空洞,空洞被讀為0,但是空洞並不佔用磁碟空間,其處理方式與檔案系統的實現有關。下面的程式將建立一個具有空洞的檔案:
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<fcntl.h>
#include<string.h>
char buf[] = "1234567890";
char buf2[] = "abcdefghij";
int
main( void )
{
remove( "file.hole" );
int file_des = open( "file.hole", O_RDWR|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR|S_IXUSR );
if ( file_des == -1 )
{
write( STDOUT_FILENO, "OPEN FILE ERROR!n", 20 );
exit( 0 );
}
if ( lseek( file_des, 5, SEEK_SET ) == -1 )
{
write( STDOUT_FILENO, "FIRST SEEK ERROR!n", 20 );
exit( 0 );
}
size_t write_bytes = write( file_des, buf, strlen( buf ) );
if ( write_bytes < strlen( buf ) )
{
write( STDOUT_FILENO, "FIRST WRITE ERROR!n", 20 );
exit( 0 );
}
if ( lseek( file_des, 5, SEEK_END ) == -1 )
{
write( STDOUT_FILENO, "SECOND SEEK ERROR!n", 20 );
exit( 0 );
}
write_bytes = write( file_des, buf2, strlen( buf ) );
if ( write_bytes < strlen( buf2 ) )
{
write( STDOUT_FILENO, "SECOND WRITE ERROR!", 20 );
exit( 0 );
}
close( file_des );
return 0;
}
使用命令od查看檔案file.hole的內容:
[root@localhost CC++]# od -c file.hole
0000000 1 2 3 4 5 6 7 8 9 0
0000020 a b c d e f g h i j
可以看到,檔案的開始處有一個空洞,中間有一個空洞,都被讀為0。
read和write函數:
#include<unistd.h>
ssize_t read( int file_des, void *buf, size_t nbytes );
傳回值:成功返回讀到的位元組數,若已到檔案結尾返回0,出錯返回-1。
ssize_t write( int file_des, const void *size_t, size_t nbytes );
傳回值:成功返回已寫的位元組數,出錯返回-1。
pread和pwrite函數:
#include<unistd.h>
ssize_t pread(int file_des, void *buf, size_t nbytes, off_t off_set);
ssize_t pwrite( int file_des, const void *buf, size_t nbytes, off_t off_set );
註:*pread和pwrite函數使lseek和read/write操作成為一個原子操作,pread/pwrite和lseek/read/write的重要區別在於:
調用pread/pwrite時,無法中斷定位和讀(寫)操作;
pread/pwrite不能更新檔案指標,可以使用ftell或fgetpos函數擷取當前讀寫位置。
dup和dup2函數:
#include<unistd.h>
int dup( int file_des );
int dup2( int file_des, int file_des2 );
dup和dup2都用於複製一個現有的檔案描述符,成功返回新的檔案描述符,出錯返回-1,區別是:
dup一定返回當前可用的最小檔案描述符;
dup2指定參數file_des2為新的檔案描述符,當file_des2開啟時,先將其關閉,若file_des和file_des2相等,返回file_des2而不關閉它。
註:*fcntl函數也可以複製檔案描述符,稍後將會介紹。
sync,fsync和fdatasync函數:
#include<unistd.h>
int sync( void );
int fsync( int file_des );
int fdatasync( int file_des );
以上函數都用於保證物理檔案和快取資料的一致性,區別是:sync只是將所有修改過的塊緩衝區排入寫隊列,並不等待寫磁碟操作的完成就返回;fsync只是針對由檔案描述符指向的單一檔案,並且等待寫磁碟操作完成再返回,但它隻影響檔案的資料部分;fdatasync和fsync類似,但除了資料之外,fdatasync還會同步更新檔案的屬性。
fcntl函數:
#include<fcntl.h>
int fcntl( int files_des, int cmd, .../* int arg */ );
fcntl函數可以改變已經開啟的檔案的性質。
cmd參數取值(一共有10中,這裡先介紹前7種):
F_DUPFD 複製檔案描述符files_des,新檔案描述符作為函數傳回值返回。新描述符有自己的檔案描述符標誌,其FD_CLOEXEC檔案描述符標誌被清除。
F_GETFD 對應於files_des的檔案描述符標誌作為函數值返回,當前只定義了一個檔案描述符標誌FD_CLOEXEC。
F_SETFD 對於files_des設定檔案描述符標誌,新標誌值按第三個參數設定。
F_GETFL 對應於files_des的檔案狀態標誌作為函數值返回。檔案狀態標誌在open函數已經說明。
F_SETFL 將檔案狀態標誌設定為第三個參數的值。可以更改的標誌是:O_APPEND,O_NONBLOCK,O_SYNC,O_DSYNC,O_RSYNC,O_FSYNC,0_ASYNC。
F_GETOWN 取當前接收SIGIO和SIGURG訊號的進程ID或進程租ID。
F_SETOWN 設定接收SIGIO和SIGURG訊號的進程ID或進程租ID。正的arg參數表示一個進程ID,負的arg參數表示等於arg的絕對值的進程租ID。