linux 檔案I/O教程(1)
一,檔案描述符
對核心而言,所以開啟的檔案都通過檔案描述符引用。每個程序都有一些與之關聯的檔案描述符。檔案描述符是一個非負整數。當開啟一個現有檔案或建立一個新檔案時,核心向程序返回一個檔案描述符。當讀或寫一個檔案時,使用open或creat返回的檔案描述符標識該檔案,將其作為引數傳送給read和write。
一般有三個以及開啟的檔案描述符,他們是:
0:標準輸入 STDIN_FILENO
1:標準輸出 STDOUT_FILENO
2標準錯誤輸出 STDERR_FILENO
每行後面的符號常量是依從POSIX而定的。
open函式
#include
#include
#include
int open(const char *pathname, int flags);
int open(const char *pathname, int flags,mode_t mode);
pathname是要開啟或建立檔案的名字。
flag用來定義開啟檔案所採取的的動作,必須呼叫以下模式之一
O_RDONLY, O_WRONLY, O_RDWR分別代表只讀,只寫,讀寫方式開啟。
open還可以包括以下可選模式的組合
O_APPEND:把寫入資料追加到檔案的尾端
O_CREAT:若檔案不存在,則建立它。使用此選項時,需要第三個引數mode,用其指定該新檔案的訪問許可權。
O_EXCL:如果同時指定了O_CREAT,而檔案存在,則會出錯。用此可以測試一個檔案是否存在,如果存在,則建立檔案,這使測試和建立兩者成為一個原子操作。
O_TRUNC: 如果此檔案存在,而且為只寫或讀寫成功開啟,則將其長度截為0。
open返回的檔案描述符一定是最小的未用描述符數值。這一點被某些應用程式用在標準輸入,標準輸出或標準錯誤輸出上。如,一個程式關閉了自己的標準輸出,然後再次呼叫open,檔案描述符1就會被呼叫,並且標準輸出將被有效的重定向到另一個檔案或裝置。
POSIX規範還標準化了一個creat呼叫,此函式等效於
open(pathname,O_WONLY |O_CREAT | O_TRUNC, mode);
close函式
#include
int close(int fd);
close呼叫終止一個檔案描述符fd與對應檔案之間的關聯。檔案描述符被釋放後並能重新使用。close呼叫成功返回0,出錯返回-1.
關閉一個檔案時會釋放該程序加在檔案上的所有記錄鎖。當一個程序終止時,核心自動關閉它所有開啟的檔案。
lseek函式
每個開啟的檔案都有一個與其相關聯的”當前檔案偏移量”。按系統預設情況,當開啟一個檔案時,除非指定O_APPEND選項,否則該偏移量被設定為0。lseek可以為一個開啟的檔案設定偏移量。
#include
#include
off_t lseek(int fd, off_t offset, intwhence);
offset用來指定位置,whence引數定義該偏移值的用法。whence可取以下值:
SEEK_SET: The offset is set to offset bytes.
SEEK_CUR: The offset is set to its current locationplus offset bytes.
SEEK_END: The offset is set to the size of the fileplus offset bytes.
成功呼叫返回從檔案頭到檔案指標被設定處的位元組偏移值,失敗返回-1。引數offset定義在中。
當偏移量大於檔案長度時,出現空洞,空洞不佔用儲存區。
read函式
#include
ssize_t read(int fd, void *buf, size_tcount);
將與檔案描述符fd關聯的檔案中讀入count個字元放到buf中。返回讀入的位元組數,它可能小於請求的位元組數。如果read呼叫返回0,就表示未讀入任何資料,已到達了檔案尾。返回-1,就表示出錯。
write函式
#include
ssize_t write(int fd, const void *buf,size_t count);
把緩衝區buf的前count個位元組寫入與檔案描述符fd相關聯的檔案中。返回實際寫入的位元組數,通常與count值相同;否則表示出錯。出錯的一個常見原因是:磁碟已寫滿,或者超出了一個給定程序的檔案長度限制。
例項:建立一個檔案,寫入資料,移動當前偏移量,在讀資料。
#include//必須最早出現,因為它可能會影響到其他標頭檔案。#include
#include
#include
#include
int main()
char* filename = ".//file";
char buf[100];
char buf1[5];
int fd;
printf("open a file to writen");
if((fd = open(filename,O_RDWR|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH ))==-1)
perror("cannot open filen");
return 1;
printf("open file successfully!n");
printf("input a string:");
gets(buf);
//write intofile
if(write(fd,buf,strlen(buf)) !=strlen(buf))
perror("cannot write intofilen");
return 1;
close(fd);
printf("open file to read.n");
if((fd=open(filename,O_RDONLY)) == -1)
perror("cannot open thefile.n");
return 1;
if(lseek(fd,3,SEEK_SET) == -1)
perror("lseek erroen");
return 1;
//read from the file
if(read(fd,buf1,4)==-1)
perror("read error.n");
return 1;
printf("read from file is%sn",buf1);
close(fd);
return 0;
執行與輸出結果:
root@:~$gcc -o io io.c
root@:~$./io
open a file towrite
open filesuccessfully!
input a string:akxivbaslzkncxcasbxbwwvaidxbd
open file toread.
read from fileis ivba
linux 檔案I/O教程(2)下面介紹了linux中有關檔案I/O的相關內容,核心使用三種資料結構表示開啟的檔案,他們之間的關係決定了在檔案共享方面一個程序對另一個程序可能產生的影響。
一,檔案共享
核心使用三種資料結構表示開啟的檔案,他們之間的關係決定了在檔案共享方面一個程序對另一個程序可能產生的影響。
1) 每個程序在程序表中都有一個記錄項,記錄項中包含一張開啟檔案描述表,可將其視為一個向量,每個描述符佔用一項。與每個檔案描述符相關聯的是:
a) 檔案描述符標誌
b) 指向一個檔案表項的指標
2) 核心為所有開啟檔案維持一張檔案表。每個檔案表項包含:
a) 檔案狀態標誌(讀、寫、讀寫、添些、同步和阻塞等)
b) 當前檔案偏移量
c) 指向檔案v節點表項的指標
3) 每個開啟檔案(或裝置)都有一個v節點(v-node)結構。v節點包含了檔案型別和對比檔案進行各種操作的函式的指標。對於大多數檔案,v節點還包含了該檔案的i節點。i節點包含檔案所有者、檔案長度、檔案所在的裝置、指向檔案實際資料塊在磁碟上所在位置的指標等。
開啟檔案的`核心資料結構
如果兩個程序各自打開了同一個檔案,則如圖2所示。假定第一個程序在檔案描述符3開啟上該檔案,而另一個程序在檔案描述符4上開啟該檔案。每個程序都得得到一個檔案表項,但對一個給定的檔案只有一個v節點表項。每個程序都有自己的檔案表項的一個理由是:使每個程序都有自己對該問價的當前偏移量。
現在對前一節檔案I/O(1)的幾個操作進一步說明:
1. 完成write之後,檔案中當前偏移量即所增加的位元組數。如果當前偏移量大於檔案長度,則將i節點中當前檔案長度設為當前檔案偏移量。
2. 用O_APPEND開啟一個檔案,相應標誌會被設定到檔案狀態標識中。每次寫時,當前偏移量會被設定為i節點中的檔案長度
3. lseek定位到檔案尾端時,則檔案當前偏移量會被設定為當前檔案長度。
可能有多個檔案描述符指向同一檔案表項。呼叫dup和fork時都能看到這一點。
多個程序讀同一檔案能正確工作。但多個程序寫同一檔案時,可能產生預期不到的後果。可以利用原子操縱避免這種情況。
原子操作
一般而言,原子操作指的是由多部組成的操作。如果該院自地執行,要麼執行完所以步驟,要麼一步也不執行。
1. 新增至一個檔案
考慮一個程序,它要講資料新增到一個檔案尾端。早期UNIX不支援open,所以可以如下實現:
if(lseek(fd, 0L, 2)<0)
err_sys(“lseekerror”);
if(write(fd, buf, 100) != 100)
err_sys(“writeerror”);
對於單個程序,這段程式能正常工作。但多個程序就不一定。結社程序A和B都對同一檔案進行新增操作。每個程序都開啟該檔案,此時資料結構之間關係如圖2中所示。假定A呼叫lseek,將A的當前偏移量設定為1500。程序B執行lseek也將其當前偏移量設為1500。然後B呼叫write,將當前偏移量增至1600。然後核心又進行程序切換使程序A恢復執行,當A呼叫write時,從其當前偏移量1500處將資料寫入,將替換B剛寫入到該檔案中的資料。
問題出在邏輯操作“定位到檔案尾端處,然後寫“使用了兩個分開的函式呼叫。解決辦法是使這兩個操作成為一個原子操作。O_APPEND標識,使核心每次對檔案進行寫之前,都將程序當前偏移量設定到該檔案的尾端處。
2.pread和pwrite函式
原子性地定位搜尋和執行I/0。
#include
ssize_t pread(int fd, void *buf, size_tcount, off_t offset);
ssize_t pwrite(int fd, const void *buf,size_t count, off_t offset);
ssize_t pread(int fd, void *buf, size_tcount, off_t offset);
ssize_t pwrite(int fd, const void *buf,size_t count, off_t offset);
dup和dup2函式
#include
int dup(int oldfd);
int dup2(int oldfd, int newfd);
上面兩個函式都可用來複制一個現存的檔案描述符。
由dup返回的新檔案描述符一定是當前可用檔案描述符中的最小數值。用dup2則可以用newfd引數指定新描述符的數值。如果newfd已經開啟,則先將其關閉。如果newfd等於oldfd,則dup2返回newfd而不關閉它。
圖3.3顯示了這種情況。
假定我們的程序執行了:
newfd = dup(1);
當此函式執行時,假設下一個可用的描述符是3。因為這兩個描述符指向同一個檔案表項,所以他們共享檔案標誌以及同一檔案偏移量。
sync、fsync和fdatasync
#include
void sync(void);
int fsync(int fd);
int fdatasync(int fd);
當將資料寫入檔案時,核心通常將資料複製到一個緩衝區,直到緩衝區寫滿,再將緩衝區排路輸出佇列,然後等待其到達隊首,才進行實際的I/O操作。這種輸出防暑被稱為延遲寫。延遲寫減少了磁碟的讀寫次數,但卻降低了檔案內容的跟新速度。當系統發生故障時,延遲寫可能造成檔案跟新內容的丟失。為了保證磁碟上實際檔案系統與緩衝區快取記憶體中內容一致性,UNIX系統提供了sync、fsync和fdatasync 三個函式。
fcntl函式
#include
#include
int fcntl(int fd, int cmd, ... /* arg */ );
可以改變已經開啟檔案的性質。
複製一個現有的描述符(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)
可以用fcntl函式設定檔案狀態,常用設定套接字描述符為非阻塞O_NONBLOCK
ioctl函式
#include
int ioctl(int d, int request, ...);
提供了一個用於控制裝置及其描述符行為和配置底層服務的介面。
/dev/fd
開啟檔案/dev/fd/n等效於複製描述符n。