- Linux遵循一切皆文件的理念担猛,任何你能讀寫的東西都可以用文件描述符來訪問松蒜。
內(nèi)核為每個進程維護一個打開文件的列表,該表被稱為文件表(file table)读串。該表由一些叫做文件描述符(file descriptors)(沉募牵縮寫為fds)的非負整數(shù)進行索引撒妈。
用戶空間和內(nèi)核空間都把文件描述符作為每個進程的唯一cookies。
- 每個進程按照慣例會至少有三個打開的文件描述符:0:標準輸入(stdin)排监,1:標準輸出(stdout)狰右,2:標準錯誤(stderr)。C標準庫提供了預(yù)處理器宏:STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO宏舆床,以取代對以上整數(shù)的直接引用棋蚌。
- 文件描述符不僅僅用于普通文件的訪問,也用于訪問設(shè)備文件挨队、管道谷暮、目錄以及快速用戶空間鎖、FIFOs和套接字瞒瘸。
打開文件
最基本的訪問文件的方法是read( )和write( )系統(tǒng)調(diào)用坷备,在一個文件能被訪問之前,必須通過open( )或者creat( )系統(tǒng)調(diào)用打開它情臭,一旦使用完畢,就應(yīng)該用close( )系統(tǒng)調(diào)用來關(guān)閉文件赌蔑。
- open( )系統(tǒng)調(diào)用
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *name, int flags);
int open(const char *name, int flags, mode_t mode);
open( )系統(tǒng)調(diào)用將路徑名name給出的文件與一個成功返回的文件描述符想關(guān)聯(lián)俯在,文件位置指針被設(shè)定為零,而文件則根據(jù)flags給出的標志位打開娃惯。
- flags參數(shù)必須是以下之一:O_RDONLY(只讀), O_WRONLY(只寫), O_RDWR(可讀可寫)
- flags參數(shù)可以和以下一個或多個值進行按位或運算跷乐, 用以修改打開文件請求的行為:
- O_APPEND: 文件以追加模式打開。在每次寫操作之前趾浅,文件位置指針將被置于文件末尾愕提。
- O_ASYNC: 當(dāng)指定文件可寫或者可讀時產(chǎn)生一個信號(默認是SIGIO),這個標志僅用于終端和套接字皿哨,不能用于普通文件浅侨。
- O_CREAT: 當(dāng)name指定的文件不存在時,將由內(nèi)核來創(chuàng)建证膨。
- O_DIRECT: 用于直接IO如输。
- O_DIRECTORY: 如果name不是一個目錄,open( )調(diào)用將會失敗央勒。這個標志用于在opendir( )內(nèi)部使用不见。
- O_EXCL: 和O_CREAT一起給出的時候,如果由name給定的文件已經(jīng)存在崔步,則open( )調(diào)用失敗稳吮。用來防止文件創(chuàng)建時出現(xiàn)競爭。
- O_LARGEFILE: 給定文件打開時將使用64位偏移量井濒,這樣大于2G的文件也能被打開灶似。
- O_NOCTTY: 如果name指向一個終端設(shè)備(/dev/tty)列林,它將不會成為這個進程的控制終端,即時該進程目前沒有控制終端喻奥。
- O_NOFOLLOW: 如果name是一個符號鏈接席纽,open( )調(diào)用會失敗。
- O_NONBLOCK: 如果可以撞蚕,文件將在非阻塞模式下打開润梯。open( )調(diào)用不會,任何其它操作都不會使該進程
- O_SYNC: 打開文件用于同步IO甥厦。
- O_TRUNC: 如果文件存在纺铭,且為普通文件,并允許寫刀疙,將文件的長度截斷為0舶赔。
新文件所有者
文件所有者的用戶ID就是創(chuàng)建該文件的進程的有效用戶id。
創(chuàng)建文件的進程的組id賦予該文件的用戶組谦秧。新文件權(quán)限
除非創(chuàng)建了新文件竟纳,否則mode參數(shù)會被忽略。而當(dāng)O_CREAT給出時則必須提供mode參數(shù)疚鲤。如果在使用O_CREAT時忘記了提供mode參數(shù)锥累,結(jié)果是未定義的,會很糟糕!
當(dāng)文件創(chuàng)建時,mode參數(shù)提供新建文件的權(quán)限客们。mode參數(shù)是常見的Unix權(quán)限位集合居灯,像八進制數(shù)0644(所有者可以讀寫,其他人只能讀)。例如:
int fd;
fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP | S_IROTH);
if (fd == -1)
/* error */
-
creat( ) 函數(shù):
O_WRONLY | O_CREAT | O_TRUNC組合經(jīng)常被使用,以至于專門有個系統(tǒng)調(diào)用來實現(xiàn):
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int creat (const char *name, mode_t mode);
對creat的調(diào)用:
int fd;
fd = creat(file, 0644);
if (fd == -1)
/* error */
等價于:
int fd;
fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1)
/* error */
-
返回值和錯誤碼
open( )和creat( )系統(tǒng)調(diào)用成功時都返回一個fd,錯誤時都返回-1鹅心,并且將errno設(shè)置為一個合適的錯誤值。
用read( )讀取文件
最基本它掂、最常見的讀取文件的機制是使用read( )系統(tǒng)調(diào)用:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t len);
該系統(tǒng)調(diào)用從fd指向的文件的當(dāng)前偏移量至多讀len個字節(jié)到buf中巴帮。成功時,將返回寫入buf中的字節(jié)數(shù)虐秋。出錯時榕茧,返回-1,并設(shè)置errno。
一次讀后客给,fd所指文件位置指針將會向前移動用押,移動的長度由之前讀取的字節(jié)數(shù)決定。(如果無法在該文件(比如一個字符設(shè)備文件)中確定文件位置靶剑,讀操作總是從“當(dāng)前”位置開始)
返回值
返回值可能有以下結(jié)果:
- 等于len蜻拨。則與預(yù)期一致
- 大于0但是小于len池充。合法,讀取的字節(jié)存入buf中缎讼。
- 返回0收夸。標志著EOF,沒有可以讀入的數(shù)據(jù)
- 返回-1血崭,errno被設(shè)置為EINTR卧惜。表示在讀入字節(jié)之前收到了一個信號,可以重新進行調(diào)用夹纫。
- 返回-1咽瓷, errno被設(shè)置為EAGAIN。這表示讀取會因為沒有可用的數(shù)據(jù)而阻礙舰讹,而讀請求應(yīng)該在之后重開茅姜。這只在非阻塞模式下發(fā)生。
- 返回-1月匣, errno被設(shè)置不同于EINTR或EAGAIN的值钻洒。這表示某種更嚴重的錯誤。
-
讀入所有的字節(jié)
讀入所有l(wèi)en個字節(jié)(至少讀到EOF)的一個例子:
ssize_t ret;
while (len != 0 && (ret = read(fd, buf, len) != 0)) {
if (ret == -1) {
if (errno == EINTR)
continue;
perror("read");
break;
}
len -= ret;
buf += ret;
}
-
非阻塞讀
有時候锄开,程序員不希望當(dāng)沒有可讀數(shù)據(jù)時讓read( )調(diào)用阻塞航唆。相反,他們傾向于在沒有可讀數(shù)據(jù)時院刁,讓調(diào)用立即返回。這種情況被稱為非阻塞I/O粪狼。 - 如果給定的fd在非阻塞模式下打開(open( )中給定O_NONBLOCK)并且沒有可讀數(shù)據(jù)退腥,
read( )調(diào)用會返回-1,且設(shè)置errno為EAGAIN而不是阻塞掉。 - 在進行非阻塞I/O時再榄,必須檢查EAGAIN狡刘,否則將可能因數(shù)據(jù)缺失而導(dǎo)致嚴重的錯誤。
char buf[BUFSIZE];
ssize_t nr;
start:
nr = read(fd, buf, BUFSIZE);
if (nr == -1) {
if (errno == EINTR)
goto start;
if (errno == EAGAIN)
/* resubmit later */
else
/* error */
}
-
read( )大小限制
在32系統(tǒng)上困鸥,size_t是unsigned int類型嗅蔬,ssize_t是有符號的size_t類型。
size_t的最大值為SIZE_MAX;ssize_t的最大值為SSIZE_MAX疾就。如果len比SSIZE_MAX大澜术,read( )調(diào)用的結(jié)果是未定義的。 - 在大部分linux系統(tǒng)上猬腰,SSIZE_MAX是LONG_MAX,在32位系統(tǒng)上即0x7fffffff鸟废。
- 可增加如下代碼:
if (len > SSIZE_MAX)
len = SSIZE_MAX;
- 一個len為0的read( )調(diào)用的結(jié)果是立即返回且返回值為0.
用write( )來寫
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
一個write( )調(diào)用從由文件描述符fd引用文件的當(dāng)前位置開始,將buf中至多count個字節(jié)寫入文件姑荷。
- 對于普通文件來說盒延,除非發(fā)生一個錯誤缩擂,否則write( )將保證寫入所有的請求。
- 對于其他類型的文件--例如套接字--得有個循環(huán)來保證你真的寫入了所有請求的字節(jié)添寺。使用循環(huán)的另一個好處是胯盯,第二個write( )調(diào)用可能會返回一個錯誤值用來說明第一次調(diào)用為什么進行了一次部分寫(盡管這種情況并不常見)。以下是一個實例代碼:
ssize_t ret, nr;
while (len != 0 && (ret = write(fd, buf, len)) != 0) {
if (ret == -1) {
if (errno == EINTR)
continue;
perror("write");
break;
}
len -= ret;
buf += ret;
}
追加模式
打開文件時计露,open( )通過指定O_APPEND參數(shù)博脑,則在追加模式下打開。寫操作就總是從當(dāng)前文件末尾開始薄坏。追加寫模式可使多個進程訪問同個文件不出錯趋厉。
** 非阻塞寫**
當(dāng)fd在非阻塞模式下打開時(通過設(shè)置O_NONBLOCK參數(shù)),并且發(fā)起的寫操作會正常阻塞時胶坠,write( )系統(tǒng)調(diào)用返回-1君账,并設(shè)置errno值為EAGAIN。請求應(yīng)該在稍后重新發(fā)起沈善。通常普通文件不會出現(xiàn)這種情況乡数。write( )大小限制
如果count比SSIZE_MAX還大,write( )調(diào)用的結(jié)果是未定義的闻牡。count值為0的write( )調(diào)用將會立即返回且返回值為0净赴。write( )的行為
write是先寫入page cache后就立刻返回了,然后由內(nèi)核的后臺線程來writeback所有的"臟"緩沖區(qū)罩润,將它們排好序玖翅,并寫入到磁盤上。
同步IO
由于一般都使用page cache & writeback的機制割以,然而又有應(yīng)用想要控制數(shù)據(jù)寫入磁盤的時間金度,于是Linux提供了同步機制,用性能來換取同步操作严沥。
- fsync( )
#include <unistd.h>
int fsync(fd);
調(diào)用fsync( )可保證fd對應(yīng)文件的臟數(shù)據(jù)回寫到磁盤上猜极。
- fdatasync( ):
#include <unistd.h>
int fdatasync(fd);
和fsync( )的區(qū)別在于,它僅僅寫入數(shù)據(jù)消玄,不保證元數(shù)據(jù)同步到磁盤上跟伏。
- 注:這兩個調(diào)用都不保證任何已經(jīng)更新的包含該文件的目錄項同步到磁盤,如果要保證目錄項也同步翩瓜,必須對目錄本身也調(diào)用fsync( )進行同步受扳。
- sync( ):
#include <unistd.h>
void sync(void);
sync( )用來對磁盤上的所有緩沖區(qū)進行同步!
- sync( )沒有參數(shù)奥溺,也沒有返回值辞色。它總是成功返回,并確保所有的緩沖區(qū)--包括數(shù)據(jù)和元數(shù)據(jù)--都能寫入磁盤。
- sync( )并非一直等待到所有緩沖區(qū)都寫到磁盤才返回相满,只需要調(diào)用它來啟動將整個緩沖區(qū)寫入磁盤的過程即可层亿。
- O_SYNC標志
O_SYNC標志在open( )中使用,使所有在文件上的I/O操作同步立美。例如:
int fd;
fd = open(file, O_WRONLY | O_SYNC);
if (fd == -1){
perror("open");
return -1;
}
- O_SYNC看起來像是在每個write( )操作后都隱式地執(zhí)行fsync( ).
- O_SYNC會使總耗時增加一到兩個數(shù)量級
- 如果一般要確保數(shù)據(jù)寫入磁盤的應(yīng)用可以使用fsync( )或者fdatasync( )匿又。相比O_SYNC來說,開銷更小一點建蹄。
- O_DSYNC和O_RSYNC
- O_DSYNC:只有普通數(shù)據(jù)被同步(與fdatasync( )類似)
- O_RSYNC: 保證讀請求同步碌更。該標志只能和O_SYNC或O_DSYNC一起使用。O_RSYNC保證每次讀操作后洞慎,元數(shù)據(jù)也立刻更新痛单。
直接IO
由于Linux內(nèi)核實現(xiàn)了一個復(fù)雜的緩存、緩沖以及設(shè)備和應(yīng)用之間的I/O管理的層次結(jié)構(gòu)劲腿。通過O_DIRECT標志使內(nèi)核最小化I/O管理的影響旭绒。
- 使用O_DIRECT標志時,I/O操作將忽略page cache機制焦人,直接對用戶空間緩沖區(qū)和設(shè)備進行初始化挥吵,所有的I/O將是同步的,操作在完成前不會返回花椭。
- 當(dāng)使用直接I/O時忽匈,請求長度,緩沖區(qū)對齊矿辽,和文件偏移必須是設(shè)別扇區(qū)大械ぴ省(通常是512字節(jié))的整數(shù)倍。
關(guān)閉文件
程序完成對某個文件的操作后袋倔,可以使用close( )系統(tǒng)調(diào)用將文件描述符和對應(yīng)的文件解除關(guān)聯(lián)嫌松。
#include <unistd.h>
int close(int fd);
close( )調(diào)用解除了已打開的文件描述符的關(guān)聯(lián),并分離進程和文件的關(guān)聯(lián)奕污。
- 關(guān)閉文件和文件被寫入磁盤沒有關(guān)系
- 關(guān)閉文件也有些副作用:fd關(guān)閉后,在內(nèi)核中表示該文件的數(shù)據(jù)結(jié)構(gòu)就被釋放了液走。當(dāng)它釋放時碳默,與文件關(guān)聯(lián)的inode的內(nèi)存拷貝被清除.