1.文件描述符
所有執(zhí)行I/O操作的系統(tǒng)調(diào)用都以文件描述符(一個非負(fù)整數(shù))來指代打開的文件恨樟。文件描述符用以表示所有類型的已打開文件褪那,包括管道幽纷、FIFO、socket博敬、終端友浸、設(shè)備、普通文件偏窝。它是一個索引值收恢,指向內(nèi)核為每一個進(jìn)程所維護(hù)的該進(jìn)程打開文件的記錄表。當(dāng)程序打開一個現(xiàn)有文件或者創(chuàng)建一個新文件時祭往,內(nèi)核向進(jìn)程返回一個文件描述符派诬。每個進(jìn)程,文件描述符都自成一套链沼。
標(biāo)準(zhǔn)流(標(biāo)準(zhǔn)文件描述符)
3中標(biāo)準(zhǔn)的文件描述符:
當(dāng)linux啟動后,會自動打開三個文件沛鸵,就是標(biāo)準(zhǔn)輸入括勺、標(biāo)準(zhǔn)輸出、標(biāo)準(zhǔn)錯誤曲掰。標(biāo)準(zhǔn)輸入流默認(rèn)是鍵盤疾捍,標(biāo)準(zhǔn)輸出流默認(rèn)是終端,向錯誤流寫數(shù)據(jù)栏妖,終端的默認(rèn)做法是打印出錯誤內(nèi)容乱豆,當(dāng)然這些流可以更改的。
-
fprintf(stdout, "input someting") <=> printf("input someting")
- 向標(biāo)準(zhǔn)輸出流(終端程序)輸出一個字符串
-
fscanf(stdin, "%d",&a) <=> scanf("%d", &a)
- 向標(biāo)準(zhǔn)輸入流(鍵盤)讀入一個數(shù)據(jù)
-
fprintf(stderr, "a error occur")
- 向標(biāo)準(zhǔn)錯誤流寫入一個錯誤信息
重定向標(biāo)準(zhǔn)流
-
./demo.out 1>>a.txt
輸出流重定向- 將1代表的標(biāo)準(zhǔn)輸出流重定向(>>)到a.txt文件
-
./demo.out 1>>a.txt
等價于./demo.out >>a.txt
-
./demo.out >>a.txt
輸出流中的內(nèi)容是追加的吊趾,追加到結(jié)尾 -
./demo.out >a.txt
輸出流中的內(nèi)容是覆蓋的宛裕,再次寫入會覆蓋之前的內(nèi)容
-
./demo.out <a.txt
輸入流重定向
2.I/O模型
I/O的4個主要系統(tǒng)調(diào)用:
-
fd = open(pathname,flags,mode)
打開或創(chuàng)建一個新文件- flags標(biāo)志
- 位掩碼參數(shù)mode指定了新創(chuàng)建文件的權(quán)限,若open()并未指定O_CREAT標(biāo)志论泛,則忽略該參數(shù)
- S_IRUSER
- S_IWUSER
- 返回文件描述符值
- SUSv3規(guī)定揩尸,如果open()成功,必須保證其返回值為進(jìn)程未用文件描述符中數(shù)值最小者屁奏,如果文件描述符0未使用岩榆,那么open一定會使用此文件描述符打開文件。
- 錯誤處理
- open()返回-1,錯誤號errno標(biāo)識錯誤原因
- EACCES
- EISDIR
- EMFILE
- ENFILE
- ENOENT
- EROFS
- ETXTBSY
- flags標(biāo)志
-
numread = read(fd,buffer,count)
讀取fd所指代的文件中之多count字節(jié)的數(shù)據(jù)勇边,并存儲到buffer中- count參數(shù)指定最多能讀取的字節(jié)數(shù)
- buffer參數(shù)提供用來存放輸入數(shù)據(jù)的內(nèi)存緩存地址
- 返回
- 遇到文件結(jié)束(EOF)則返回0
- 出錯返回 -1
- 正確返回存放讀取的字節(jié)數(shù)
numwritten = write(fd,buffer,count)
-
status = close(fd)
- 文件描述符屬于有限資源犹撒,因此文件描述符關(guān)閉失敗可能會導(dǎo)致一個進(jìn)程將文件描述符資源消耗殆盡。
3.改變文件偏移量:lseek()
off_t lseek(int fd, off_t offset, int whence)
- offset參數(shù)指定了一個以字節(jié)為單位的數(shù)值
- whence參數(shù)則表明贏參照哪個基點來解釋offset參數(shù)粒褒,應(yīng)為下列其中之一:
- SEEK_SET:文件頭部開始
- SEEK_CUR:當(dāng)前文件偏移量處
- SEEK_END:文件結(jié)尾
4.通用I/O模型以外的操作:ioctl()识颊、fcntl()
ioctl()
ioctl()系統(tǒng)調(diào)用又為執(zhí)行文件和設(shè)備操作提供了一種多用途機制。
-
int ioctl(int fd, int request,...);
- request指定了將在fd上執(zhí)行的控制操作
- 第三個參數(shù)...(argp)可以是任意數(shù)據(jù)類型怀浆,根據(jù)request的參數(shù)值來確定argp所期望的類型谊囚。通常情況,argp指向整數(shù)或結(jié)構(gòu)的指針
fcntl()
fcntl()系統(tǒng)調(diào)用對一個打開的文件描述符執(zhí)行一些列控制操作
-
int fcntl(intn fd, int cmd, ...)
- cmd參數(shù)所支持的操作范圍很廣
5.原子操作和競爭條件
原子操作:將某一系統(tǒng)調(diào)用所要完成的各個動作作為不可中斷的操作执赡,一次性加以執(zhí)行镰踏,期間不會為其他進(jìn)程或線程所中斷。所有的系統(tǒng)調(diào)用都是以原子操作方式執(zhí)行的沙合。
舉例:當(dāng)同時制定O_EXCL與O_CREAT作為open()標(biāo)志位時奠伪,如果要打開已經(jīng)存在的文件,就會返回一個錯誤首懈,這提供了一種機制绊率,對文件是否存在的檢查和創(chuàng)建文件屬于同一原子操作。區(qū)別于先檢查文件再創(chuàng)建可能會造成其他進(jìn)程在這個過程中搶占資源究履。
O_EXCL確保調(diào)用者就是文件的創(chuàng)建者滤否。
O_APPEND標(biāo)志,確保多個進(jìn)程在對同一文件追加數(shù)據(jù)時不會覆蓋彼此的輸出最仑。
6.打開文件的狀態(tài)標(biāo)志
獲取訪問模式和狀態(tài)標(biāo)志
fcntl()的用途之一是針對一個打開的文件藐俺,獲取或修改其訪問模式和狀態(tài)標(biāo)志(這些值是通過open()調(diào)用的flag參數(shù)設(shè)置的),應(yīng)將fcntl()的cmd參數(shù)設(shè)置為F_GETTFL泥彤,并且獲取的標(biāo)志中總是包含O_LARGEFILE標(biāo)志
flags = fcntl(fd, F_GETFL);
要判斷是否包含某一標(biāo)志位欲芹,只需要將flags于其相&即可。如下可以判斷文件是否以同步方式打開:
if (flags & O_SYNC)
判定文件的訪問模式稍微復(fù)雜一點吟吝,因為O_RDONLY(0) O_WRONLY(1) O_RDWR(2)這三個常量并不與打開文件狀態(tài)標(biāo)志中的單個比特位對應(yīng)菱父,需使用掩碼O_ACCMODE與flag相與
accessMode = flags & O_ACCMODE
if (accessMode == O_WRONLY) {...}
修改訪問模式和狀態(tài)標(biāo)志
使用fcntl()的F_SETFL來修改,允許更改的標(biāo)志有:
- O_APPEND
- O_NONBLOCK
- O_NOATIME
- A_ASYNC
- O_DIRECT
適用的場景:
- 文件不是由調(diào)用程序打開的剑逃,所以無法使用open來控制這些標(biāo)志(文件是3個標(biāo)準(zhǔn)描述符浙宜,這些描述符在程序啟動之前就被打開)
- 文件描述符獲取是通過open之外的系統(tǒng)調(diào)用,比如pipe()蛹磺、socket()
修改標(biāo)志位代碼如下:
flags = fcntl(fd, F_GETFL)
flags |= O_APPEND
if(fcntl(fd, F_SETFL, flags) == -1) { errExit()}
7.文件描述符和打開文件之間的關(guān)系
文件描述符和打開的文件不一定是一一對應(yīng)的關(guān)系梆奈,多個文件描述符可以指向同一打開文件。這些文件描述符可在相同或不同的進(jìn)程中打開称开。
內(nèi)核維護(hù)的3個數(shù)據(jù)結(jié)構(gòu):
- 進(jìn)程級的文件描述符表
- 系統(tǒng)級的打開文件表
- 文件系統(tǒng)的i-node表
針對每個進(jìn)程亩钟,內(nèi)核為其維護(hù)打開的文件描述符表乓梨,每一條記錄的相關(guān)信息:
- 控制文件描述符操作的一組標(biāo)志
- 對打開文件句柄的引用
內(nèi)核對所有打開的文件維護(hù)一個系統(tǒng)級的描述表格(打開文件表),并將表中各條目稱為打開文件句柄清酥,一個打開文件句柄存儲了與一個打開文件相關(guān)的全部信息:
- 當(dāng)前文件偏移量
- 打開文件時所使用的狀態(tài)標(biāo)識(flags參數(shù))
- 文件訪問模式(只讀只寫等)
- 與信號驅(qū)動I/O相關(guān)的設(shè)置
- 對該文件i-node對象的引用
文件系統(tǒng)會為駐留其上的所有文件建立一個i-node表:
- 文件類型(例如扶镀,普通文件、套接字焰轻、FIFO)和訪問權(quán)限
- 一個指針臭觉,指向該文件所持有的鎖的列表
- 文件的各種屬性,包括文件大小以及與不同類型操作相關(guān)的時間戳
文件描述符辱志、打開的文件句柄蝠筑、i-node的關(guān)系:
總結(jié)以下要點:
- 不同文件描述符(1和2)可以指向同一打開文件句柄,可能是通過調(diào)用dup() dup2()或fcntl()形成的
- 不同進(jìn)程文件描述符可以指向同一打開文件句柄揩懒,可能調(diào)用fork()出現(xiàn)
- 不同的文件句柄指向同一i-node表條目什乙,換言之,指向同一文件已球,可能因為每個進(jìn)程各自對同一文件發(fā)起了open調(diào)用
- 兩個不同的文件描述符臣镣,若指向同一打開文件句柄,將共享同一文件偏移量
- 文件描述符標(biāo)志(close_on_exec標(biāo)志)為進(jìn)程和文件描述符所私有
8.復(fù)制文件描述符
int dup(int oldfd)
dup()調(diào)用復(fù)制一個打開的文件描述符oldfd智亮,并返回一個新描述符忆某,二者都指向同一打開文件句柄。系統(tǒng)會保證新描述一定是編號值最低的未用文件描述符
int dup2(int oldfd, int newfd)
dup2()系統(tǒng)調(diào)用會為oldfd參數(shù)所指定的文件描述符創(chuàng)建副本阔蛉,其編號由newfd參數(shù)指定弃舒,如果newfd已經(jīng)打開,那么dup2會將其先關(guān)閉
newfd = fcntl(oldfd, FU_DUPFD, startfd)
該調(diào)用為oldfd創(chuàng)建一個副本状原,且將使用大于等于startfd的最小未使用值作為描述符編號棒坏。
文件描述符的正、副本之間共享同一打開文件句柄所含的文件偏移量和狀態(tài)標(biāo)志遭笋,新的文件描述符有其自己一套文件描述符標(biāo)志,且其close-onexec標(biāo)志(FD_CLOEXEC)總是處于關(guān)閉
int dup3(int oldfd, int newfd, int flags)
dup3與dup2相同徒探,只是增加了一個附加參數(shù)flag
9.在文件特定偏移量出的I/O:pread()和pwrite()
ssize_t pread(int fd, void *buf, size_t count, off_t offset)
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset)
相比于read()和write()瓦呼,會直接設(shè)置offset參數(shù),是一個原子操作测暗,且性能更好
10.分散輸入和集中輸出:readv()和writev()
readv()和writev()系統(tǒng)調(diào)用分別實現(xiàn)了分散輸入和集中輸出的功能
ssize_t readv(int fd, const struct iovec *iov, int iovcnt)
ssize_t writev(int fd, const struct iovec *iov, int iovcnt)
這些系統(tǒng)調(diào)用并非只對單個緩沖區(qū)進(jìn)行讀寫操作央串,而是一次即可傳輸多個緩存區(qū)的數(shù)據(jù)。數(shù)組iov定義了一組用來傳輸數(shù)據(jù)的緩沖區(qū)碗啄。iovcnt指定了iov的成員個數(shù)质和,iov中的數(shù)據(jù)結(jié)構(gòu):
struct iovec {
void *iov_base;
size_t iov_len;
}
下圖展示關(guān)系:
分散輸入
從文件描述符fd所指代的文件中讀取一片連續(xù)的字節(jié),然后將其散置于iov指定的緩沖區(qū)中稚字,這一散置動作從iov[0]開始依次填滿每個緩沖區(qū)饲宿。是原子性操作厦酬。
集中輸出
將iov所指定的緩沖區(qū)中的數(shù)據(jù)拼接起來,然后寫入fd中瘫想。
在指定offset處分散輸入和集中輸出
preadv()仗阅、pwirtev()
11.截斷文件:truncate()和ftruncate()
truncate()和ftruncate()系統(tǒng)調(diào)用將文件大小設(shè)置為length指定長度
int truncate(const char *pathname, off_t length)
int ftruncate(int fd, off_t length)
若長度大于length則丟棄超出部分,若小于length国夜,則在文件尾追加一系列字節(jié)或一個文件空洞减噪。
12.非阻塞I/O
打開文件時指定O_NONBLOCK標(biāo)志的作用:
- 若open()未能立即打開文件,則返回錯誤车吹,而非陷入阻塞
- 調(diào)用open()成功后筹裕,后續(xù)I/O操作也是非阻塞的
由于管道、FIFO窄驹、套接字朝卒、設(shè)備都支持非阻塞模式,因無法通過open()設(shè)置標(biāo)志馒吴,只能通過fcntl()的F_SETFL命令來修改扎运。
由于內(nèi)核緩沖區(qū)保證了普通文件I/O陷入阻塞,故而打開普通文件會忽略O(shè)_NONBLOCK標(biāo)志
13.大文件I/O
LFS規(guī)范定義了一套擴展功能饮戳,允許在32位系統(tǒng)中運行的進(jìn)程來操作無法以32位表示的大文件豪治。
14./dev/fd 目錄
對于每個進(jìn)程,內(nèi)核都提供一個特殊的虛擬目錄/dev/fd/n扯罐,n是與進(jìn)程中的打開文件描述符相對應(yīng)的編號负拟。
打開/dev/fd目錄中的一個文件等同于復(fù)制相應(yīng)的文件描述符:
fd = open("/dev/fd/1", O_WRONLY)
fd = dup(1)
/dev/fd實際上是一個符號鏈接,鏈接到linux所專有的/proc/self/fd目錄
15.創(chuàng)建臨時文件
mkstemp()歹河、tmpfile()